001package com.fs.starfarer.api.impl.campaign.rulecmd.salvage;
002
003import java.awt.Color;
004import java.util.ArrayList;
005import java.util.LinkedHashMap;
006import java.util.List;
007import java.util.Map;
008import java.util.Random;
009
010import org.lwjgl.input.Keyboard;
011import org.lwjgl.util.vector.Vector2f;
012
013import com.fs.starfarer.api.Global;
014import com.fs.starfarer.api.campaign.CampaignFleetAPI;
015import com.fs.starfarer.api.campaign.CargoAPI;
016import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType;
017import com.fs.starfarer.api.campaign.CargoStackAPI;
018import com.fs.starfarer.api.campaign.CoreInteractionListener;
019import com.fs.starfarer.api.campaign.FactionAPI;
020import com.fs.starfarer.api.campaign.InteractionDialogAPI;
021import com.fs.starfarer.api.campaign.OptionPanelAPI;
022import com.fs.starfarer.api.campaign.ResourceCostPanelAPI;
023import com.fs.starfarer.api.campaign.SectorEntityToken;
024import com.fs.starfarer.api.campaign.SpecialItemData;
025import com.fs.starfarer.api.campaign.TextPanelAPI;
026import com.fs.starfarer.api.campaign.econ.CommoditySpecAPI;
027import com.fs.starfarer.api.campaign.listeners.ListenerUtil;
028import com.fs.starfarer.api.campaign.rules.MemoryAPI;
029import com.fs.starfarer.api.combat.MutableStat;
030import com.fs.starfarer.api.combat.MutableStat.StatMod;
031import com.fs.starfarer.api.combat.ShipVariantAPI;
032import com.fs.starfarer.api.impl.campaign.DerelictShipEntityPlugin;
033import com.fs.starfarer.api.impl.campaign.RepairGantry;
034import com.fs.starfarer.api.impl.campaign.ids.Commodities;
035import com.fs.starfarer.api.impl.campaign.ids.Entities;
036import com.fs.starfarer.api.impl.campaign.ids.Items;
037import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
038import com.fs.starfarer.api.impl.campaign.ids.Stats;
039import com.fs.starfarer.api.impl.campaign.ids.Tags;
040import com.fs.starfarer.api.impl.campaign.procgen.DropGroupRow;
041import com.fs.starfarer.api.impl.campaign.procgen.SalvageEntityGenDataSpec;
042import com.fs.starfarer.api.impl.campaign.procgen.SalvageEntityGenDataSpec.DropData;
043import com.fs.starfarer.api.impl.campaign.procgen.StarSystemGenerator;
044import com.fs.starfarer.api.impl.campaign.procgen.themes.SalvageEntityGeneratorOld;
045import com.fs.starfarer.api.impl.campaign.rulecmd.BaseCommandPlugin;
046import com.fs.starfarer.api.impl.campaign.rulecmd.FireBest;
047import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BaseSalvageSpecial;
048import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.ShipRecoverySpecial.ShipRecoverySpecialData;
049import com.fs.starfarer.api.impl.campaign.terrain.DebrisFieldTerrainPlugin;
050import com.fs.starfarer.api.impl.campaign.terrain.DebrisFieldTerrainPlugin.DebrisFieldParams;
051import com.fs.starfarer.api.impl.campaign.terrain.DebrisFieldTerrainPlugin.DebrisFieldSource;
052import com.fs.starfarer.api.ui.Alignment;
053import com.fs.starfarer.api.ui.TooltipMakerAPI;
054import com.fs.starfarer.api.ui.TooltipMakerAPI.StatModValueGetter;
055import com.fs.starfarer.api.util.Misc;
056import com.fs.starfarer.api.util.Misc.Token;
057import com.fs.starfarer.api.util.WeightedRandomPicker;
058
059/**
060 * NotifyEvent $eventHandle <params> 
061 * 
062 */
063public class SalvageEntity extends BaseCommandPlugin {
064        
065        public static float SALVAGE_DETECTION_MOD_FLAT = 1000;
066        
067        public static int FIELD_RADIUS_FOR_BASE_REQ = 200;
068        public static int FIELD_RADIUS_FOR_MAX_REQ = 1000;
069        public static int FIELD_RADIUS_MAX_REQ_MULT = 10;
070        public static float FIELD_MIN_SALVAGE_MULT = 0.01f;
071        
072        
073        
074        //public static float FIELD_SALVAGE_FRACTION_PER_ATTEMPT = 0.5f;
075        public static float FIELD_SALVAGE_FRACTION_PER_ATTEMPT = 1f;
076        
077        public static float FIELD_CONTENT_MULTIPLIER_AFTER_SALVAGE = 0.25f;
078        //public static float FIELD_CONTENT_MULTIPLIER_AFTER_DEMOLITION = 0.65f;
079        public static float FIELD_CONTENT_MULTIPLIER_AFTER_DEMOLITION = 1f;
080        
081        public static int BASE_MACHINERY = 10;
082        public static int BASE_CREW = 30;
083        public static int MIN_MACHINERY = 5;
084        
085        public static float COST_HEIGHT = 67;
086        
087        
088        protected CampaignFleetAPI playerFleet;
089        protected SectorEntityToken entity;
090        protected FactionAPI playerFaction;
091        protected FactionAPI entityFaction;
092        protected TextPanelAPI text;
093        protected OptionPanelAPI options;
094        protected SalvageEntityGenDataSpec spec;
095        protected CargoAPI cargo;
096        protected MemoryAPI memory;
097        protected InteractionDialogAPI dialog;
098        private DebrisFieldTerrainPlugin debris;
099        private Map<String, MemoryAPI> memoryMap;
100
101        
102        public boolean execute(String ruleId, InteractionDialogAPI dialog, List<Token> params, Map<String, MemoryAPI> memoryMap) {
103                
104                this.dialog = dialog;
105                this.memoryMap = memoryMap;
106                
107                String command = params.get(0).getString(memoryMap);
108                if (command == null) return false;
109                
110                memory = getEntityMemory(memoryMap);
111                
112                entity = dialog.getInteractionTarget();
113                
114                String specId = entity.getCustomEntityType();
115                if (specId == null || entity.getMemoryWithoutUpdate().contains(MemFlags.SALVAGE_SPEC_ID_OVERRIDE)) {
116                        specId = entity.getMemoryWithoutUpdate().getString(MemFlags.SALVAGE_SPEC_ID_OVERRIDE);
117                }
118                spec = SalvageEntityGeneratorOld.getSalvageSpec(specId);
119                
120                text = dialog.getTextPanel();
121                options = dialog.getOptionPanel();
122                
123                playerFleet = Global.getSector().getPlayerFleet();
124                cargo = playerFleet.getCargo();
125                
126                playerFaction = Global.getSector().getPlayerFaction();
127                entityFaction = entity.getFaction();
128                
129                Object test = entity.getMemoryWithoutUpdate().get(MemFlags.SALVAGE_DEBRIS_FIELD);
130                if (test instanceof DebrisFieldTerrainPlugin) {
131                        debris = (DebrisFieldTerrainPlugin) test;
132                }
133                
134                if (command.equals("showCost")) {
135                        if (debris == null) {
136                                showCost();
137                        } else {
138                                //showCost();
139                                showCostDebrisField();
140                        }
141                } else if (command.equals("performSalvage")) {
142                        performSalvage();
143                } else if (command.equals("descDebris")) {
144                        showDebrisDescription();
145                } else if (command.equals("checkAccidents")) {
146                        checkAccidents();
147                } else if (command.equals("demolish")) {
148                        demolish();
149                } else if (command.equals("canBeMadeRecoverable")) {
150                        return canBeMadeRecoverable();
151                } else if (command.equals("showRecoverable")) {
152                        showRecoverable();
153                }
154                
155                return true;
156        }
157
158        private void demolish() {
159                boolean isDebrisField = Entities.DEBRIS_FIELD_SHARED.equals(entity.getCustomEntityType());
160                if (!isDebrisField) {
161                        convertToDebrisField(FIELD_CONTENT_MULTIPLIER_AFTER_DEMOLITION);
162                        
163                        Global.getSoundPlayer().playSound("hit_heavy", 1, 1, Global.getSoundPlayer().getListenerPos(), new Vector2f());
164                        
165                        dialog.dismiss();
166                        
167//                      text.addParagraph("Salvage crews set targeting beacons at key points in the structure, " +
168//                                      "and you give the order to fire once everyone is safely off.");
169//                      text.addParagraph("Salvage crews set targeting beacons at key points in the structure.");                       
170//                      options.clearOptions();
171//                      options.addOption("Leave", "defaultLeave");
172//                      options.setShortcut("defaultLeave", Keyboard.KEY_ESCAPE, false, false, false, true);
173                }
174        }
175
176        private float getAccidentProbability() {
177                if (debris == null) return 0f;
178                float accidentProbability = 0.2f + 0.8f * (1f - debris.getParams().density);
179                if (accidentProbability > 0.9f) accidentProbability = 0.9f;
180                return accidentProbability;
181        }
182        
183        private void checkAccidents() {
184                if (debris == null) {
185                        memory.set("$option", "salPerform");
186                        FireBest.fire(null, dialog, memoryMap, "DialogOptionSelected");
187                        return;
188                }
189                
190                float accidentProbability = getAccidentProbability();
191                //accidentProbability = 1f;
192                
193                long seed = memory.getLong(MemFlags.SALVAGE_SEED);
194                Random random = Misc.getRandom(seed, 175);
195                
196                if (random.nextFloat() > accidentProbability) {
197                        memory.set("$option", "salPerform");
198                        FireBest.fire(null, dialog, memoryMap, "DialogOptionSelected");
199                        return;
200                }
201                
202                Color color = playerFaction.getColor();
203                Color bad = Misc.getNegativeHighlightColor();
204                Color highlight = Misc.getHighlightColor();
205                
206                Map<String, Integer> requiredRes = computeRequiredToSalvage(entity);
207                float reqCrew = (int) requiredRes.get(Commodities.CREW);
208                float reqMachinery = (int) requiredRes.get(Commodities.HEAVY_MACHINERY);
209
210                float crew = playerFleet.getCargo().getCrew();
211                float machinery = playerFleet.getCargo().getCommodityQuantity(Commodities.HEAVY_MACHINERY);
212                float fCrew = crew / reqCrew;
213                if (fCrew < 0) fCrew = 0;
214                if (fCrew > 1) fCrew = 1;
215                
216                float fMachinery = machinery / reqMachinery;
217                if (fMachinery < 0) fMachinery = 0;
218                if (fMachinery > 1) fMachinery = 1;
219                
220                
221//              CommoditySpecAPI crewSpec = Global.getSector().getEconomy().getCommoditySpec(Commodities.CREW);
222//              CommoditySpecAPI machinerySpec = Global.getSector().getEconomy().getCommoditySpec(Commodities.HEAVY_MACHINERY);
223                
224                float lossValue = reqCrew * fCrew * 5f;
225                lossValue += (1f - debris.getParams().density / debris.getParams().baseDensity) * 500f;
226                lossValue *= 0.5f + random.nextFloat();
227                //lossValue *= StarSystemGenerator.getNormalRandom(random, 0.5f, 1.5f);
228                
229                WeightedRandomPicker<String> lossPicker = new WeightedRandomPicker<String>(random);
230                lossPicker.add(Commodities.CREW, 10f + 100f * (1f - fMachinery));
231                lossPicker.add(Commodities.HEAVY_MACHINERY, 10f + 100f * fMachinery);
232                
233                CargoAPI losses = Global.getFactory().createCargo(true);
234                float loss = 0;
235                while (loss < lossValue) {
236                        String id = lossPicker.pick();
237                        CommoditySpecAPI spec = Global.getSector().getEconomy().getCommoditySpec(id);
238                        loss += spec.getBasePrice();
239                        losses.addCommodity(id, 1f);
240                }
241                losses.sort();
242                
243                int crewLost = losses.getCrew();
244                if (crewLost > 0) {
245                        losses.removeCrew(crewLost);
246                        crewLost *= playerFleet.getStats().getDynamic().getValue(Stats.NON_COMBAT_CREW_LOSS_MULT);
247                        if (crewLost < 1) crewLost = 1;
248                        losses.addCrew(crewLost);
249                }
250                
251                int machineryLost = (int) losses.getCommodityQuantity(Commodities.HEAVY_MACHINERY);
252                if (crewLost > crew) crewLost = (int) crew;
253                if (machineryLost > machinery) machineryLost = (int) machinery;
254                
255                if (crewLost <= 0 && machineryLost <= 0) {
256                        memory.set("$option", "salPerform");
257                        FireBest.fire(null, dialog, memoryMap, "DialogOptionSelected");
258                }
259                
260                
261                for (CargoStackAPI stack : losses.getStacksCopy()) {
262                        cargo.removeCommodity(stack.getCommodityId(), stack.getSize());
263                }
264                
265                
266                
267                text.setFontInsignia();
268                text.addParagraph("An accident during the operation has resulted in the loss of ");
269                
270                if (crewLost <= 0) {
271                        text.appendToLastParagraph("" + machineryLost + " heavy machinery.");
272                        text.highlightInLastPara(highlight, "" + machineryLost);
273                } else if (machineryLost <= 0) {
274                        text.appendToLastParagraph("" + crewLost + " crew.");
275                        text.highlightInLastPara(highlight, "" + crewLost);
276                } else {
277                        text.appendToLastParagraph("" + crewLost + " crew and " + machineryLost + " heavy machinery.");
278                        text.highlightInLastPara(highlight, "" + crewLost, "" + machineryLost);
279                }
280                
281                
282                Global.getSoundPlayer().playSound("hit_solid", 1, 1, Global.getSoundPlayer().getListenerPos(), new Vector2f());
283                
284                options.clearOptions();
285                options.addOption("Continue", "salPerform");
286                //FireBest.fire(null, dialog, memoryMap, "PerformSalvage");
287                //FireBest.fire(null, dialog, memoryMap, "PerformSalvage");
288        }
289
290        private void showDebrisDescription() {
291                if (debris == null) return;
292
293                float daysLeft = debris.getDaysLeft();
294                if (daysLeft >= 1000) {
295                        text.addParagraph("The field appears stable and will not drift apart any time soon.");
296                } else {
297                        String atLeastTime = Misc.getAtLeastStringForDays((int) daysLeft);
298                        text.addParagraph("The field is unstable, but should not drift apart for " + atLeastTime + ".");
299                }
300                
301//              boolean stillHot = debris.getGlowDaysLeft() > 0;
302//              switch (debris.getParams().source) {
303//              case BATTLE:
304//                      text.addParagraph("Pieces of ships, weapons, and escape pods litter the starscape.");
305//                      break;
306//              case MIXED:
307//                      text.addParagraph("Pieces of ships, weapons, and escape pods litter the starscape.");
308//                      break;
309//              case PLAYER_SALVAGE:
310//                      break;
311//              case SALVAGE:
312//                      break;
313//              }
314                
315//              if (stillHot) {
316//                      text.appendToLastParagraph(" Some of the pieces of debris are still radiating heat, making any salvage operations more dangerous.");
317//              }
318                
319                float lootValue = 0;
320                for (DropData data : debris.getEntity().getDropValue()) {
321                        lootValue += data.value;
322                }
323                for (DropData data : debris.getEntity().getDropRandom()) {
324                        if (data.value > 0) {
325                                lootValue += data.value;
326                        } else {
327                                lootValue += 500; // close enough
328                        }
329                }
330                float d = debris.getParams().density;
331                
332                lootValue *= d;
333                
334                // doesn't work because "extra" expires
335//              ExtraSalvage extra = BaseSalvageSpecial.getExtraSalvage(memoryMap);
336//              if (extra != null) {
337//                      for (CargoStackAPI stack : extra.cargo.getStacksCopy()) {
338//                              lootValue += stack.getBaseValuePerUnit() * stack.getSize();
339//                      }
340//              }
341                
342                if (lootValue < 500) {
343                        text.appendToLastParagraph(" Long-range scans indicate it's unlikely anything of much value would be found inside.");
344                        text.highlightLastInLastPara("unlikely", Misc.getNegativeHighlightColor());
345                } else if (lootValue < 2500) {
346                        text.appendToLastParagraph(" Long-range scans indicate it's possible something of value could be found inside.");
347                        text.highlightLastInLastPara("possible", Misc.getHighlightColor());
348                } else {
349                        text.appendToLastParagraph(" Long-range scans indicate it's likely something of value could be found inside.");
350                        text.highlightLastInLastPara("likely", Misc.getPositiveHighlightColor());
351                }
352                
353                float accidentProbability = getAccidentProbability();
354                if (accidentProbability <= 0.2f) {
355                        //text.addParagraph("There are indications of some easy pickings to be had, and the risk of an accident during a salvage operation is low.");
356                        text.addPara("There are indications of some easy pickings to be had, and the risk of an accident during a salvage operation is low.",
357                                        Misc.getPositiveHighlightColor(), "low");
358                } else if (accidentProbability < 0.7f) {
359                        text.addPara("There are indications that what salvage is to be had may not be easy to get to, " +
360                                                 "and there's %s risk involved in running a salvage operation.", Misc.getHighlightColor(), "significant");
361                } else {
362                        text.addPara("The salvage that remains is extremely difficult to get to, " +
363                                                 "and there's %s risk involved in running a salvage operation.", Misc.getNegativeHighlightColor(), "high");
364                }
365        }
366
367        public static Map<String, Integer> computeRequiredToSalvage(SectorEntityToken entity) {
368                Map<String, Integer> result = new LinkedHashMap<String, Integer>();
369                
370                String specId = entity.getCustomEntityType();
371                if (specId == null || entity.getMemoryWithoutUpdate().contains(MemFlags.SALVAGE_SPEC_ID_OVERRIDE)) {
372                        specId = entity.getMemoryWithoutUpdate().getString(MemFlags.SALVAGE_SPEC_ID_OVERRIDE);
373                }
374                SalvageEntityGenDataSpec spec = SalvageEntityGeneratorOld.getSalvageSpec(specId);
375                float mult = 1f + spec.getSalvageRating() * 9f;
376                
377                Object test = entity.getMemoryWithoutUpdate().get(MemFlags.SALVAGE_DEBRIS_FIELD);
378                if (test instanceof DebrisFieldTerrainPlugin) {
379                        DebrisFieldTerrainPlugin debris = (DebrisFieldTerrainPlugin) test;
380                        mult = getDebrisReqMult(debris);
381                }
382                
383                int crew = Math.round((int) (BASE_CREW * mult) / 10f) * 10;
384                int machinery = Math.round((int) (BASE_MACHINERY * mult) / 10f) * 10;
385                
386                result.put(Commodities.CREW, crew);
387                result.put(Commodities.HEAVY_MACHINERY, machinery);
388                
389                return result;
390        }
391        
392        protected MutableStat getValueRecoveryStat(boolean withSkillMultForRares) {
393                Map<String, Integer> requiredRes = computeRequiredToSalvage(entity);
394                MutableStat valueRecovery = new MutableStat(1f);
395                int i = 0;
396                
397                float machineryContrib = 0.75f;
398                valueRecovery.modifyPercent("base", -100f);
399                if (machineryContrib < 1f) {
400                        valueRecovery.modifyPercent("base_positive", (int) Math.round(100f - 100f * machineryContrib), "Base effectiveness");
401                }
402                //valueRecovery.modifyPercent("base", -75f);
403                
404                float per = 0.5f;
405                per = 1f;
406                for (String commodityId : requiredRes.keySet()) {
407                        float required = requiredRes.get(commodityId);
408                        float available = (int) cargo.getCommodityQuantity(commodityId);
409                        if (required <= 0) continue;
410                        CommoditySpecAPI spec = Global.getSector().getEconomy().getCommoditySpec(commodityId);
411                        
412                        float val = Math.min(available / required, 1f) * per;
413                        int percent = (int) Math.round(val * 100f);
414                        //valueRecovery.modifyPercent("" + i++, percent, Misc.ucFirst(spec.getLowerCaseName()) + " requirements met");
415                        if (Commodities.HEAVY_MACHINERY.equals(commodityId)) {
416                                val = Math.min(available / required, machineryContrib) * per;
417                                percent = (int) Math.round(val * 100f);
418                                valueRecovery.modifyPercentAlways("" + i++, percent, Misc.ucFirst(spec.getLowerCaseName()) + " available");
419                        } else {
420                                valueRecovery.modifyMultAlways("" + i++, val, Misc.ucFirst(spec.getLowerCaseName()) + " available");
421                        }
422//                      float val = Math.max(1f - available / required, 0f) * per;
423//                      int percent = -1 * (int) Math.round(val * 100f);
424//                      valueRecovery.modifyPercent("" + i++, percent, "Insufficient " + spec.getLowerCaseName());
425                }
426                
427                boolean modified = false;
428                if (withSkillMultForRares) {
429                        for (StatMod mod : playerFleet.getStats().getDynamic().getStat(Stats.SALVAGE_VALUE_MULT_FLEET_INCLUDES_RARE).getFlatMods().values()) {
430                                modified = true;
431                                valueRecovery.modifyPercentAlways("" + i++, (int) Math.round(mod.value * 100f), mod.desc);
432                        }
433                }
434                
435                {
436                        for (StatMod mod : playerFleet.getStats().getDynamic().getStat(Stats.SALVAGE_VALUE_MULT_FLEET_NOT_RARE).getFlatMods().values()) {
437                                modified = true;
438                                valueRecovery.modifyPercentAlways("" + i++, (int) Math.round(mod.value * 100f), mod.desc);
439                        }
440                }
441                if (!modified) {
442                        valueRecovery.modifyPercentAlways("" + i++, (int) Math.round(0f), "Salvaging skill");
443                }
444                
445                float fleetSalvageShips = getPlayerShipsSalvageModUncapped();
446                valueRecovery.modifyPercentAlways("" + i++, (int) Math.round(fleetSalvageShips * 100f), "Fleetwide salvaging capability");
447                
448                return valueRecovery;
449        }
450        
451//      protected StatBonus getRareRecoveryStat() {
452//              StatBonus rareRecovery = new StatBonus();
453//              int i = 0;
454//              for (StatMod mod : playerFleet.getStats().getDynamic().getMod(Stats.SALVAGE_MAX_RATING).getFlatBonuses().values()) {
455//                      rareRecovery.modifyPercent("" + i++, (int) Math.round(mod.value * 100f), mod.desc);
456//              }
457//              return rareRecovery;
458//      }
459        
460        public void showCost() {
461                Color color = playerFaction.getColor();
462                Color bad = Misc.getNegativeHighlightColor();
463                Color highlight = Misc.getHighlightColor();
464                
465                float pad = 3f;
466                float opad = 10f;
467                float small = 5f;
468                
469                Map<String, Integer> requiredRes = computeRequiredToSalvage(entity);
470                
471                text.addParagraph("You receive a preliminary assessment of a potential salvage operation from the exploration crews.");
472                
473                ResourceCostPanelAPI cost = text.addCostPanel("Crew & machinery: required (available)", COST_HEIGHT,
474                                color, playerFaction.getDarkUIColor());
475                cost.setNumberOnlyMode(true);
476                cost.setWithBorder(false);
477                cost.setAlignment(Alignment.LMID);
478                
479                for (String commodityId : requiredRes.keySet()) {
480                        int required = requiredRes.get(commodityId);
481                        int available = (int) cargo.getCommodityQuantity(commodityId);
482                        Color curr = color;
483                        if (required > cargo.getQuantity(CargoItemType.RESOURCES, commodityId)) {
484                                curr = bad;
485                        }
486                        cost.addCost(commodityId, "" + required + " (" + available + ")", curr);
487                }
488                cost.update();
489                
490                
491                MutableStat valueRecovery = getValueRecoveryStat(true);
492                
493                //rareRecovery.unmodify();
494                int valuePercent = (int)Math.round(valueRecovery.getModifiedValue() * 100f);
495                if (valuePercent < 0) valuePercent = 0;
496                String valueString = "" + valuePercent + "%";
497                Color valueColor = highlight;
498
499                if (valuePercent < 100) {
500                        valueColor = bad;
501                }
502                
503                TooltipMakerAPI info = text.beginTooltip();
504                info.setParaSmallInsignia();
505                info.addPara("Resource recovery effectiveness: %s", 0f, valueColor, valueString);
506                if (!valueRecovery.isUnmodified()) {
507                        info.addStatModGrid(300, 50, opad, small, valueRecovery, true, getModPrinter());
508                }
509                text.addTooltip();
510                
511                printSalvageModifiers();
512        }
513        
514        protected StatModValueGetter getModPrinter() {
515                return new StatModValueGetter() {
516                        boolean percent = false;
517                        public String getPercentValue(StatMod mod) {
518                                percent = true;
519                                
520                                 // should make it not shown; it's a "base" value that has to be applied to make the calculations work with multipliers
521                                if (mod.desc == null || mod.desc.isEmpty()) return "";
522                                
523                                String prefix = mod.getValue() >= 0 ? "+" : "";
524                                return prefix + (int)(mod.getValue()) + "%";
525                        }
526                        public String getMultValue(StatMod mod) {percent = false; return null;}
527                        public String getFlatValue(StatMod mod) {percent = false; return null;}
528                        public Color getModColor(StatMod mod) {
529                                if ((!percent && mod.getValue() < 1f) || mod.getValue() < 0) return Misc.getNegativeHighlightColor();
530                                return null;
531                        }
532                };
533        }
534        
535        protected void printSalvageModifiers() {
536                
537                float fuelMult = playerFleet.getStats().getDynamic().getValue(Stats.FUEL_SALVAGE_VALUE_MULT_FLEET);
538                String fuelStr = "" + (int)Math.round((fuelMult - 1f) * 100f) + "%";
539                
540                float rareMult = playerFleet.getStats().getDynamic().getValue(Stats.SALVAGE_VALUE_MULT_FLEET_INCLUDES_RARE);
541                String rareStr = "" + (int)Math.round((rareMult - 1f) * 100f) + "%";
542                
543                if (fuelMult > 1f && rareMult > 1f) {
544                        text.addPara("Your fleet also has a %s bonus to the amount of fuel recovered, and " +
545                                        "a %s bonus to the number of rare items found.",
546                                         Misc.getHighlightColor(), fuelStr, rareStr);
547                } else if (fuelMult > 1) {
548                        text.addPara("Your fleet also has a %s bonus to the amount of fuel recovered.",
549                                         Misc.getHighlightColor(), fuelStr);
550                } else if (rareMult > 1) {
551                        text.addPara("Your fleet also has a %s bonus to the number of rare items found.",
552                                         Misc.getHighlightColor(), rareStr);
553                }
554                
555                if (debris != null) {
556                        text.addParagraph("The density of the debris field affects both the amount of resources and the number of rare items found.");
557                } else {
558                        text.addPara("The recovery effectiveness does not affect the chance of finding rare and valuable items.");
559                }
560
561        }
562        
563        public void showCostDebrisField() {
564                Color color = playerFaction.getColor();
565                Color bad = Misc.getNegativeHighlightColor();
566                Color highlight = Misc.getHighlightColor();
567                
568                float pad = 3f;
569                float opad = 10f;
570                float small = 5f;
571                
572                Map<String, Integer> requiredRes = computeRequiredToSalvage(entity);
573                
574                //text.addParagraph("You receive a preliminary assessment of a potential salvage operation from the exploration crews.");
575                
576                ResourceCostPanelAPI cost = text.addCostPanel("Crew & machinery: required (available)", COST_HEIGHT,
577                                color, playerFaction.getDarkUIColor());
578                cost.setNumberOnlyMode(true);
579                cost.setWithBorder(false);
580                cost.setAlignment(Alignment.LMID);
581                
582                for (String commodityId : requiredRes.keySet()) {
583                        int required = requiredRes.get(commodityId);
584                        int available = (int) cargo.getCommodityQuantity(commodityId);
585                        Color curr = color;
586                        if (required > cargo.getQuantity(CargoItemType.RESOURCES, commodityId)) {
587                                curr = bad;
588                        }
589                        cost.addCost(commodityId, "" + required + " (" + available + ")", curr);
590                }
591                cost.update();
592                
593                
594                MutableStat valueRecovery = getValueRecoveryStat(true);
595                float overallMult = computeOverallMultForDebrisField();
596                valueRecovery.modifyMult("debris_mult", overallMult, "Debris field density");
597                //rareRecovery.unmodify();
598                int valuePercent = (int)Math.round(valueRecovery.getModifiedValue() * 100f);
599                if (valuePercent < 0) valuePercent = 0;
600                String valueString = "" + valuePercent + "%";
601                Color valueColor = highlight;
602
603                if (valuePercent < 100) {
604                        valueColor = bad;
605                }
606                
607                TooltipMakerAPI info = text.beginTooltip();
608                info.setParaSmallInsignia();
609                info.addPara("Scavenging effectiveness: %s", 0f, valueColor, valueString);
610                if (!valueRecovery.isUnmodified()) {
611                        info.addStatModGrid(300, 50, opad, small, valueRecovery, true, getModPrinter());
612                }
613                text.addTooltip();
614                
615//              text.addParagraph("The density of the debris field affects both the amount resources and the number of rare items found.");
616                
617//              text.addParagraph("It's possible to scavenge using fewer crew and less machinery than required, but using fewer crew will reduce " + 
618//                                                "the amount of salvage recovered, while having less machinery will increase the danger to crew.");
619                
620                printSalvageModifiers();
621                
622        }
623        
624        protected float computeOverallMultForDebrisField() {
625                float overallMult = 1f;
626                if (debris != null) {
627//                      Map<String, Integer> reqs = computeRequiredToSalvage(entity);
628//                      float crewMax = 1f;
629//                      if (reqs.get(Commodities.CREW) != null) {
630//                              crewMax = reqs.get(Commodities.CREW);
631//                      }
632//                      float crew = playerFleet.getCargo().getCrew();
633//                      float f = crew / crewMax;
634//                      if (f < 0) f = 0;
635//                      if (f > 1) f = 1;
636//                      
637//                      //if (Global.getSettings().isDevMode()) f = 1f;
638                        
639                        float f = 1f;
640                        DebrisFieldParams params = debris.getParams();
641                        if (params.baseDensity > 0) {
642                                overallMult = params.density / params.baseDensity * f * FIELD_SALVAGE_FRACTION_PER_ATTEMPT;
643                        } else {
644                                overallMult = 0f;
645                        }
646                        if (overallMult < FIELD_MIN_SALVAGE_MULT) overallMult = FIELD_MIN_SALVAGE_MULT;
647                }
648                return overallMult;
649        }
650        
651        
652        public void performSalvage() {
653                long seed = memory.getLong(MemFlags.SALVAGE_SEED);
654                Random random = Misc.getRandom(seed, 100);
655                
656                Misc.stopPlayerFleet();
657                
658//              if (Global.getSettings().isDevMode()) {
659//                      random = Misc.random;
660//              }
661                
662//              float salvageRating = spec.getSalvageRating();
663//              float valueMultFleet = playerFleet.getStats().getDynamic().getValue(Stats.SALVAGE_VALUE_MULT_FLEET_INCLUDES_RARE);
664//              float valueModShips = getPlayerShipsSalvageMod(salvageRating);
665                
666                MutableStat valueRecovery = getValueRecoveryStat(true);
667                float valueMultFleet = valueRecovery.getModifiedValue();
668                float rareItemSkillMult = playerFleet.getStats().getDynamic().getValue(Stats.SALVAGE_VALUE_MULT_FLEET_INCLUDES_RARE);
669
670                List<DropData> dropValue = new ArrayList<DropData>(spec.getDropValue());
671                List<DropData> dropRandom = new ArrayList<DropData>(spec.getDropRandom());
672                dropValue.addAll(entity.getDropValue());
673                dropRandom.addAll(entity.getDropRandom());
674                
675//              DropData d = new DropData();
676//              d.group = "misc_test";
677//              d.chances = 1500;
678//              dropRandom.add(d);
679                
680                
681                float overallMult = computeOverallMultForDebrisField();
682                if (debris != null) {
683                        // to avoid same special triggering over and over while scavenging through
684                        // the same debris field repeatedly
685                        BaseCommandPlugin.getEntityMemory(memoryMap).unset(MemFlags.SALVAGE_SPECIAL_DATA);
686                }
687                
688                float fuelMult = playerFleet.getStats().getDynamic().getValue(Stats.FUEL_SALVAGE_VALUE_MULT_FLEET);
689                CargoAPI salvage = generateSalvage(random, valueMultFleet, rareItemSkillMult, overallMult, fuelMult, dropValue, dropRandom);
690                
691                //ExtraSalvage extra = BaseSalvageSpecial.getExtraSalvage(memoryMap);
692                CargoAPI extra = BaseSalvageSpecial.getCombinedExtraSalvage(memoryMap);
693                salvage.addAll(extra);
694                BaseSalvageSpecial.clearExtraSalvage(memoryMap);
695                if (!extra.isEmpty()) {
696                        ListenerUtil.reportExtraSalvageShown(entity);
697                }
698                
699                //salvage.addCommodity(Commodities.ALPHA_CORE, 1);
700                
701                if (debris != null) {
702                        debris.getParams().density -= overallMult;
703                        if (debris.getParams().density < 0) debris.getParams().density = 0;
704                        
705                        debris.getEntity().getMemoryWithoutUpdate().set(MemFlags.SALVAGE_SEED, random.nextLong());
706                        //System.out.println("Post-salvage density: " + debris.getParams().density);
707                        debris.setScavenged(true);
708                }
709                
710                //if (loot)
711                if (!salvage.isEmpty()) {
712                        dialog.getVisualPanel().showLoot("Salvaged", salvage, false, true, true, new CoreInteractionListener() {
713                                public void coreUIDismissed() {
714                                        long xp = 0;
715                                        if (entity.hasSalvageXP()) {
716                                                xp = (long) (float) entity.getSalvageXP();
717                                        } else if (spec != null && spec.getXpSalvage() > 0) {
718                                                xp = (long) spec.getXpSalvage();
719                                        }
720                                        if (!memory.contains("$doNotDismissDialogAfterSalvage")) {
721                                                dialog.dismiss();
722                                                dialog.hideTextPanel();
723                                                dialog.hideVisualPanel();
724                                                
725                                                if (xp > 0) {
726                                                        Global.getSector().getPlayerPerson().getStats().addXP(xp);
727                                                }
728                                        } else {
729                                                if (xp > 0) {
730                                                        Global.getSector().getPlayerPerson().getStats().addXP(xp, dialog.getTextPanel());
731                                                }
732                                        }
733//                                      if (entity.hasSalvageXP()) {
734//                                              Global.getSector().getPlayerPerson().getStats().addXP((long) (float) entity.getSalvageXP());
735//                                      } else if (spec != null && spec.getXpSalvage() > 0) {
736//                                              Global.getSector().getPlayerPerson().getStats().addXP((long) spec.getXpSalvage());
737//                                      }
738                                        //Global.getSector().setPaused(false);
739                                }
740                        });
741                        options.clearOptions();
742                        dialog.setPromptText("");
743                } else {
744                        text.addParagraph("Operations conclude with nothing of value found.");
745                        options.clearOptions();
746                        String leave = "Leave";
747                        if (memory.contains("$salvageLeaveText")) {
748                                leave = memory.getString("$salvageLeaveText");
749                        }
750                        options.addOption(leave, "defaultLeave");
751                        options.setShortcut("defaultLeave", Keyboard.KEY_ESCAPE, false, false, false, true);
752                }
753                
754
755                boolean isDebrisField = Entities.DEBRIS_FIELD_SHARED.equals(entity.getCustomEntityType());
756                if (!isDebrisField) {
757                        if (!spec.hasTag(Tags.SALVAGE_ENTITY_NO_DEBRIS)) {
758                                convertToDebrisField(FIELD_CONTENT_MULTIPLIER_AFTER_SALVAGE);
759                        } else {
760                                if (!spec.hasTag(Tags.SALVAGE_ENTITY_NO_REMOVE)) { 
761                                        Misc.fadeAndExpire(entity, 1f);
762                                }
763                        }
764                }
765                
766                if (playerFleet != null) {
767                        playerFleet.getStats().addTemporaryModFlat(0.25f, "salvage_ops", 
768                                        "Recent salvage operation", SALVAGE_DETECTION_MOD_FLAT,
769                                        playerFleet.getStats().getDetectedRangeMod());
770                        Global.getSector().addPing(playerFleet, "noticed_player");
771                }
772        }
773        
774        
775        public void convertToDebrisField(float valueMult) {
776                convertToDebrisField(null, valueMult);
777        }
778        
779        public void convertToDebrisField(Random random, float valueMult) {
780                if (random == null) random = new Random();
781                
782                Misc.fadeAndExpire(entity, 1f);
783                
784                float salvageRating = spec.getSalvageRating();
785                //entity.addTag(Tags.NON_CLICKABLE);
786                
787                float debrisFieldRadius = 200f + salvageRating * 400f;
788                
789                float density = 0.5f + salvageRating * 0.5f;
790                density = 1f;
791                if (valueMult <= FIELD_CONTENT_MULTIPLIER_AFTER_SALVAGE) {
792                        density = 0.5f + salvageRating * 0.5f;
793                }
794                
795                float duration = 10f + salvageRating * 20f;
796                
797                DebrisFieldParams params = new DebrisFieldParams(debrisFieldRadius, density, duration, duration * 0.5f);
798                params.source = DebrisFieldSource.PLAYER_SALVAGE;
799                
800//              params.minSize = 12;
801//              params.maxSize = 16;
802//              params.defenderProb = 1;
803//              params.minStr = 20;
804//              params.maxStr = 30;
805//              params.maxDefenderSize = 1;
806                
807                float xp = spec.getXpSalvage() * 0.25f;
808                if (entity.hasSalvageXP()) {
809                        xp = entity.getSalvageXP() * 0.25f;
810                }
811                if (xp >= 10) {
812                        params.baseSalvageXP = (long) xp;
813                }
814                
815                SectorEntityToken debris = Misc.addDebrisField(entity.getContainingLocation(), params, null);
816                
817                //ExtraSalvage extra = BaseSalvageSpecial.getExtraSalvage(memoryMap);
818                CargoAPI extra = BaseSalvageSpecial.getCombinedExtraSalvage(memoryMap);
819                if (extra != null && !extra.isEmpty()) {
820                        // don't prune extra cargo - it could have come from not recovering ships,
821                        // and so could've been gotten by recovering and then stripping/scuttling them
822                        // so shouldn't punish shortcutting that process
823                        // (this can happen when "pound into scrap" vs ship derelict)
824//                      CargoAPI extraCopy = Global.getFactory().createCargo(true);
825//                      for (CargoStackAPI stack : extra.cargo.getStacksCopy()) {
826//                              float qty = stack.getSize();
827//                              qty *= valueMult;
828//                              if (qty < 1) {
829//                                      if (random.nextFloat() >= qty) continue;
830//                                      qty = 1;
831//                              } else {
832//                                      qty = (int) qty;
833//                              }
834//                              extraCopy.addItems(stack.getType(), stack.getData(), qty);
835//                      }
836//                      BaseSalvageSpecial.setExtraSalvage(extraCopy, debris.getMemoryWithoutUpdate(), -1f);
837                        //BaseSalvageSpecial.addExtraSalvage(extra.cargo, debris.getMemoryWithoutUpdate(), -1f);
838                        BaseSalvageSpecial.addExtraSalvage(extra, debris.getMemoryWithoutUpdate(), -1f);
839                }
840                
841//              int count = 0;
842//              for (CampaignTerrainAPI curr : entity.getContainingLocation().getTerrainCopy()) {
843//                      if (curr.getPlugin() instanceof DebrisFieldTerrainPlugin) {
844//                              count++;
845//                      }
846//              }
847                //System.out.println("DEBRIS: " + count);
848                
849                debris.setSensorProfile(null);
850                debris.setDiscoverable(null);
851                //debris.setDiscoveryXP(123f);
852                
853                debris.setFaction(entity.getFaction().getId());
854                
855                debris.getDropValue().clear();
856                debris.getDropRandom().clear();
857                
858                for (DropData data : spec.getDropValue()) {
859                        DropData copy = data.clone();
860                        copy.valueMult = valueMult;
861                        debris.addDropValue(data.clone());
862                }
863                for (DropData data : spec.getDropRandom()) {
864                        DropData copy = data.clone();
865                        copy.valueMult = valueMult;
866                        debris.addDropRandom(copy);
867                }
868                
869                for (DropData data : entity.getDropValue()) {
870                        DropData copy = data.clone();
871                        copy.valueMult = valueMult;
872                        debris.addDropValue(data.clone());
873                }
874                for (DropData data : entity.getDropRandom()) {
875                        DropData copy = data.clone();
876                        copy.valueMult = valueMult;
877                        debris.addDropRandom(copy);
878                }
879                //debris.addDropRandom("weapons_test", 10);
880                
881                if (entity.getOrbit() != null) {
882                        debris.setOrbit(entity.getOrbit().makeCopy());
883                } else {
884                        debris.getLocation().set(entity.getLocation());
885                }
886                
887                long seed = memory.getLong(MemFlags.SALVAGE_SEED);
888                if (seed != 0) {
889                        debris.getMemoryWithoutUpdate().set(MemFlags.SALVAGE_SEED, Misc.getRandom(seed, 150).nextLong());
890                }
891        }
892        
893        
894        
895        
896        public static float getPlayerShipsSalvageModUncapped() {
897                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
898                //float valueModShips = Misc.getFleetwideTotalMod(playerFleet, Stats.SALVAGE_VALUE_MULT_MOD, 0f);
899                float valueModShips = RepairGantry.getAdjustedGantryModifier(playerFleet, null, 0);
900                return valueModShips;
901        }
902//      public static float getPlayerShipsSalvageMod(float salvageRating) {
903//              CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
904//              float valueModShips = Misc.getFleetwideTotalMod(playerFleet, Stats.SALVAGE_VALUE_MULT_MOD, 0f);
905//              if (valueModShips > salvageRating) {
906//                      valueModShips = salvageRating;
907//              }
908//              return valueModShips;
909//      }
910        
911        public static float getDebrisReqMult(DebrisFieldTerrainPlugin field) {
912//              public static int FIELD_RADIUS_FOR_BASE_REQ = 200;
913//              public static int FIELD_RADIUS_FOR_MAX_REQ = 1000;
914//              public static int FIELD_RADIUS_MAX_REQ_MULT = 10;
915                float r = field.getParams().bandWidthInEngine;
916                float f = (r - FIELD_RADIUS_FOR_BASE_REQ) / (FIELD_RADIUS_FOR_MAX_REQ - FIELD_RADIUS_FOR_BASE_REQ);
917                if (f < 0) f = 0;
918                if (f > 1) f = 1;
919                
920                float mult = 1f + (FIELD_RADIUS_MAX_REQ_MULT - 1f) * f;
921                return mult;
922        }
923        
924//      public static CargoAPI generateSalvage(Random random, float valueMult, List<DropData> dropValue, List<DropData> dropRandom) {
925//              return generateSalvage(random, valueMult, 1f, dropValue, dropRandom);
926//      }
927        public static CargoAPI generateSalvage(Random random, float valueMult, float overallMult, float fuelMult, List<DropData> dropValue, List<DropData> dropRandom) {
928                return generateSalvage(random, valueMult, 1f, overallMult, fuelMult, dropValue, dropRandom);
929        }
930        public static CargoAPI generateSalvage(Random random, float valueMult, float randomMult, 
931                        float overallMult, float fuelMult, List<DropData> dropValue, List<DropData> dropRandom) {
932                if (random == null) random = new Random();
933                CargoAPI result = Global.getFactory().createCargo(true);
934                
935                
936                if (Misc.isEasy()) {
937                        overallMult *= Global.getSettings().getFloat("easySalvageMult");
938                }
939//              CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
940                
941                //overallMult = 1f;
942                
943//              float valueMultFleet = playerFleet.getStats().getDynamic().getValue(Stats.SALVAGE_VALUE_MULT_FLEET);
944//              float valueModShips = getPlayerShipsSalvageMod(salvageRating);
945                
946                // check dropRandom first so that changing the drop value by dropping off crew/machinery
947                // does not change the RNG for dropRandom
948                if (dropRandom != null) {
949                        for (DropData data : dropRandom) {
950                                //if (random.nextFloat() < data.valueMult) continue;
951                                
952                                int chances = data.chances;
953                                if (data.maxChances > chances) {
954                                        chances = chances + random.nextInt(data.maxChances - chances + 1);
955                                }
956//                              if (data.group.endsWith("misc_test")) {
957//                                      System.out.println("fewfwefwe");
958//                              }
959                                //WeightedRandomPicker<DropGroupRow> picker = DropGroupRow.getPicker(data.group);
960                                
961                                float modifiedChances = chances;
962                                modifiedChances *= overallMult;
963                                if (data.value <= 0) {
964                                        modifiedChances *= randomMult;
965                                }
966                                modifiedChances *= data.valueMult;
967                                float rem = modifiedChances - (int) modifiedChances;
968                                
969                                chances = (int) modifiedChances + (random.nextFloat() < rem ? 1 : 0);
970                                
971                                WeightedRandomPicker<DropGroupRow> picker = data.getCustom();
972                                if (picker == null && data.group == null) continue; // meant for custom, but empty
973                                if (picker == null) {
974                                        picker = DropGroupRow.getPicker(data.group);
975                                }
976                                
977                                Random innerRandom = Misc.getRandom(random.nextLong(), 5);
978                                //innerRandom = random;
979                                picker.setRandom(innerRandom);
980                                for (int i = 0; i < chances; i++) {
981//                                      if (random.nextFloat() > overallMult) continue;
982//                                      if (random.nextFloat() > data.valueMult) continue;
983                                        
984                                        DropGroupRow row = picker.pick();
985                                        if (row.isMultiValued()) {
986                                                row = row.resolveToSpecificItem(innerRandom);
987                                        }
988                                        
989                                        if (row.isNothing()) continue;
990                                        
991                                        float baseUnitValue = row.getBaseUnitValue();
992                                        
993                                        float qty = 1f;
994                                        if (data.value > 0) {
995                                                float randMult = StarSystemGenerator.getNormalRandom(innerRandom, 0.5f, 1.5f);
996                                                //qty = (data.value * randMult * valueMult * overallMult) / baseUnitValue;
997                                                // valueMult and overallMult are considered in figuring out number of chances to roll
998                                                qty = (data.value * valueMult * randMult) / baseUnitValue;
999                                                qty = (int) qty;
1000                                                if (valueMult <= 0) continue;
1001                                                if (qty < 1) qty = 1;
1002                                        }
1003                                        
1004                                        
1005                                        if (row.isWeapon()) {
1006                                                result.addWeapons(row.getWeaponId(), (int) qty);
1007//                                      } else if (row.isHullMod()) {
1008//                                              result.addItems(CargoItemType.MOD_SPEC, row.getHullModId(), (int) qty);
1009                                        } else if (row.isFighterWing()) {
1010                                                result.addItems(CargoItemType.FIGHTER_CHIP, row.getFighterWingId(), (int) qty);
1011                                        } else if (row.isSpecialItem()) { 
1012                                                if (Items.TAG_MODSPEC.equals(row.getSpecialItemId()) && 
1013                                                                result.getQuantity(CargoItemType.SPECIAL, 
1014                                                                                new SpecialItemData(row.getSpecialItemId(), row.getSpecialItemData())) > 0) {
1015                                                        continue;
1016                                                }
1017                                                result.addItems(CargoItemType.SPECIAL, 
1018                                                                new SpecialItemData(row.getSpecialItemId(), row.getSpecialItemData()), (int) qty);
1019                                        } else {
1020                                                result.addCommodity(row.getCommodity(), qty);
1021                                        }
1022                                }
1023                        }
1024                }
1025                
1026                
1027                if (dropValue != null) {
1028                        
1029                        for (DropData data : dropValue) {
1030                                //if (random.nextFloat() < data.valueMult) continue;
1031                                
1032                                float maxValue = data.value;
1033                                
1034                                // if value is 1, it's a "guaranteed pick one out of this usually-dropRandom group"
1035                                // so still allow it even if valueMult is 0 due to a lack of heavy machinery
1036                                // since dropRandom works w/ no machinery, too
1037                                if (data.value > 1) {
1038                                        maxValue *= valueMult;
1039                                }
1040                                
1041                                maxValue *= overallMult;
1042                                maxValue *= data.valueMult;
1043                                
1044                                float randMult = StarSystemGenerator.getNormalRandom(random, 0.5f, 1.5f);
1045                                maxValue *= randMult;
1046                                
1047                                
1048                                WeightedRandomPicker<DropGroupRow> picker = data.getCustom();
1049                                if (picker == null && data.group == null) continue; // meant for custom, but empty
1050                                if (picker == null) {
1051                                        picker = DropGroupRow.getPicker(data.group);
1052                                }
1053                                picker.setRandom(random);
1054                                float value = 0f;
1055                                int nothingInARow = 0;
1056                                while (value < maxValue && nothingInARow < 10) {
1057                                        DropGroupRow row = picker.pick();
1058                                        if (row.isMultiValued()) {
1059                                                row = row.resolveToSpecificItem(random);
1060                                        }
1061                                        if (row.isNothing()) {
1062                                                nothingInARow++;
1063                                                continue;
1064                                        } else {
1065                                                nothingInARow = 0;
1066                                        }
1067                                        //System.out.println(nothingInARow);
1068                                        
1069                                        float baseUnitValue = row.getBaseUnitValue();
1070                                        
1071                                        float qty = 1f;
1072                                        float currValue = baseUnitValue * qty;
1073                                        value += currValue;
1074                                        
1075                                        if (row.isWeapon()) {
1076                                                if (value <= maxValue) {
1077                                                        result.addWeapons(row.getWeaponId(), (int) qty);
1078                                                }
1079//                                      } else if (row.isHullMod()) {
1080//                                              if (value <= maxValue) {
1081//                                                      result.addHullmods(row.getHullModId(), (int) qty);
1082//                                              }
1083                                        } else if (row.isFighterWing()) {
1084                                                if (value <= maxValue) {
1085                                                        result.addItems(CargoItemType.FIGHTER_CHIP, row.getFighterWingId(), (int) qty);
1086                                                }
1087                                        } else if (row.isSpecialItem()) {
1088                                                if (Items.TAG_MODSPEC.equals(row.getSpecialItemId()) && 
1089                                                                result.getQuantity(CargoItemType.SPECIAL, 
1090                                                                                new SpecialItemData(row.getSpecialItemId(), row.getSpecialItemData())) > 0) {
1091                                                        continue;
1092                                                }
1093                                                result.addItems(CargoItemType.SPECIAL, 
1094                                                                        new SpecialItemData(row.getSpecialItemId(), row.getSpecialItemData()), (int) qty);
1095                                        } else {
1096                                                if (value <= maxValue) {
1097                                                        result.addCommodity(row.getCommodity(), qty);
1098                                                }
1099                                        }
1100                                }
1101                        }
1102                }
1103                
1104                
1105                float fuel = result.getFuel();
1106                if (fuelMult > 1f) {
1107                        result.addFuel((int) Math.round(fuel * (fuelMult - 1f)));
1108                }
1109                
1110                result.sort();
1111                
1112                return result;
1113        }
1114        
1115        
1116        public boolean canBeMadeRecoverable() {
1117                if (entity.getCustomPlugin() instanceof DerelictShipEntityPlugin) {
1118                        
1119                        //if (Misc.getSalvageSpecial(entity) != null) return false;
1120                        
1121                        if (Misc.getSalvageSpecial(entity) instanceof ShipRecoverySpecialData) {
1122                                return false;
1123                        }
1124                        if (entity.hasTag(Tags.UNRECOVERABLE)) {
1125                                return false;
1126                        }
1127                        
1128//                      int room = Global.getSettings().getMaxShipsInFleet() - 
1129//                                         Global.getSector().getPlayerFleet().getFleetData().getMembersListCopy().size();
1130//                      if (room < 1) return false;
1131                        
1132                        DerelictShipEntityPlugin plugin = (DerelictShipEntityPlugin) entity.getCustomPlugin();
1133                        ShipVariantAPI variant = plugin.getData().ship.getVariant();
1134                        if (variant != null && !Misc.isUnboardable(variant.getHullSpec())) {
1135                                return true;
1136                        }
1137                }
1138                return false;
1139        }
1140
1141        
1142        public void showRecoverable() {
1143                
1144                Object prev = Misc.getSalvageSpecial(entity);
1145                if (prev != null) {
1146                        Misc.setPrevSalvageSpecial(entity, prev);
1147                }
1148                
1149                ShipRecoverySpecialData data = new ShipRecoverySpecialData(null);
1150                DerelictShipEntityPlugin plugin = (DerelictShipEntityPlugin) entity.getCustomPlugin();
1151                data.addShip(plugin.getData().ship.clone());
1152                data.storyPointRecovery = true;
1153                Misc.setSalvageSpecial(entity, data);
1154                
1155                long seed = Misc.getSalvageSeed(entity);
1156                entity.getMemoryWithoutUpdate().set(MemFlags.SALVAGE_SEED, seed);
1157        }
1158        
1159}
1160
1161
1162
1163
1164
1165
1166