Starsector API
Loading...
Searching...
No Matches
DerelictShipEntityPlugin.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.campaign;
2
3import java.util.HashSet;
4import java.util.List;
5import java.util.Random;
6import java.util.Set;
7
8import com.fs.starfarer.api.Global;
9import com.fs.starfarer.api.campaign.CampaignEngineLayers;
10import com.fs.starfarer.api.campaign.CargoAPI;
11import com.fs.starfarer.api.campaign.CustomCampaignEntityAPI;
12import com.fs.starfarer.api.campaign.FactionAPI;
13import com.fs.starfarer.api.campaign.SectorEntityToken;
14import com.fs.starfarer.api.campaign.SectorEntityToken.VisibilityLevel;
15import com.fs.starfarer.api.combat.ShipAPI.HullSize;
16import com.fs.starfarer.api.combat.ViewportAPI;
17import com.fs.starfarer.api.fleet.FleetMemberAPI;
18import com.fs.starfarer.api.fleet.FleetMemberType;
19import com.fs.starfarer.api.graphics.SpriteAPI;
20import com.fs.starfarer.api.impl.campaign.ids.Drops;
21import com.fs.starfarer.api.impl.campaign.ids.ShipRoles;
22import com.fs.starfarer.api.impl.campaign.procgen.DropGroupRow;
23import com.fs.starfarer.api.impl.campaign.procgen.SalvageEntityGenDataSpec.DropData;
24import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BaseSalvageSpecial;
25import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.ShipRecoverySpecial.PerShipData;
26import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.ShipRecoverySpecial.ShipCondition;
27import com.fs.starfarer.api.ui.TooltipMakerAPI;
28import com.fs.starfarer.api.util.Misc;
29import com.fs.starfarer.api.util.WeightedRandomPicker;
30
32
33 public static float DEFAULT_SMOD_PROB = 0.05f;
34
35 public static enum DerelictType {
36 SMALL,
37 MEDIUM,
38 LARGE,
39 CIVILIAN
40 }
41
42 public static float getDefaultSModProb() {
43 return DEFAULT_SMOD_PROB;
44 }
45
46 public static DerelictShipData createHull(String hullId, Random random, float sModProb) {
47 List<String> list = Global.getSettings().getHullIdToVariantListMap().get(hullId);
48 WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(random);
49 picker.addAll(list);
50 String variantId = picker.pick();
51 if (variantId == null) {
52 return null;
53 }
54 return createVariant(variantId, random, sModProb);
55 }
56 public static DerelictShipData createVariant(String variantId, Random random, float sModProb) {
57 ShipCondition condition = pickDerelictCondition(random);
58 PerShipData ship = new PerShipData(variantId, condition, sModProb);
59 return new DerelictShipData(ship, true);
60 }
61
62 public static DerelictShipData createRandom(String factionId, DerelictType type, Random random) {
63 return createRandom(factionId, type, random, 0f);
64 }
65 public static DerelictShipData createRandom(String factionId, DerelictType type, Random random, float sModProb) {
66 if (random == null) random = new Random();
67 if (type == null) type = pickDerelictType(random);
68 String variantId = null;
69 switch (type) {
70 case CIVILIAN: variantId = pickCivilianVariantId(factionId, random); break;
71 case LARGE: variantId = pickLargeVariantId(factionId, random); break;
72 case MEDIUM: variantId = pickMediumVariantId(factionId, random); break;
73 case SMALL: variantId = pickSmallVariantId(factionId, random); break;
74 }
75
76 if (variantId == null) return null;
77
78 ShipCondition condition = pickDerelictCondition(random);
79
80 PerShipData ship = new PerShipData(variantId, condition, sModProb);
81
82 return new DerelictShipData(ship, true);
83 }
84
85 public static DerelictType pickDerelictType(Random random) {
86 if (random == null) random = new Random();
87 WeightedRandomPicker<DerelictType> picker = new WeightedRandomPicker<DerelictType>(random);
88
89 picker.add(DerelictType.CIVILIAN, 10f);
90 picker.add(DerelictType.LARGE, 5f);
91 picker.add(DerelictType.MEDIUM, 10f);
92 picker.add(DerelictType.SMALL, 20f);
93
94 return picker.pick();
95 }
96 public static ShipCondition pickDerelictCondition(Random random) {
97 if (random == null) random = new Random();
98 WeightedRandomPicker<ShipCondition> picker = new WeightedRandomPicker<ShipCondition>(random);
99
100 picker.add(ShipCondition.WRECKED, 10f);
101 picker.add(ShipCondition.BATTERED, 10f);
102 picker.add(ShipCondition.AVERAGE, 7f);
103 picker.add(ShipCondition.GOOD, 5f);
104 picker.add(ShipCondition.PRISTINE, 1f);
105
106 return picker.pick();
107 }
108
109 public static ShipCondition pickBadCondition(Random random) {
110 if (random == null) random = new Random();
111 WeightedRandomPicker<ShipCondition> picker = new WeightedRandomPicker<ShipCondition>(random);
112
113 picker.add(ShipCondition.WRECKED, 10f);
114 picker.add(ShipCondition.BATTERED, 10f);
115 picker.add(ShipCondition.AVERAGE, 3f);
116
117 return picker.pick();
118 }
119
120
121 public static String pickCivilianVariantId(String factionId, Random random) {
122 String variantId = pickVariant(factionId, random,
123 ShipRoles.CIV_RANDOM, 7f, // ox or crig
124 ShipRoles.FREIGHTER_SMALL, 10f,
125 ShipRoles.FREIGHTER_MEDIUM, 3f,
126 ShipRoles.FREIGHTER_LARGE, 1f,
127 ShipRoles.LINER_SMALL, 10f,
128 ShipRoles.LINER_MEDIUM, 3f,
129 ShipRoles.LINER_LARGE, 1f,
130 ShipRoles.TANKER_SMALL, 10f,
131 ShipRoles.TANKER_MEDIUM, 3f,
132 ShipRoles.TANKER_LARGE, 1f,
133 ShipRoles.PERSONNEL_SMALL, 10f,
134 ShipRoles.PERSONNEL_MEDIUM, 3f,
135 ShipRoles.PERSONNEL_LARGE, 1f
136 );
137 return variantId;
138 }
139 public static String pickSmallVariantId(String factionId, Random random) {
140 String variantId = pickVariant(factionId, random,
141 ShipRoles.COMBAT_SMALL, 10f,
142 ShipRoles.COMBAT_FREIGHTER_SMALL, 3f,
143 ShipRoles.FREIGHTER_SMALL, 1f,
144 ShipRoles.TANKER_SMALL, 1f,
145 ShipRoles.LINER_SMALL, 1f,
146 ShipRoles.PERSONNEL_SMALL, 1f
147 );
148 return variantId;
149 }
150
151 public static String pickMediumVariantId(String factionId, Random random) {
152 String variantId = pickVariant(factionId, random,
153 ShipRoles.COMBAT_MEDIUM, 10f,
154 ShipRoles.COMBAT_FREIGHTER_MEDIUM, 3f,
155 ShipRoles.CARRIER_SMALL, 1f,
156 ShipRoles.FREIGHTER_MEDIUM, 1f,
157 ShipRoles.TANKER_MEDIUM, 1f,
158 ShipRoles.LINER_MEDIUM, 1f,
159 ShipRoles.PERSONNEL_MEDIUM, 1f
160 );
161 return variantId;
162 }
163
164 public static String pickLargeVariantId(String factionId, Random random) {
165 String variantId = pickVariant(factionId, random,
166 ShipRoles.COMBAT_LARGE, 10f,
167 ShipRoles.COMBAT_CAPITAL, 3f,
168 ShipRoles.COMBAT_FREIGHTER_LARGE, 1f,
169 ShipRoles.CARRIER_MEDIUM, 1f,
170 ShipRoles.FREIGHTER_LARGE, 1f,
171 ShipRoles.CARRIER_LARGE, 1f,
172 ShipRoles.TANKER_LARGE, 1f,
173 ShipRoles.LINER_LARGE, 1f,
174 ShipRoles.TANKER_MEDIUM, 1f,
175 ShipRoles.PERSONNEL_LARGE, 1f
176 );
177 return variantId;
178 }
179
180
181
182 public static String pickVariant(String factionId, Random random, Object ... shipRoles) {
183 if (random == null) random = new Random();
184
185 FactionAPI faction = Global.getSector().getFaction(factionId);
186
187 WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(random);
188 for (int i = 0; i < shipRoles.length; i += 2) {
189 String role = (String) shipRoles[i];
190 Float weight = (Float) shipRoles[i + 1];
191 picker.add(role, weight);
192 }
193
194 Set<String> variantsForRole = new HashSet<String>();
195 while (variantsForRole.isEmpty() && !picker.isEmpty()) {
196 String role = picker.pickAndRemove();
197 if (role == null) return null;
198
199 variantsForRole = faction.getVariantsForRole(role);
200 }
201
202 picker.clear();
203 picker.addAll(variantsForRole);
204 String variantId = picker.pick();
205
206 return variantId;
207 }
208
209
210
211 public static class DerelictShipData {
212 public PerShipData ship;
213 public float durationDays = 10000000f;
214 public boolean canHaveExtraCargo = false;
215 public DerelictShipData(PerShipData ship, boolean canHaveExtraCargo) {
216 this.ship = ship;
217 this.canHaveExtraCargo = canHaveExtraCargo;
218 }
219// public DerelictShipData(String variantId, ShipCondition condition, float duration, boolean canHaveExtraCargo) {
220// if (condition == null) condition = pickDerelictCondition(null);
221// if (duration <= 0) duration = 1000000000f;
222// durationDays = duration;
223// ship = new PerShipData(variantId, condition);
224// this.canHaveExtraCargo = canHaveExtraCargo;
225// }
226 }
227
228 //private CustomCampaignEntityAPI entity;
229 private DerelictShipData data;
230
231 private transient GenericCampaignEntitySprite sprite;
232 private transient FleetMemberAPI member;
233 private transient float scale;
234
235 private float angVel = 0f;
236
237 public void init(SectorEntityToken entity, Object params) {
238 super.init(entity, params);
239 //this.entity = (CustomCampaignEntityAPI) entity;
240 data = (DerelictShipData) params;
241
242 angVel = 5f + (float) Math.random() * 10f;
243 angVel *= Math.signum((float) Math.random() - 0.5f);
244
245 readResolve();
246
247 entity.setSensorProfile(1f);
248 entity.setDiscoverable(false);
249
250 float range = getDetectedAtRange(member.getHullSpec().getHullSize());
251
252 // "gen" is id used when spawning salvage entity by default
253 // so this overrides that value
254 entity.getDetectedRangeMod().modifyFlat("gen", range);
255
256 ((CustomCampaignEntityAPI)entity).setRadius(getRadius(member.getHullSpec().getHullSize()));
257
258 // add some default salvage
259 // some uses of this will want to clear that out and add something more specific
260 DropData data = new DropData();
261 data.group = Drops.BASIC;
262 data.value = (int) getBasicDropValue(member);
263 entity.addDropValue(data);
264
265// data = new DropData();
266// data.group = Drops.ANY_HULLMOD_LOW;
267// data.chances = 1;
268// entity.addDropRandom(data);
269
270 if (this.data.canHaveExtraCargo) {
271 // why add this as extraSavlage instead of drops?
272 // because needs to be based on cargo capacity not cargo value (which all drops are)
273 long seed = Misc.getSalvageSeed(entity);
274 Random r = Misc.getRandom(seed, 2);
275 float extraProb = 0.5f;
276 if (r.nextFloat() < extraProb) {
277 if (member.getVariant().isFreighter()) {
278 WeightedRandomPicker<DropGroupRow> picker = DropGroupRow.getPicker(Drops.FREIGHTER_CARGO);
279 picker.setRandom(new Random(seed));
280 CargoAPI extraSalvage = Global.getFactory().createCargo(true);
281 for (int i = 0; i < 3; i++) {
282 DropGroupRow pick = picker.pick();
283 if (pick.isCommodity()) {
284 extraSalvage.addCommodity(pick.getCommodity(),
285 (int)Math.ceil(member.getCargoCapacity() * (0.15f + 0.15f * r.nextFloat())));
286 }
287 }
288 BaseSalvageSpecial.addExtraSalvage(extraSalvage, entity.getMemoryWithoutUpdate(), -1);
289 } else if (member.getVariant().isTanker()) {
290 CargoAPI extraSalvage = Global.getFactory().createCargo(true);
291 extraSalvage.addFuel((int)Math.ceil(member.getFuelCapacity() * (0.25f + 0.25f * r.nextFloat())));
292 BaseSalvageSpecial.addExtraSalvage(extraSalvage, entity.getMemoryWithoutUpdate(), -1);
293 }
294 }
295 }
296
297
298
299
300 // can't be "discovered" by default, but something else could setDiscoverable(true)
301 // in which case this XP value will matter
302 entity.setDiscoveryXP((float) data.value * 0.05f);
303
304 entity.setSalvageXP((float) data.value * 0.15f);
305
306 //this.data.durationDays = 1f;
307
308 }
309
310 public static float getRadius(HullSize size) {
311 switch (size) {
312 case CAPITAL_SHIP: return 40f;
313 case CRUISER: return 35f;
314 case DESTROYER: return 30f;
315 case FRIGATE: return 25f;
316 }
317 return 20f;
318 }
319
320 public static float getBaseDuration(HullSize size) {
321 switch (size) {
322 case CAPITAL_SHIP: return 50f;
323 case CRUISER: return 40f;
324 case DESTROYER: return 30f;
325 case FRIGATE: return 25f;
326 }
327 return 25f;
328 }
329
330 public static float getDetectedAtRange(HullSize size) {
331 switch (size) {
332 case CAPITAL_SHIP: return 1700f;
333 case CRUISER: return 1300f;
334 case DESTROYER: return 1000f;
335 case FRIGATE: return 800f;
336 }
337 return 800f;
338 }
339
340 public static float getBasicDropValue(FleetMemberAPI member) {
341 float value = member.getDeploymentCostSupplies() * 200f;
342 return value;
343 }
344
345 Object readResolve() {
346 //sprite = new GenericFieldItemSprite(entity, category, key, cellSize, size, spawnRadius);
347 if (data.ship.variantId != null) {
348 member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, data.ship.variantId);
349 } else {
350 member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, data.ship.variant);
351 }
352
353 scale = Misc.getCampaignShipScaleMult(member.getHullSpec().getHullSize());
354
355 //scale *= 5f;
356
357 sprite = new GenericCampaignEntitySprite(entity, member.getHullSpec().getSpriteName(), scale);
358
359 SpriteAPI base = Global.getSettings().getSprite(member.getHullSpec().getSpriteName());
360 SpriteAPI overlay = Global.getSettings().getSprite("misc", "campaignDerelictOverlay");
361 float w = base.getWidth();
362 float h = base.getHeight();
363 float size = Math.max(w, h) * 3f * scale;
364 overlay.setSize(size, size);
365 sprite.setOverlay(overlay);
366
367// SpriteAPI glow = Global.getSettings().getSprite("misc", "campaignDerelictOverlay");
368// glow.setSize(size, size);
369// sprite.setGlow(glow);
370
371 return this;
372 }
373
374 protected float elapsed = 0f;
375 protected Boolean expiring = null;
376 public void advance(float amount) {
377 if (entity.isInCurrentLocation()) {
378 float turn = amount * angVel;
379 entity.setFacing(Misc.normalizeAngle(entity.getFacing() + turn));
380 }
381
382// if (!entity.hasTag(Tags.NON_CLICKABLE)) {
383// Misc.fadeAndExpire(entity);
384// }
385
386 float days = Global.getSector().getClock().convertToDays(amount);
387 elapsed += days;
388
389 if (elapsed > data.durationDays && expiring == null) {
390 VisibilityLevel vis = entity.getVisibilityLevelToPlayerFleet();
391 boolean playerCanSee = entity.isInCurrentLocation() &&
392 (vis == VisibilityLevel.COMPOSITION_AND_FACTION_DETAILS ||
393 vis == VisibilityLevel.COMPOSITION_DETAILS);
394 if (!playerCanSee) {
395 Misc.fadeAndExpire(entity, 1f);
396 expiring = true;
397 }
398 }
399 }
400
401 public float getRenderRange() {
402 return entity.getRadius() + 100f;
403 }
404
405 public void render(CampaignEngineLayers layer, ViewportAPI viewport) {
406 float alphaMult = viewport.getAlphaMult();
407 alphaMult *= entity.getSensorFaderBrightness();
408 alphaMult *= entity.getSensorContactFaderBrightness();
409 if (alphaMult <= 0) return;
410
411 sprite.render(0, 0, entity.getFacing(), alphaMult);
412 }
413
414 public DerelictShipData getData() {
415 return data;
416 }
417
418 @Override
419 public void appendToCampaignTooltip(TooltipMakerAPI tooltip, VisibilityLevel level) {
420 // doesn't work, since those aren't rolled for until the ship is interacted-with
421// if (Misc.getCurrPermanentMods(data.ship.variant) > 0) {
422// float opad = 10f;
423// tooltip.addPara("Sensor returns are slightly abnormal.", opad);
424// }
425 }
426
427
428}
429
430
431
432
433
434
435
436
437
438
static SettingsAPI getSettings()
Definition Global.java:51
static FactoryAPI getFactory()
Definition Global.java:35
static SectorAPI getSector()
Definition Global.java:59
static String pickVariant(String factionId, Random random, Object ... shipRoles)
static DerelictShipData createRandom(String factionId, DerelictType type, Random random, float sModProb)
static String pickCivilianVariantId(String factionId, Random random)
static String pickSmallVariantId(String factionId, Random random)
static String pickLargeVariantId(String factionId, Random random)
static DerelictShipData createRandom(String factionId, DerelictType type, Random random)
static String pickMediumVariantId(String factionId, Random random)
void appendToCampaignTooltip(TooltipMakerAPI tooltip, VisibilityLevel level)
void render(CampaignEngineLayers layer, ViewportAPI viewport)
static DerelictShipData createVariant(String variantId, Random random, float sModProb)
static DerelictShipData createHull(String hullId, Random random, float sModProb)
CargoAPI createCargo(boolean unlimitedStacks)
FleetMemberAPI createFleetMember(FleetMemberType type, String variantOrWingId)
ListMap< String > getHullIdToVariantListMap()
SpriteAPI getSprite(String filename)