001package com.fs.starfarer.api.impl.campaign;
002
003import java.util.HashSet;
004import java.util.List;
005import java.util.Random;
006import java.util.Set;
007
008import com.fs.starfarer.api.Global;
009import com.fs.starfarer.api.campaign.CampaignEngineLayers;
010import com.fs.starfarer.api.campaign.CargoAPI;
011import com.fs.starfarer.api.campaign.CustomCampaignEntityAPI;
012import com.fs.starfarer.api.campaign.FactionAPI;
013import com.fs.starfarer.api.campaign.SectorEntityToken;
014import com.fs.starfarer.api.campaign.SectorEntityToken.VisibilityLevel;
015import com.fs.starfarer.api.combat.ShipAPI.HullSize;
016import com.fs.starfarer.api.combat.ViewportAPI;
017import com.fs.starfarer.api.fleet.FleetMemberAPI;
018import com.fs.starfarer.api.fleet.FleetMemberType;
019import com.fs.starfarer.api.graphics.SpriteAPI;
020import com.fs.starfarer.api.impl.campaign.ids.Drops;
021import com.fs.starfarer.api.impl.campaign.ids.ShipRoles;
022import com.fs.starfarer.api.impl.campaign.procgen.DropGroupRow;
023import com.fs.starfarer.api.impl.campaign.procgen.SalvageEntityGenDataSpec.DropData;
024import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BaseSalvageSpecial;
025import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.ShipRecoverySpecial.PerShipData;
026import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.ShipRecoverySpecial.ShipCondition;
027import com.fs.starfarer.api.ui.TooltipMakerAPI;
028import com.fs.starfarer.api.util.Misc;
029import com.fs.starfarer.api.util.WeightedRandomPicker;
030
031public class DerelictShipEntityPlugin extends BaseCustomEntityPlugin {
032
033        public static float DEFAULT_SMOD_PROB = 0.05f;
034        
035        public static enum DerelictType {
036                SMALL,
037                MEDIUM,
038                LARGE,
039                CIVILIAN
040        }
041        
042        public static float getDefaultSModProb() {
043                return DEFAULT_SMOD_PROB;
044        }
045        
046        public static DerelictShipData createHull(String hullId, Random random, float sModProb) {
047                List<String> list = Global.getSettings().getHullIdToVariantListMap().get(hullId);
048                WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(random);
049                picker.addAll(list);
050                String variantId = picker.pick();
051                if (variantId == null) {
052                        return null;
053                }
054                return createVariant(variantId, random, sModProb);
055        }
056        public static DerelictShipData createVariant(String variantId, Random random, float sModProb) {
057                ShipCondition condition = pickDerelictCondition(random);
058                PerShipData ship = new PerShipData(variantId, condition, sModProb);
059                return new DerelictShipData(ship, true);
060        }
061        
062        public static DerelictShipData createRandom(String factionId, DerelictType type, Random random) {
063                return createRandom(factionId, type, random, 0f);
064        }
065        public static DerelictShipData createRandom(String factionId, DerelictType type, Random random, float sModProb) {
066                if (random == null) random = new Random();
067                if (type == null) type = pickDerelictType(random);
068                String variantId = null;
069                switch (type) {
070                case CIVILIAN: variantId = pickCivilianVariantId(factionId, random); break;
071                case LARGE: variantId = pickLargeVariantId(factionId, random); break;
072                case MEDIUM: variantId = pickMediumVariantId(factionId, random); break;
073                case SMALL: variantId = pickSmallVariantId(factionId, random); break;
074                }
075                
076                if (variantId == null) return null;
077                
078                ShipCondition condition = pickDerelictCondition(random);
079                
080                PerShipData ship = new PerShipData(variantId, condition, sModProb);
081                
082                return new DerelictShipData(ship, true);
083        }
084        
085        public static DerelictType pickDerelictType(Random random) {
086                if (random == null) random = new Random();
087                WeightedRandomPicker<DerelictType> picker = new WeightedRandomPicker<DerelictType>(random);
088                
089                picker.add(DerelictType.CIVILIAN, 10f);
090                picker.add(DerelictType.LARGE, 5f);
091                picker.add(DerelictType.MEDIUM, 10f);
092                picker.add(DerelictType.SMALL, 20f);
093                
094                return picker.pick();
095        }
096        public static ShipCondition pickDerelictCondition(Random random) {
097                if (random == null) random = new Random();
098                WeightedRandomPicker<ShipCondition> picker = new WeightedRandomPicker<ShipCondition>(random);
099                
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