001package com.fs.starfarer.api.impl.campaign;
002
003import java.util.ArrayList;
004import java.util.HashSet;
005import java.util.List;
006import java.util.Random;
007import java.util.Set;
008
009import java.awt.Color;
010
011import org.apache.log4j.Logger;
012import org.lwjgl.util.vector.Vector2f;
013
014import com.fs.starfarer.api.EveryFrameScript;
015import com.fs.starfarer.api.Global;
016import com.fs.starfarer.api.campaign.BaseCampaignEventListener;
017import com.fs.starfarer.api.campaign.BattleAPI;
018import com.fs.starfarer.api.campaign.CampaignFleetAPI;
019import com.fs.starfarer.api.campaign.CampaignTerrainAPI;
020import com.fs.starfarer.api.campaign.CargoAPI;
021import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType;
022import com.fs.starfarer.api.campaign.CargoStackAPI;
023import com.fs.starfarer.api.campaign.CustomCampaignEntityAPI;
024import com.fs.starfarer.api.campaign.FactionAPI;
025import com.fs.starfarer.api.campaign.FactionAPI.ShipPickMode;
026import com.fs.starfarer.api.campaign.FactionProductionAPI;
027import com.fs.starfarer.api.campaign.FactionProductionAPI.ItemInProductionAPI;
028import com.fs.starfarer.api.campaign.FactionProductionAPI.ProductionItemType;
029import com.fs.starfarer.api.campaign.FleetInflater;
030import com.fs.starfarer.api.campaign.JumpPointAPI.JumpDestination;
031import com.fs.starfarer.api.campaign.LocationAPI;
032import com.fs.starfarer.api.campaign.PlanetAPI;
033import com.fs.starfarer.api.campaign.PlayerMarketTransaction;
034import com.fs.starfarer.api.campaign.PlayerMarketTransaction.ShipSaleInfo;
035import com.fs.starfarer.api.campaign.SectorAPI;
036import com.fs.starfarer.api.campaign.SectorEntityToken;
037import com.fs.starfarer.api.campaign.SpecialItemData;
038import com.fs.starfarer.api.campaign.SpecialItemPlugin;
039import com.fs.starfarer.api.campaign.StarSystemAPI;
040import com.fs.starfarer.api.campaign.comm.CommMessageAPI.MessageClickAction;
041import com.fs.starfarer.api.campaign.econ.CommodityOnMarketAPI;
042import com.fs.starfarer.api.campaign.econ.Industry;
043import com.fs.starfarer.api.campaign.econ.MarketAPI;
044import com.fs.starfarer.api.campaign.econ.MarketAPI.SurveyLevel;
045import com.fs.starfarer.api.campaign.econ.MonthlyReport;
046import com.fs.starfarer.api.campaign.econ.MonthlyReport.FDNode;
047import com.fs.starfarer.api.campaign.econ.SubmarketAPI;
048import com.fs.starfarer.api.campaign.listeners.ListenerUtil;
049import com.fs.starfarer.api.campaign.rules.MemoryAPI;
050import com.fs.starfarer.api.characters.AdminData;
051import com.fs.starfarer.api.characters.OfficerDataAPI;
052import com.fs.starfarer.api.characters.PersonAPI;
053import com.fs.starfarer.api.characters.SkillsChangeRemoveExcessOPEffect;
054import com.fs.starfarer.api.characters.SkillsChangeRemoveVentsCapsEffect;
055import com.fs.starfarer.api.combat.ShipVariantAPI;
056import com.fs.starfarer.api.fleet.FleetMemberAPI;
057import com.fs.starfarer.api.impl.campaign.DerelictShipEntityPlugin.DerelictShipData;
058import com.fs.starfarer.api.impl.campaign.econ.impl.InstallableItemEffect;
059import com.fs.starfarer.api.impl.campaign.econ.impl.ItemEffectsRepo;
060import com.fs.starfarer.api.impl.campaign.events.BaseEventPlugin.MarketFilter;
061import com.fs.starfarer.api.impl.campaign.fleets.DefaultFleetInflaterParams;
062import com.fs.starfarer.api.impl.campaign.fleets.RouteManager;
063import com.fs.starfarer.api.impl.campaign.ids.Commodities;
064import com.fs.starfarer.api.impl.campaign.ids.Drops;
065import com.fs.starfarer.api.impl.campaign.ids.Entities;
066import com.fs.starfarer.api.impl.campaign.ids.Factions;
067import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
068import com.fs.starfarer.api.impl.campaign.ids.Stats;
069import com.fs.starfarer.api.impl.campaign.ids.Tags;
070import com.fs.starfarer.api.impl.campaign.intel.MessageIntel;
071import com.fs.starfarer.api.impl.campaign.intel.misc.ProductionReportIntel;
072import com.fs.starfarer.api.impl.campaign.intel.misc.ProductionReportIntel.ProductionData;
073import com.fs.starfarer.api.impl.campaign.procgen.SalvageEntityGenDataSpec.DropData;
074import com.fs.starfarer.api.impl.campaign.procgen.themes.BaseThemeGenerator;
075import com.fs.starfarer.api.impl.campaign.procgen.themes.SalvageSpecialAssigner;
076import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.ShipRecoverySpecial;
077import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.ShipRecoverySpecial.PerShipData;
078import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.ShipRecoverySpecial.ShipCondition;
079import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.ShipRecoverySpecial.ShipRecoverySpecialData;
080import com.fs.starfarer.api.impl.campaign.shared.SharedData;
081import com.fs.starfarer.api.impl.campaign.terrain.DebrisFieldTerrainPlugin;
082import com.fs.starfarer.api.impl.campaign.terrain.DebrisFieldTerrainPlugin.DebrisFieldParams;
083import com.fs.starfarer.api.impl.campaign.terrain.DebrisFieldTerrainPlugin.DebrisFieldSource;
084import com.fs.starfarer.api.impl.campaign.tutorial.TutorialMissionIntel;
085import com.fs.starfarer.api.loading.FighterWingSpecAPI;
086import com.fs.starfarer.api.loading.HullModSpecAPI;
087import com.fs.starfarer.api.loading.WeaponSpecAPI;
088import com.fs.starfarer.api.util.IntervalUtil;
089import com.fs.starfarer.api.util.Misc;
090import com.fs.starfarer.api.util.WeightedRandomPicker;
091
092public class CoreScript extends BaseCampaignEventListener implements EveryFrameScript {
093
094        public static Logger log = Global.getLogger(CoreScript.class);
095        
096        public static final String SHARED_DATA_KEY = "core_CEFSSharedDataKey";
097        
098        private SharedData shared;
099        
100        private IntervalUtil timer = new IntervalUtil(0.5f, 1.5f);
101        //private Set<String> marketsWithAssignedPatrolScripts = new HashSet<String>();
102        
103        public CoreScript() {
104                super(true);
105//              shared = new SharedData();
106                shared = SharedData.getData();
107        }
108
109        private boolean firstFrame = true;
110        public void advance(float amount) {
111                SectorAPI sector = Global.getSector();
112                playRepChangeSoundsIfNeeded();
113                
114                if (sector.isPaused()) {
115                        if (Global.getSettings().isDevMode()) {
116                                //RouteManager.getInstance().advance(amount);
117                                RouteManager.getInstance().advance(0f);
118                        }
119                        return;
120                }
121                
122                if (firstFrame) {
123                        firstFrame = false;
124                }
125                
126                float days = sector.getClock().convertToDays(amount);
127                shared.advance(amount);
128                
129                timer.advance(days);
130                if (timer.intervalElapsed()) {
131                }
132                
133                RouteManager.getInstance().advance(amount);
134                
135                Misc.computeCoreWorldsExtent();
136                
137                //updateSlipstreamVisibility(amount);
138        }
139        
140//      protected transient CampaignTerrainAPI currentStream = null;
141//      public void updateSlipstreamVisibility(float amount) {
142//              float sw = Global.getSettings().getFloat("sectorWidth");
143//              float sh = Global.getSettings().getFloat("sectorHeight");
144//              float minCellSize = 12000f;
145//              float cellSize = Math.max(minCellSize, sw * 0.05f);
146//              CollisionGridUtil grid = new CollisionGridUtil(-sw/2f, sw/2f, -sh/2f, sh/2f, cellSize);
147//              Set<String> seenSystems = new LinkedHashSet<String>();
148//              for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
149//                      if (market.isHidden()) continue;
150//                      if (market.getContainingLocation() == null) continue;
151//                      if (!market.getContainingLocation().isHyperspace()) {
152//                              String systemId = market.getContainingLocation().getId();
153//                              if (seenSystems.contains(systemId)) continue;
154//                              seenSystems.add(systemId);
155//                      }
156//                      //if (market.hasIndustry(Industries.SPACEPORT)) continue;
157//                      Industry spaceport = market.getIndustry(Industries.SPACEPORT);
158//                      if (spaceport == null || !spaceport.isFunctional()) continue;
159//                      
160//                      Vector2f loc = market.getLocationInHyperspace();
161//                      float size = 10000f;
162//                      if (!market.getContainingLocation().hasTag(Tags.THEME_CORE)) {
163//                              size = 10000f;
164//                      }
165//                      //size = 200000;
166////                    if (market.getName().equals("Tartessus")) {
167////                            System.out.println("ewfwefe");
168////                    }
169//                      CustomStreamRevealer revealer = new CustomStreamRevealer(loc, size);
170//                      grid.addObject(revealer, loc, size * 2f, size * 2f);
171//              }
172//              
173//              //System.out.println("BEGIN");
174//              float maxDist = 0f;
175//              List<CampaignTerrainAPI> terrainList = Global.getSector().getHyperspace().getTerrainCopy();
176//              boolean processNext = false;
177//              for (CampaignTerrainAPI terrain : terrainList) {
178//                      if (terrain.getPlugin() instanceof SlipstreamTerrainPlugin2) {
179//                              boolean process = false;
180//                              if (currentStream == null || processNext) {
181//                                      process = true;
182//                              } else if (currentStream == terrain) {
183//                                      processNext = true;
184//                              }
185//                              
186//                              if (!process) continue;
187//                              
188//                              currentStream = terrain;
189//                              if (terrainList.indexOf(terrain) == terrainList.size() - 1) {
190//                                      currentStream = null;
191//                              }
192//                              //System.out.println("Processing: " + terrain.getId());
193//                              SlipstreamTerrainPlugin2 stream = (SlipstreamTerrainPlugin2) terrain.getPlugin();
194//                              for (SlipstreamSegment curr : stream.getSegments()) {
195//                                      if (curr.discovered) continue;
196//                                      Iterator<Object> iter = grid.getCheckIterator(curr.loc, curr.width / 2f, curr.width / 2f);
197//                                      //Iterator<Object> iter = grid.getCheckIterator(curr.loc, 100f, 100f);
198//                                      while (iter.hasNext()) {
199//                                              Object obj = iter.next();
200//                                              if (obj instanceof CustomStreamRevealer) {
201//                                                      CustomStreamRevealer rev = (CustomStreamRevealer) obj;
202//                                                      Vector2f loc = rev.loc;
203//                                                      float radius = rev.radius;
204//                                                      
205//                                                      float dist = Misc.getDistance(loc, curr.loc);
206//                                                      if (dist > maxDist) {
207//                                                              maxDist = dist;
208////                                                            if (dist >= 32500) {
209////                                                                    System.out.println("Rev loc: " + rev.loc);
210////                                                                    //grid.getCheckIterator(curr.loc, 100f, 100f);
211////                                                            }
212//                                                      }
213//                                                      if (dist < radius) {
214//                                                              curr.discovered = true;
215//                                                              break;
216//                                                      }
217//                                              }
218//                                      }
219//                              }
220//                              break;
221//                      }
222//              }
223//              //System.out.println("Max dist: " + maxDist);
224//      }
225        
226        
227        private void playRepChangeSoundsIfNeeded() {
228                if (deltaFaction != null) {
229                        if (highestDelta > 0) {
230                                Global.getSoundPlayer().playUISound("ui_rep_raise", 1, 1);
231                        } else if (highestDelta < 0) {
232                                Global.getSoundPlayer().playUISound("ui_rep_drop", 1, 1);
233                        }
234                }
235                
236                highestDelta = 0f;
237                deltaFaction = null;
238        }
239
240        
241        private float highestDelta = 0f;
242        private String deltaFaction = null;
243        @Override
244        public void reportPlayerReputationChange(String faction, float delta) {
245                super.reportPlayerReputationChange(faction, delta);
246                if (Math.abs(delta) > Math.abs(highestDelta)) {
247                        highestDelta = delta;
248                        deltaFaction = faction;
249                }
250        }
251        
252
253        @Override
254        public void reportPlayerReputationChange(PersonAPI person, float delta) {
255                super.reportPlayerReputationChange(person, delta);
256                if (Math.abs(delta) > Math.abs(highestDelta)) {
257                        highestDelta = delta;
258                        deltaFaction = person.getFaction().getId();
259                }
260        }
261
262
263        public boolean isDone() {
264                return false;
265        }
266
267        public boolean runWhilePaused() {
268                return true;
269        }
270
271
272        @Override
273        public void reportPlayerMarketTransaction(PlayerMarketTransaction transaction) {
274                super.reportPlayerMarketTransaction(transaction);
275                
276                for (ShipSaleInfo info : transaction.getShipsBought()) {
277                        FleetMemberAPI member = info.getMember();
278                        if (!member.getVariant().hasTag(Tags.VARIANT_ALLOW_EXCESS_OP_ETC)){ 
279                                SkillsChangeRemoveExcessOPEffect.clampOP(member, Global.getSector().getPlayerStats());
280                                SkillsChangeRemoveVentsCapsEffect.clampNumVentsAndCaps(member, Global.getSector().getPlayerStats());
281                        }
282                }
283
284                
285                
286                SubmarketAPI submarket = transaction.getSubmarket();
287                MarketAPI market = transaction.getMarket();
288                
289                if (!market.isPlayerOwned() && submarket.getPlugin().isParticipatesInEconomy() &&
290                                !submarket.getPlugin().isBlackMarket() && submarket.getFaction() == market.getFaction()) {
291                        CargoAPI cargo = transaction.getSubmarket().getCargo();
292                        boolean didAnything = false;
293                        OUTER: for (CargoStackAPI stack : transaction.getSold().getStacksCopy()) {
294                                SpecialItemPlugin plugin = stack.getPlugin();
295                                if (plugin == null) continue;
296                                
297                                SpecialItemData data = stack.getSpecialDataIfSpecial();
298                                
299                                InstallableItemEffect effect = ItemEffectsRepo.ITEM_EFFECTS.get(data.getId());
300                                for (Industry ind : market.getIndustries()) {
301                                        if (ind.wantsToUseSpecialItem(data)) {
302                                                if (effect != null) {
303                                                        List<String> unmet = effect.getUnmetRequirements(ind);
304                                                        if (unmet != null && !unmet.isEmpty()) {
305                                                                continue;
306                                                        }
307                                                }
308                                                
309                                                if (ind.getSpecialItem() != null) { // upgrade, put item into cargo
310                                                        cargo.addItems(CargoItemType.SPECIAL, ind.getSpecialItem(), 1);
311                                                }
312                                                cargo.removeItems(CargoItemType.SPECIAL, data, 1);
313                                                ind.setSpecialItem(data);
314                                                didAnything = true;
315                                                continue OUTER;
316                                        }
317                                }
318                        }
319                        
320                        if (didAnything) {
321                                cargo.sort();
322                        }
323                        
324                        // not sure about doing this for major factions:
325                        // - it can mess with their flavor, unintentionally if the player just sells blueprints
326                        // - it can be a way to make their fleets weaker, by selling loads of blueprints for poor ships
327                        //BlackMarketPlugin.delayedLearnBlueprintsFromTransaction(submarket.getFaction(), cargo, transaction);
328                }
329                
330                //SharedData.getData().getPlayerActivityTracker().updateLastVisit(transaction.getMarket());
331
332                // moved below code to BaseSubmarketPlugin
333//              SubmarketAPI sub = transaction.getSubmarket();
334//              if (sub.getPlugin().isParticipatesInEconomy()) {
335//                      SharedData.getData().getPlayerActivityTracker().getPlayerTradeData(sub).addTransaction(transaction);
336//              }
337        }
338
339        @Override
340        public void reportPlayerOpenedMarket(MarketAPI market) {
341                super.reportPlayerOpenedMarket(market);
342                SharedData.getData().getPlayerActivityTracker().updateLastVisit(market);
343                
344                //new TempImmigrationModifier(market, 100, 200f, "Testing immigration mod");
345        }
346
347        
348
349        @Override
350        public void reportBattleOccurred(CampaignFleetAPI primaryWinner, BattleAPI battle) {
351                
352                generateOrAddToDebrisFieldFromBattle(primaryWinner, battle);
353                
354                if (!battle.isPlayerInvolved()) return;
355
356                
357                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
358                if (!playerFleet.isValidPlayerFleet()) {
359                        float fp = 0;
360                        float crew = 0;
361                        for (FleetMemberAPI member : Misc.getSnapshotMembersLost(playerFleet)) {
362                                fp += member.getFleetPointCost();
363                                crew = member.getMinCrew();
364                        }
365                        shared.setPlayerPreLosingBattleFP(fp);
366                        shared.setPlayerPreLosingBattleCrew(crew);
367                        shared.setPlayerLosingBattleTimestamp(Global.getSector().getClock().getTimestamp());
368                }
369                
370                
371                for (final CampaignFleetAPI otherFleet : battle.getNonPlayerSideSnapshot()) {
372                        if (otherFleet.hasScriptOfClass(TOffAlarm.class)) continue;
373                        MemoryAPI memory = otherFleet.getMemoryWithoutUpdate();
374                        //if (!playerFleet.isTransponderOn()) {
375                        //if (!memory.getBoolean(MemFlags.MEMORY_KEY_LOW_REP_IMPACT)) {
376                                Misc.setFlagWithReason(memory, MemFlags.MEMORY_KEY_MAKE_HOSTILE_WHILE_TOFF, "battle", true, 7f + (float) Math.random() * 7f);
377                        //}
378                        //}
379                                
380                        if (!otherFleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_LOW_REP_IMPACT) ||
381                                        otherFleet.getMemoryWithoutUpdate().getBoolean(MemFlags.SPREAD_TOFF_HOSTILITY_IF_LOW_IMPACT)) {
382                                otherFleet.addScript(new TOffAlarm(otherFleet));
383                        }
384                        
385                        
386                        float fpLost = Misc.getSnapshotFPLost(otherFleet);
387        
388                        List<MarketAPI> markets = Misc.findNearbyLocalMarkets(otherFleet,
389                                        Global.getSettings().getFloat("sensorRangeMax") + 500f,
390                                new MarketFilter() {
391                                        public boolean acceptMarket(MarketAPI market) {
392                                                //return market.getFaction().isAtWorst(otherFleet.getFaction(), RepLevel.COOPERATIVE);
393                                                return market.getFaction() != null && market.getFaction() == otherFleet.getFaction();
394                                        }
395                                });
396                        
397                        for (MarketAPI market : markets) {
398                                MemoryAPI mem = market.getMemoryWithoutUpdate();
399                                float expire = fpLost;
400                                if (mem.contains(MemFlags.MEMORY_KEY_PLAYER_HOSTILE_ACTIVITY_NEAR_MARKET)) {
401                                        expire += mem.getExpire(MemFlags.MEMORY_KEY_PLAYER_HOSTILE_ACTIVITY_NEAR_MARKET); 
402                                }
403                                if (expire > 180) expire = 180;
404                                if (expire > 0) {
405                                        mem.set(MemFlags.MEMORY_KEY_PLAYER_HOSTILE_ACTIVITY_NEAR_MARKET, true, expire);
406                                }
407                        }
408                }
409                
410                Misc.getSimulatorPlugin().reportPlayerBattleOccurred(primaryWinner, battle);
411        }
412        
413
414        @Override
415        public void reportFleetJumped(CampaignFleetAPI fleet, SectorEntityToken from, JumpDestination to) {
416                super.reportFleetJumped(fleet, from, to);
417                
418                if (!fleet.isPlayerFleet()) return;
419
420                FactionAPI faction = Global.getSector().getPlayerFaction();
421                Color color = faction.getBaseUIColor();
422                Color dark = faction.getDarkUIColor();
423                Color grid = faction.getGridUIColor();
424                Color bright = faction.getBrightUIColor();
425                
426                if (fleet.getContainingLocation() instanceof StarSystemAPI) {
427                        StarSystemAPI system = (StarSystemAPI) fleet.getContainingLocation();
428                        markSystemAsEntered(system, true);
429                }
430        }
431        
432        public static void markSystemAsEntered(StarSystemAPI system, boolean withMessages) {
433                system.setEnteredByPlayer(true);
434                
435                for (PlanetAPI planet : system.getPlanets()) {
436                        if (planet.isStar()) continue;
437                        
438                        MarketAPI market = planet.getMarket();
439                        if (market == null) continue;
440                        if (market.getSurveyLevel() == SurveyLevel.NONE) {
441                                market.setSurveyLevel(SurveyLevel.SEEN);
442                                String type = planet.getSpec().getName();
443                                if (!planet.isGasGiant()) type += " World";
444//                                      Global.getSector().getCampaignUI().addMessage(
445//                                                      "New planet data: " + planet.getName() + ", " + type,
446//                                                      color);
447                                
448                                
449                                if (withMessages) {
450//                                      CommMessageAPI message = Global.getFactory().createMessage();
451//                                      message.setSubject("New planet data: " + planet.getName() + ", " + type);
452//                                      message.setAction(MessageClickAction.INTEL_TAB);
453//                                      message.setCustomData(planet);
454//                                      message.setAddToIntelTab(false);
455//                                      message.setSmallIcon(Global.getSettings().getSpriteName("intel_categories", "star_systems"));
456//                                      Global.getSector().getCampaignUI().addMessage(message);
457                                        
458                                        MessageIntel intel = new MessageIntel("New planet data: " + planet.getName() + ", " + type,
459                                                        Misc.getBasePlayerColor());//, new String[] {"" + points}, Misc.getHighlightColor());
460                                        intel.setIcon(Global.getSettings().getSpriteName("intel", "new_planet_info"));
461                                        Global.getSector().getCampaignUI().addMessage(intel, MessageClickAction.INTEL_TAB, planet);
462                                }
463                        }
464                }
465        }
466
467        
468        public static void addMiscToDropData(DropData data, FleetMemberAPI member,
469                                                                                  boolean weapons, boolean mods, boolean fighters) {
470                ShipVariantAPI variant = member.getVariant();
471                
472                if (weapons) {
473                        float p = Global.getSettings().getFloat("salvageWeaponProb");
474                        for (String slotId : variant.getNonBuiltInWeaponSlots()) {
475                                String weaponId = variant.getWeaponId(slotId);
476                                WeaponSpecAPI spec = Global.getSettings().getWeaponSpec(weaponId);
477                                if (spec.hasTag(Tags.NO_DROP)) continue;
478                                data.addWeapon(weaponId, 1f * p);
479                        }
480                }
481                
482                if (mods) {
483                        float p = Global.getSettings().getFloat("salvageHullmodProb");
484                        for (String id : member.getVariant().getHullMods()) {
485                                HullModSpecAPI spec = Global.getSettings().getHullModSpec(id);
486                                if (spec.isHidden() || spec.isHiddenEverywhere()) continue;
487                                if (spec.hasTag(Tags.HULLMOD_NO_DROP)) continue;
488                                data.addHullMod(id, 1f * p);
489                        }
490                }
491                
492                if (fighters) {
493                        float p = Global.getSettings().getFloat("salvageWingProb");
494                        
495                        for (String id : member.getVariant().getFittedWings()) {
496                                FighterWingSpecAPI spec = Global.getSettings().getFighterWingSpec(id);
497                                if (spec.hasTag(Tags.WING_NO_DROP)) continue;
498                                data.addFighterChip(id, 1f * p);
499                        }
500                }
501                
502                data.valueMult = Global.getSettings().getFloat("salvageDebrisFieldFraction");
503        }
504
505        public static Set<String> getCargoCommodities(CargoAPI cargo) {
506                Set<String> result = new HashSet<String>();
507                for (CargoStackAPI stack : cargo.getStacksCopy()) {
508                        if (stack.isCommodityStack()) {
509                                result.add(stack.getCommodityId());
510                        }
511                }
512                return result;
513        }
514        
515        public static void generateOrAddToDebrisFieldFromBattle(CampaignFleetAPI primaryWinner, BattleAPI battle) {
516                
517                if (primaryWinner == null) return;
518                LocationAPI location = primaryWinner.getContainingLocation();
519                if (location == null) return;
520                
521                //if (location.isHyperspace()) return;
522                
523                boolean allowDebris = !location.isHyperspace(); 
524                
525                
526                boolean playerInvolved = battle.isPlayerInvolved();
527                
528                DropData misc = new DropData();
529                int miscChances = 0;
530                
531                List<DropData> cargoList = new ArrayList<DropData>();
532                
533                WeightedRandomPicker<FleetMemberAPI> recoverySpecialChoices = new WeightedRandomPicker<FleetMemberAPI>();
534                
535                Vector2f com = new Vector2f();
536                float count = 0f;
537                float fpDestroyed = 0;
538                for (CampaignFleetAPI fleet : battle.getSnapshotSideOne()) {
539                        count++;
540                        com.x += fleet.getLocation().x;
541                        com.y += fleet.getLocation().y;
542                        
543                        float fpForThisFleet = 0;
544                        for (FleetMemberAPI loss : Misc.getSnapshotMembersLost(fleet)) {
545                                //if (loss.getVariant().isStockVariant()) {
546                                if (!loss.getVariant().hasTag(Tags.SHIP_RECOVERABLE)) { // was not recoverable by player
547                                        if (!loss.isStation() && !fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_NO_SHIP_RECOVERY)) {
548                                                recoverySpecialChoices.add(loss);
549                                        }
550                                }
551                                fpDestroyed += loss.getFleetPointCost();
552                                fpForThisFleet += loss.getFleetPointCost();
553                                if (allowDebris && !fleet.isPlayerFleet()) {
554                                        addMiscToDropData(misc, loss, true, true, true);
555                                        miscChances++;
556                                }
557                        }
558                        if (allowDebris && !fleet.isPlayerFleet()) {
559                                DropData cargo = new DropData();
560                                float cargoValue = fpForThisFleet * Global.getSettings().getFloat("salvageValuePerFP");
561                                cargoValue *= Global.getSettings().getFloat("salvageDebrisFieldFraction");
562                                if (cargoValue >= 1) {
563                                        for (String cid : getCargoCommodities(fleet.getCargo())) {
564                                                cargo.addCommodity(cid, 1f);
565                                        }
566                                        cargo.value = (int) cargoValue;
567                                        cargo.chances = 1;
568                                        cargoList.add(cargo);
569                                }
570                        }
571                }
572                for (CampaignFleetAPI fleet : battle.getSnapshotSideTwo()) {
573                        count++;
574                        com.x += fleet.getLocation().x;
575                        com.y += fleet.getLocation().y;
576                        
577                        float fpForThisFleet = 0;
578                        for (FleetMemberAPI loss : Misc.getSnapshotMembersLost(fleet)) {
579                                //if (loss.getVariant().isStockVariant()) {
580                                if (!loss.getVariant().hasTag(Tags.SHIP_RECOVERABLE)) { // was not recoverable by player
581                                        if (!loss.isStation() && !fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_NO_SHIP_RECOVERY)) {
582                                                recoverySpecialChoices.add(loss);
583                                        }
584                                } else {
585                                        loss.getVariant().removeTag(Tags.SHIP_RECOVERABLE);
586                                }
587                                
588                                fpDestroyed += loss.getFleetPointCost();
589                                fpForThisFleet += loss.getFleetPointCost();
590                                if (allowDebris && !fleet.isPlayerFleet()) {
591                                        addMiscToDropData(misc, loss, true, true, true);
592                                        miscChances++;
593                                }
594                        }
595                        if (allowDebris && !fleet.isPlayerFleet()) {
596                                DropData cargo = new DropData();
597                                float cargoValue = fpForThisFleet * Global.getSettings().getFloat("salvageValuePerFP");
598                                cargoValue *= Global.getSettings().getFloat("salvageDebrisFieldFraction");
599                                if (cargoValue >= 1) {
600                                        for (String cid : getCargoCommodities(fleet.getCargo())) {
601                                                cargo.addCommodity(cid, 1f);
602                                        }
603                                        cargo.value = (int) cargoValue;
604                                        cargo.chances = 1;
605                                        cargoList.add(cargo);
606                                }
607                        }
608                }
609                //Global.getSector().getPlayerFleet().getLocation()
610                if (count <= 0) return;
611                
612                com.scale(1f / count);
613                
614                // spawn some derelict ships, maybe. do this here, regardless of whether the value is enough to
615                // warrant a debris field
616                float numShips = recoverySpecialChoices.getItems().size();
617                float chanceDerelict = 1f - 10f / (numShips + 10f);
618                //chanceNothing = 0f;
619                //Vector2f com = battle.computeCenterOfMass();
620                
621                // in a battle that involves the player, recoverable ships are non-stock variants
622                // (due to being prepared for recovery; dmods etc) and so don't show up as possible recovery choices here
623                // which is good! since 1) they could've been actually recovered, and 2) they already contributed to player salvage
624                // replaced this with Tags.SHIP_RECOVERABLE tag in recoverable variant for cleanness
625                int max = 3;
626                if (playerInvolved) {
627                        max = 2;
628                        chanceDerelict *= 0.25f;
629                }
630                for (int i = 0; i < max && !recoverySpecialChoices.isEmpty(); i++) {
631                        boolean spawnShip = Math.random() < chanceDerelict;
632                        if (spawnShip) {
633                                FleetMemberAPI member = recoverySpecialChoices.pickAndRemove();
634                                String variantId = member.getVariant().getHullVariantId();
635                                if (!member.getVariant().isStockVariant()) variantId = member.getVariant().getOriginalVariant();
636                                if (variantId == null) continue;
637                                DerelictShipData params = new DerelictShipData(new PerShipData(variantId,
638                                                                                DerelictShipEntityPlugin.pickBadCondition(null), 0f), false);
639                                params.durationDays = DerelictShipEntityPlugin.getBaseDuration(member.getHullSpec().getHullSize());
640                                CustomCampaignEntityAPI entity = (CustomCampaignEntityAPI) BaseThemeGenerator.addSalvageEntity(
641                                                                                                 primaryWinner.getContainingLocation(),
642                                                                                                 Entities.WRECK, Factions.NEUTRAL, params);
643                                entity.addTag(Tags.EXPIRES);
644                                SalvageSpecialAssigner.assignSpecialForBattleWreck(entity);
645                                
646//                              entity.getLocation().x = com.x + (50f - (float) Math.random() * 100f);
647//                              entity.getLocation().y = com.y + (50f - (float) Math.random() * 100f);
648                                
649                                float angle = (float) Math.random() * 360f;
650                                float speed = 10f + 10f * (float) Math.random();
651                                Vector2f vel = Misc.getUnitVectorAtDegreeAngle(angle);
652                                vel.scale(speed);
653                                entity.getVelocity().set(vel);
654                                
655                                entity.getLocation().x = com.x + vel.x * 3f;
656                                entity.getLocation().y = com.y + vel.y * 3f;
657                        }
658                }
659                
660                
661                
662                float salvageValue = fpDestroyed * Global.getSettings().getFloat("salvageValuePerFP");
663                if (Misc.isEasy()) {
664                        salvageValue *= Global.getSettings().getFloat("easySalvageMult");
665                }
666                
667                
668                salvageValue *= Global.getSettings().getFloat("salvageDebrisFieldFraction");
669                float salvageXP = salvageValue * 0.1f;
670                
671                float minForField = Global.getSettings().getFloat("minSalvageValueForDebrisField");
672                //if (playerInvolved) minForField *= 6f;
673                if (playerInvolved) minForField = 2500f + (float) Math.random() * 1000f;
674                
675                if (salvageValue < minForField || !allowDebris) return;
676                
677                
678                CampaignTerrainAPI debris = null;
679                for (CampaignTerrainAPI curr : primaryWinner.getContainingLocation().getTerrainCopy()) {
680                        if (curr.getPlugin() instanceof DebrisFieldTerrainPlugin) {
681                                DebrisFieldTerrainPlugin plugin = (DebrisFieldTerrainPlugin) curr.getPlugin();
682                                if (plugin.params.source == DebrisFieldSource.BATTLE && 
683                                                plugin.params.density >= 1f &&
684                                                plugin.containsPoint(com, 100f)) {
685                                        debris = curr;
686                                        break;
687                                }
688                        }
689                }
690                
691                if (debris == null) {
692                        DebrisFieldParams params = new DebrisFieldParams(
693                                        200f, // field radius - should not go above 1000 for performance reasons
694                                        -1f, // density, visual - affects number of debris pieces
695                                        1f, // duration in days 
696                                        1f); // days the field will keep generating glowing pieces
697                        params.source = DebrisFieldSource.BATTLE;
698                        params.baseSalvageXP = (long) salvageXP; // base XP for scavenging in field
699                        
700                        debris = (CampaignTerrainAPI) Misc.addDebrisField(location, params, null);
701                        
702                        // makes the debris field always visible on map/sensors and not give any xp or notification on being discovered
703                        //debris.setSensorProfile(null);
704                        //debris.setDiscoverable(null);
705                        
706                        // makes it discoverable and give 200 xp on being found
707                        // sets the range at which it can be detected (as a sensor contact) to 2000 units
708                        //debris.setDiscoverable(true);
709                        //debris.setDiscoveryXP(200f);
710                        //debris.setSensorProfile(1f);
711                        //debris.getDetectedRangeMod().modifyFlat("gen", 2000);
712                        
713                        debris.setDiscoverable(null);
714                        debris.setDiscoveryXP(null);
715                        //debris.setSensorProfile(1f);
716                        //debris.getDetectedRangeMod().modifyFlat("gen", 1000);
717                        
718                        debris.getLocation().set(com);
719                        
720                        debris.getDropValue().clear();
721                        debris.getDropRandom().clear();
722                }
723                
724                
725                DebrisFieldTerrainPlugin plugin = (DebrisFieldTerrainPlugin) debris.getPlugin();
726                DropData basicDrop = null;
727                for (DropData data : debris.getDropValue()) {
728                        if (Drops.BASIC.equals(data.group)) {
729                                basicDrop = data;
730                                break;
731                        }
732                }
733                
734                // since we're only adding to fields with density 1 (i.e. ones the player hasn't salvaged)
735                // no need to worry about how density would affect the salvage value of what we're adding
736                if (basicDrop == null) {
737                        basicDrop = new DropData();
738                        basicDrop.group = Drops.BASIC;
739                        debris.addDropValue(basicDrop);
740                }
741                basicDrop.value += salvageValue;
742                
743                if (misc.getCustom() != null) {
744                        misc.chances = miscChances;
745                        
746                        float total = misc.getCustom().getTotal();
747                        if (total > 0) {
748                                misc.addNothing(Math.max(1f, total));
749                        }
750                        debris.addDropRandom(misc);
751                        //misc.getCustom().print("MISC DROP");
752                }
753                
754                for (DropData cargo : cargoList) {
755                        debris.addDropRandom(cargo);
756                }
757                
758                
759                //if (!battle.isPlayerInvolved()) {
760                        ShipRecoverySpecialData data = ShipRecoverySpecial.getSpecialData(debris, null, true, false);
761                        if (data != null && data.ships.size() < 3) {
762                                float items = recoverySpecialChoices.getTotal();
763                                float total = items + 25f;
764                                for (int i = 0; i < 3; i++) {
765                                        if ((float) Math.random() * total < items) {
766                                                FleetMemberAPI pick = recoverySpecialChoices.pick();
767                                                if (pick != null) {
768                                                        String variantId = pick.getVariant().getHullVariantId();
769                                                        if (!pick.getVariant().isStockVariant()) variantId = pick.getVariant().getOriginalVariant();
770                                                        data.addShip(variantId, ShipCondition.WRECKED, 0f);
771                                                }
772                                        }
773                                }
774                        }
775                //}
776                
777//              basicDrop = new DropData();
778//              basicDrop.group = "misc_test";
779//              basicDrop.value = 100000;
780//              existing.addDropValue(basicDrop);
781                
782                // resize and adjust duration here
783                
784                float radius = 100f + (float) Math.min(900, Math.sqrt(basicDrop.value));
785                float durationExtra = (float) Math.sqrt(salvageValue) * 0.1f;
786                
787                float minDays = DebrisFieldTerrainPlugin.DISSIPATE_DAYS + 1f;
788                if (durationExtra < minDays) durationExtra = minDays;
789                
790                float time = durationExtra + plugin.params.lastsDays;
791                if (time > 30f) time = 30f;
792                
793                plugin.params.lastsDays = time;
794                plugin.params.glowsDays = time;
795                
796                plugin.params.bandWidthInEngine = radius;
797                plugin.params.middleRadius = plugin.params.bandWidthInEngine / 2f;
798                
799                float range = DebrisFieldTerrainPlugin.computeDetectionRange(plugin.params.bandWidthInEngine);
800                debris.getDetectedRangeMod().modifyFlat("gen", range);
801        }
802
803
804        
805        
806        @Override
807        public void reportPlayerDumpedCargo(CargoAPI cargo) {
808                super.reportPlayerDumpedCargo(cargo);
809                
810                
811                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
812                
813                CustomCampaignEntityAPI pods = Misc.addCargoPods(playerFleet.getContainingLocation(), playerFleet.getLocation());
814                pods.getCargo().addAll(cargo);
815                
816                CargoPodsResponse script = new CargoPodsResponse(pods);
817                pods.getContainingLocation().addScript(script);
818                ListenerUtil.reportPlayerLeftCargoPods(pods);
819//              pods.getMemoryWithoutUpdate().set(CargoPods.LOCKED, true);
820//              pods.getMemoryWithoutUpdate().set(CargoPods.CAN_UNLOCK, true);
821                //pods.getMemoryWithoutUpdate().set(CargoPods.TRAPPED, true);
822                
823                //pods.getDetectedRangeMod().modifyFlat("gen", 1000);
824        }
825        
826        @Override
827        public void reportPlayerDidNotTakeCargo(CargoAPI cargo) {
828                super.reportPlayerDumpedCargo(cargo);
829                
830                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
831                
832                CustomCampaignEntityAPI pods = Misc.addCargoPods(playerFleet.getContainingLocation(), playerFleet.getLocation());
833                pods.getCargo().addAll(cargo);
834                
835                ListenerUtil.reportPlayerLeftCargoPods(pods);
836        }
837
838        protected Random prodRandom = new Random();
839        public void doCustomProduction() {
840                FactionAPI pf = Global.getSector().getPlayerFaction();
841                FactionProductionAPI prod = pf.getProduction();
842                
843                MarketAPI gatheringPoint = prod.getGatheringPoint();
844                if (gatheringPoint == null) return;
845
846                //CargoAPI local = Misc.getLocalResourcesCargo(gatheringPoint);
847                CargoAPI local = Misc.getStorageCargo(gatheringPoint);
848                if (local == null) return;
849                
850                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
851                MonthlyReport report = SharedData.getData().getCurrentReport();
852                report.computeTotals();
853                
854                // limit production capacity by available credits - the net coming in this month plus what the player has
855                int total = (int) (report.getRoot().totalIncome - report.getRoot().totalUpkeep);
856                int credits = (int) playerFleet.getCargo().getCredits().get();
857                credits += total;
858                if (credits < 0) credits = 0;
859                
860                int capacity = prod.getMonthlyProductionCapacity();
861                capacity = Math.min(capacity, credits);
862                //capacity = 1000000;
863                
864                int remainingValue = capacity + prod.getAccruedProduction();
865                //if (remainingValue <= 0) return;
866                
867                
868                
869                //Random random = new Random();
870                if (prodRandom == null) prodRandom = new Random();
871                Random random = prodRandom;
872                
873                WeightedRandomPicker<ItemInProductionAPI> picker = new WeightedRandomPicker<ItemInProductionAPI>(random);
874                for (ItemInProductionAPI item : prod.getCurrent()) {
875                        if (item.getBuildDelay() > 0 && !Global.getSettings().isDevMode()) continue;
876                        picker.add(item, item.getQuantity());
877                }
878                int accrued = 0;
879                
880                boolean wantedToDoProduction = !picker.isEmpty();
881                boolean unableToDoProduction = capacity <= 0;
882
883                ProductionData data = new ProductionData();
884                //CargoAPI cargo = Global.getFactory().createCargo(true);
885                //cargo.initMothballedShips(Factions.PLAYER);
886                CargoAPI cargo = data.getCargo("Heavy Industry - Custom Production");
887                
888                float quality = -1f;
889                for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
890                        if (!market.isPlayerOwned()) continue;
891                        //quality = Math.max(quality, ShipQuality.getShipQuality(market, Factions.PLAYER));
892                        float currQuality = market.getStats().getDynamic().getMod(Stats.PRODUCTION_QUALITY_MOD).computeEffective(0f);
893                        currQuality += market.getStats().getDynamic().getMod(Stats.FLEET_QUALITY_MOD).computeEffective(0f);
894                        quality = Math.max(quality, currQuality);
895                }
896                quality -= Global.getSector().getFaction(Factions.PLAYER).getDoctrine().getShipQualityContribution();
897                quality += 4f * Global.getSettings().getFloat("doctrineFleetQualityPerPoint");
898                
899                CampaignFleetAPI ships = Global.getFactory().createEmptyFleet(Factions.PLAYER, "temp", true);
900                ships.setCommander(Global.getSector().getPlayerPerson());
901                DefaultFleetInflaterParams p = new DefaultFleetInflaterParams();
902                p.quality = quality;
903                p.mode = ShipPickMode.PRIORITY_THEN_ALL;
904                p.persistent = false;
905                p.seed = random.nextLong();
906                p.timestamp = null;
907                
908                FleetInflater inflater = Misc.getInflater(ships, p);
909                ships.setInflater(inflater);
910                
911                int totalCost = 0;
912                while (remainingValue > 0 && !picker.isEmpty()) {
913                        ItemInProductionAPI pick = picker.pick();
914                        int baseCost = pick.getBaseCost();
915                        
916                        int count = Math.min(pick.getQuantity(), remainingValue / Math.max(1, baseCost));
917                        if (count > 0) {
918                                count = random.nextInt(count) + 1;
919                        }
920                        if (count <= 0) {
921                                accrued = remainingValue;
922                                remainingValue = 0;
923                        } else {
924                                int currCost = count * baseCost;
925                                totalCost += currCost;
926                                remainingValue -= currCost;
927                                
928                                if (pick.getType() == ProductionItemType.SHIP) {
929                                        List<String> variants = Global.getSettings().getHullIdToVariantListMap().get(pick.getSpecId());
930                                        if (variants.isEmpty()) {
931                                                variants.add(pick.getSpecId() + "_Hull");
932                                                continue;
933                                        }
934                                        
935                                        int index = random.nextInt(variants.size());
936                                        //cargo.addMothballedShip(FleetMemberType.SHIP, variants.get(index), null);
937                                        for (int i = 0; i < count; i++) {
938                                                ships.getFleetData().addFleetMember(variants.get(index));
939                                        }
940                                } else if (pick.getType() == ProductionItemType.FIGHTER) {
941                                        cargo.addFighters(pick.getSpecId(), count);
942                                } else if (pick.getType() == ProductionItemType.WEAPON) {
943                                        cargo.addWeapons(pick.getSpecId(), count);
944                                }
945                                
946                                prod.removeItem(pick.getType(), pick.getSpecId(), count);
947                                if (pick.getQuantity() <= 0) {
948                                        picker.remove(pick);
949                                }
950                        }
951                }
952                
953                int weaponCost = 0;
954                ships.inflateIfNeeded();
955                for (FleetMemberAPI member : ships.getFleetData().getMembersListCopy()) {
956                        cargo.getMothballedShips().addFleetMember(member);
957                        for (String wingId : member.getVariant().getNonBuiltInWings()) {
958                                FighterWingSpecAPI spec = Global.getSettings().getFighterWingSpec(wingId);
959                                weaponCost += spec.getBaseValue();
960                        }
961                        for (String slotId : member.getVariant().getNonBuiltInWeaponSlots()) {
962                                WeaponSpecAPI spec = member.getVariant().getWeaponSpec(slotId);
963                                weaponCost += spec.getBaseValue();
964                        }
965                }
966                if (!DebugFlags.WEAPONS_HAVE_COST) {
967                        weaponCost = 0;
968                }
969                
970                // add some supplies, fuel, and crew
971                int addedValue = (int) (totalCost * Global.getSettings().getFloat("productionSuppliesBonusFraction"));
972                int sCost = (int) Global.getSettings().getCommoditySpec(Commodities.SUPPLIES).getBasePrice();
973                int fCost = (int) Global.getSettings().getCommoditySpec(Commodities.FUEL).getBasePrice();
974                int cCost = (int) Global.getSettings().getCommoditySpec(Commodities.CREW).getBasePrice();
975                
976                int supplies = (int) (addedValue * (0.5f * (0.5f + random.nextFloat() * 0.5f))) / sCost;
977                int fuel = (int) (addedValue * (0.3f * (0.5f + random.nextFloat() * 0.5f))) / fCost;
978                int crew = (addedValue - sCost * supplies - fCost * fuel) / cCost;
979                
980                supplies = supplies / 10 * 10;
981                fuel = fuel / 10 * 10;
982                crew = crew / 10 * 10;
983                
984                cargo.addSupplies(supplies);
985                cargo.addFuel(fuel);
986                cargo.addCrew(crew);
987                
988                //accrued += remainingValue; // don't do that, only want to accrue when trying to produce something
989
990                totalCost -= prod.getAccruedProduction();
991                totalCost += accrued;
992                if (totalCost < 0) totalCost = 0;
993                prod.setAccruedProduction(accrued);
994                
995                for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
996                        if (!market.isPlayerOwned()) continue;
997                
998                        for (Industry ind : market.getIndustries()) {
999                                Random curr = Misc.getRandom(random.nextLong(), 11);
1000                                CargoAPI added = ind.generateCargoForGatheringPoint(curr);
1001                                if (added != null && (!added.isEmpty() || 
1002                                                (added.getMothballedShips() != null && !added.getMothballedShips().getMembersListCopy().isEmpty()))) {
1003                                        String title = ind.getCargoTitleForGatheringPoint();
1004                                        data.getCargo(title).addAll(added, true);
1005                                }
1006                        }
1007                }
1008                
1009                
1010                // done with production
1011                
1012                
1013                
1014                if (!data.isEmpty() || totalCost > 0 || (wantedToDoProduction && unableToDoProduction)) {
1015                        if (totalCost > 0) {
1016                                //MonthlyReport report = SharedData.getData().getCurrentReport();
1017                                
1018                                FDNode marketsNode = report.getNode(MonthlyReport.OUTPOSTS);
1019                                if (marketsNode.name == null) {
1020                                        marketsNode.name = "Colonies";
1021                                        marketsNode.custom = MonthlyReport.OUTPOSTS;
1022                                        marketsNode.tooltipCreator = report.getMonthlyReportTooltip();
1023                                }
1024                                
1025                                FDNode production = report.getNode(marketsNode, MonthlyReport.PRODUCTION);
1026                                production.name = "Custom production orders";
1027                                production.custom = MonthlyReport.PRODUCTION;
1028                                production.custom2 = cargo;
1029                                production.tooltipCreator = report.getMonthlyReportTooltip();
1030                                
1031                                production.upkeep += totalCost;
1032                                
1033                                if (weaponCost > 0) {
1034                                        FDNode productionWeapons = report.getNode(marketsNode, MonthlyReport.PRODUCTION_WEAPONS);
1035                                        productionWeapons.name = "Weapons & fighter LPCs for produced ships";
1036                                        productionWeapons.custom = MonthlyReport.PRODUCTION_WEAPONS;
1037                                        productionWeapons.tooltipCreator = report.getMonthlyReportTooltip();
1038                                        productionWeapons.upkeep += weaponCost;
1039                                }
1040                        }
1041                
1042                        for (CargoAPI curr : data.data.values()) {
1043                                local.addAll(curr);
1044                                local.initMothballedShips(Factions.PLAYER);
1045                                for (FleetMemberAPI member : curr.getMothballedShips().getMembersListCopy()) {
1046//                                      member.getRepairTracker().setCR(0f);
1047//                                      member.getRepairTracker().setMothballed(true);
1048                                        member.getRepairTracker().setMothballed(false);
1049                                        member.getRepairTracker().setCR(0.5f);
1050                                        local.getMothballedShips().addFleetMember(member);
1051                                }
1052                        }
1053                        local.sort();
1054                        
1055                        ProductionReportIntel intel = new ProductionReportIntel(gatheringPoint, data, 
1056                                                                                                        totalCost + weaponCost, prod.getAccruedProduction(),
1057                                                                                                        wantedToDoProduction && unableToDoProduction);
1058                        Global.getSector().getIntelManager().addIntel(intel);
1059                }
1060                
1061        }
1062        
1063
1064        @Override
1065        public void reportEconomyMonthEnd() {
1066                super.reportEconomyMonthEnd();
1067
1068                if (TutorialMissionIntel.isTutorialInProgress()) {
1069                        return;
1070                }
1071                
1072                MonthlyReport report = SharedData.getData().getCurrentReport();
1073                FDNode marketsNode = report.getNode(MonthlyReport.OUTPOSTS);
1074                if (marketsNode.custom != null) {
1075                        for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
1076                                if (!market.isPlayerOwned()) continue;
1077                                
1078                                float incentive = market.getIncentiveCredits();
1079                                if (incentive > 0) {
1080                                        FDNode mNode = report.getNode(marketsNode, market.getId());
1081                                        if (mNode.custom != null) {
1082                                                FDNode incNode = report.getNode(mNode, "incentives"); 
1083                                                incNode.name = "Hazard pay";
1084                                                incNode.custom = MonthlyReport.INCENTIVES;
1085                                                incNode.mapEntity = market.getPrimaryEntity();
1086                                                incNode.tooltipCreator = report.getMonthlyReportTooltip();
1087                                                incNode.upkeep += incentive;
1088                                        }
1089                                        market.setIncentiveCredits(0);
1090                                }
1091                        }
1092                }
1093                
1094                
1095                MonthlyReport previous = SharedData.getData().getPreviousReport();
1096                float debt = previous.getDebt();
1097                if (debt > 0) {
1098                        MonthlyReport current = SharedData.getData().getCurrentReport();
1099                        current.getDebtNode().upkeep = debt;
1100                }
1101                
1102                doCustomProduction();
1103                
1104                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
1105                
1106                //MonthlyReport previous = SharedData.getData().getPreviousReport();
1107                SharedData.getData().rollOverReport();
1108                report = SharedData.getData().getPreviousReport();
1109                report.setPreviousDebt(previous.getDebt());
1110                report.setTimestamp(Global.getSector().getClock().getTimestamp());
1111                
1112                report.computeTotals();
1113                int total = (int) (report.getRoot().totalIncome - report.getRoot().totalUpkeep);
1114                float credits = (int) playerFleet.getCargo().getCredits().get();
1115                
1116                float newCredits = credits + total;
1117                if (newCredits < 0) {
1118                        report.setDebt((int) Math.abs(newCredits));
1119                        newCredits = 0;
1120                }
1121                playerFleet.getCargo().getCredits().set(newCredits);
1122                
1123                
1124                String totalStr = Misc.getDGSCredits(Math.abs(total));
1125                //Global.getSector().getCampaignUI().showCoreUITab(CoreUITabId.OUTPOSTS);
1126                
1127                String title = "Monthly income: " + totalStr;
1128                Color highlight = Misc.getHighlightColor();
1129                if (total < 0) {
1130                        title = "Monthly expenses: " + totalStr;
1131                        highlight = Misc.getNegativeHighlightColor();
1132                }
1133                
1134                MessageIntel intel = new MessageIntel(title,
1135                                Misc.getBasePlayerColor(), new String[] {totalStr}, highlight);
1136                intel.setIcon(Global.getSettings().getSpriteName("intel", "monthly_income_report"));
1137                
1138                if (total >= 0) {
1139                        intel.setSound("ui_intel_monthly_income_positive");
1140                } else {
1141                        intel.setSound("ui_intel_monthly_income_negative");
1142                }
1143                
1144                Global.getSector().getCampaignUI().addMessage(intel, MessageClickAction.INCOME_TAB, Tags.INCOME_REPORT);
1145                
1146                
1147//              CommMessageAPI message = FleetLog.beginEntry(
1148//                              title,
1149//                              playerFleet, 
1150//                              highlight,
1151//                              totalStr
1152//              );
1153//              
1154//              message.setSmallIcon(Global.getSettings().getSpriteName("intel_categories", "bounties"));
1155//              if (total >= 0) {
1156//                      message.getSection1().addPara("Over the last month, your fleet, outposts, and other ventures have" +
1157//                                      " produced an income of " + totalStr + ".");                    
1158//              } else {
1159//                      message.getSection1().addPara("Over the last month, your fleet, outposts, and other ventures have" +
1160//                                      " produced expenses of " + totalStr + ".");
1161//              }
1162//              
1163//              if (total < 0 && Math.abs(total) > credits) {
1164//                      message.getSection1().addPara("Your expenses have exceeded your credit balance. If this continues for more than a month, " +
1165//                                      "some crew and officers may begin to leave, and other undertakings requiring credit expenditures may fail.");
1166//                      //message.getSection1().addPara(".");
1167//              }
1168//              message.getSection1().addPara("See the \"Income\" tab in the command screen for a detailed breakdown.");
1169//              
1170//              message.getSection1().setHighlights(totalStr);
1171//              message.getSection1().setHighlightColors(highlight);
1172//              
1173//              message.setShowInCampaignList(true);
1174//              message.setAddToIntelTab(true);
1175//              message.setAction(MessageClickAction.INCOME_TAB);
1176//              message.addTag(Tags.FLEET_LOG);
1177//              message.addTag(Tags.INCOME_REPORT);
1178//              
1179//              Global.getSector().getCampaignUI().addMessage(message);
1180        }
1181
1182//      private MonthlyReportNodeTooltipCreator monthlyReportTooltip;
1183//      public static MonthlyReportNodeTooltipCreator getMonthlyReportTooltip() {
1184//              if (monthlyReportTooltip == null) {
1185//                      monthlyReportTooltip = new MonthlyReportNodeTooltipCreator();
1186//              }
1187//              return monthlyReportTooltip;
1188//      }
1189
1190
1191        @Override
1192        public void reportEconomyTick(int iterIndex) {
1193                super.reportEconomyTick(iterIndex);
1194                
1195                if (TutorialMissionIntel.isTutorialInProgress()) {
1196                        return;
1197                }
1198                
1199                //for (int i = 0; i < 100; i++) {
1200                int crewSalary = Global.getSettings().getInt("crewSalary");
1201                int marineSalary = Global.getSettings().getInt("marineSalary");
1202                
1203                float numIter = Global.getSettings().getFloat("economyIterPerMonth");
1204                float f = 1f / numIter;
1205                
1206                //f = 1f;
1207                
1208                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
1209                MonthlyReport report = SharedData.getData().getCurrentReport();
1210                
1211                
1212                FDNode fleetNode = report.getNode(MonthlyReport.FLEET);
1213                fleetNode.name = "Fleet";
1214                fleetNode.custom = MonthlyReport.FLEET;
1215                fleetNode.tooltipCreator = report.getMonthlyReportTooltip();
1216                
1217                int crewCost = playerFleet.getCargo().getCrew() * crewSalary;
1218                FDNode crewNode = report.getNode(fleetNode, MonthlyReport.CREW);
1219                crewNode.upkeep += crewCost * f;
1220                crewNode.name = "Crew payroll";
1221                crewNode.custom = MonthlyReport.CREW;
1222                crewNode.tooltipCreator = report.getMonthlyReportTooltip();
1223                
1224                int marineCost = playerFleet.getCargo().getMarines() * marineSalary;
1225                if (marineSalary > 0) {
1226                        FDNode marineNode = report.getNode(fleetNode, MonthlyReport.MARINES);
1227                        marineNode.upkeep += marineCost * f;
1228                        marineNode.name = "Marine payroll";
1229                        marineNode.custom = MonthlyReport.MARINES;
1230                        marineNode.tooltipCreator = report.getMonthlyReportTooltip();
1231                }
1232                
1233                
1234//              List<PersonnelAtEntity> droppedOffMarines = PlayerFleetPersonnelTracker.getInstance().getDroppedOff();
1235//              for (PersonnelAtEntity curr : droppedOffMarines) {
1236//                      
1237//              }
1238//              for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
1239//                      if (Misc.playerHasStorageAccess(market)) {
1240//                              
1241//                      }
1242//              }
1243                
1244                
1245                
1246                //FDNode officersNode = report.getNode(MonthlyReport.OFFICERS);
1247                FDNode officersNode = report.getNode(fleetNode, MonthlyReport.OFFICERS);
1248                officersNode.name = "Officer payroll";
1249                officersNode.custom = MonthlyReport.OFFICERS;
1250                officersNode.tooltipCreator = report.getMonthlyReportTooltip();
1251                
1252                
1253                for (OfficerDataAPI od : playerFleet.getFleetData().getOfficersCopy()) {
1254                        float salary = Misc.getOfficerSalary(od.getPerson());
1255                        FDNode oNode = report.getNode(officersNode, od.getPerson().getId()); 
1256                        oNode.name = od.getPerson().getName().getFullName();
1257                        oNode.upkeep += salary * f;
1258                        oNode.custom = od;
1259                }
1260                
1261                FDNode marketsNode = report.getNode(MonthlyReport.OUTPOSTS);
1262                marketsNode.name = "Colonies";
1263                marketsNode.custom = MonthlyReport.OUTPOSTS;
1264                marketsNode.tooltipCreator = report.getMonthlyReportTooltip();
1265                
1266                FDNode storageNode = null;
1267//              storageNode = report.getNode(MonthlyReport.STORAGE);
1268//              storageNode.name = "Storage";
1269//              storageNode.custom = MonthlyReport.STORAGE;
1270//              storageNode.tooltipCreator = report.getMonthlyReportTooltip();
1271                
1272                float storageFraction = Global.getSettings().getFloat("storageFreeFraction");
1273                
1274                int index = 0;
1275                for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
1276//                      if (index % 10 != 3 || index == 0) {
1277//                              index++;
1278//                              continue;
1279//                      }
1280                        
1281                        if (!market.isPlayerOwned() && Misc.playerHasStorageAccess(market)) {
1282                                float vc = Misc.getStorageCargoValue(market);
1283                                float vs = Misc.getStorageShipValue(market);
1284                                
1285                                float fc = (int) (vc * storageFraction);
1286                                float fs = (int) (vs * storageFraction);
1287                                if (fc > 0 || fs > 0) {
1288                                        if (storageNode == null) {
1289                                                storageNode = report.getNode(MonthlyReport.STORAGE);
1290                                                storageNode.name = "Storage";
1291                                                storageNode.custom = MonthlyReport.STORAGE;
1292                                                storageNode.tooltipCreator = report.getMonthlyReportTooltip();
1293                                        }
1294                                        FDNode mNode = report.getNode(storageNode, market.getId());
1295                                        String desc = "";
1296                                        if (fc > 0 && fs > 0) {
1297                                                desc = "ships & cargo";
1298                                        } else if (fc > 0) {
1299                                                desc = "cargo";
1300                                        } else {
1301                                                desc = "ships";
1302                                        }
1303                                        mNode.name = market.getName() + " (" + desc + ")";
1304                                        mNode.custom = market;
1305                                        mNode.custom2 = MonthlyReport.STORAGE;
1306                                        //mNode.tooltipCreator = report.getMonthlyReportTooltip();
1307                                        
1308                                        mNode.upkeep += (fc + fs) * f;
1309                                }
1310                                continue;
1311                        }
1312                        
1313                        
1314                        //if (market.isHidden()) continue;
1315                        //if (!Factions.DIKTAT.equals(market.getFaction().getId()) && !market.isPlayerOwned()) continue;
1316                        if (!market.isPlayerOwned()) continue;
1317                        
1318                        FDNode mNode = report.getNode(marketsNode, market.getId()); 
1319                        mNode.name = market.getName() + " (" + market.getSize() + ")";
1320                        mNode.custom = market;
1321                        
1322                        FDNode indNode = report.getNode(mNode, "industries"); 
1323                        indNode.name = "Industries & structures";
1324                        indNode.custom = MonthlyReport.INDUSTRIES;
1325                        indNode.mapEntity = market.getPrimaryEntity();
1326                        indNode.tooltipCreator = report.getMonthlyReportTooltip();
1327//                      node.income += (int) market.getIndustryIncome();
1328//                      node.upkeep += (int) market.getIndustryUpkeep();
1329                        
1330                        for (Industry curr : market.getIndustries()) {
1331                                FDNode iNode = report.getNode(indNode, curr.getId());
1332                                iNode.name = curr.getCurrentName();
1333                                iNode.income += curr.getIncome().getModifiedInt() * f;
1334                                iNode.upkeep += curr.getUpkeep().getModifiedInt() * f;
1335                                iNode.custom = curr;
1336                                iNode.mapEntity = market.getPrimaryEntity();
1337                        }
1338                        
1339                        FDNode exportNode = report.getNode(mNode, "exports"); 
1340                        exportNode.name = "Exports";
1341                        exportNode.custom = MonthlyReport.EXPORTS;
1342                        exportNode.mapEntity = market.getPrimaryEntity();
1343                        exportNode.tooltipCreator = report.getMonthlyReportTooltip();
1344                        
1345                        addExportsGroupedByCommodity(report, exportNode, market, f);
1346                        //addExportsGroupedByFaction(report, exportNode, market, f);
1347                        
1348                        
1349//                      FDNode overheadNode = report.getNode(exportNode, "overhead");
1350//                      if (overheadNode.name == null) {
1351//                              overheadNode.name = "Overhead";
1352//                              overheadNode.icon = Global.getSettings().getSpriteName("income_report", "overhead");
1353//                              overheadNode.custom = market;
1354//                              overheadNode.mapEntity = market.getPrimaryEntity();
1355//                              overheadNode.tooltipCreator = new OverheadTooltipCreator();
1356//                      }
1357//                      
1358//                      OverheadData overhead = computeOverhead(market);
1359//                      if (overhead.fraction > 0) {
1360//                              float totalIncome = market.getExportIncome(false);
1361//                              overheadNode.upkeep += totalIncome * overhead.fraction * f; 
1362//                      }
1363                        
1364                        index++;
1365                }
1366                //}
1367                
1368                
1369                FDNode adminNode = report.getNode(marketsNode, MonthlyReport.ADMIN);
1370                adminNode.name = "Administrators";
1371                adminNode.custom = MonthlyReport.ADMIN;
1372                adminNode.tooltipCreator = report.getMonthlyReportTooltip();
1373                
1374                for (AdminData data : Global.getSector().getCharacterData().getAdmins()) {
1375                        float salary = Misc.getAdminSalary(data.getPerson());
1376                        if (salary <= 0) continue;
1377                        
1378                        FDNode aNode = report.getNode(adminNode, data.getPerson().getId()); 
1379                        aNode.name = data.getPerson().getName().getFullName();
1380                        if (data.getMarket() != null) {
1381                                aNode.name += " (" + data.getMarket().getName() + ")";
1382                        } else {
1383                                aNode.name += " (unassigned)";
1384                                salary *= Global.getSettings().getFloat("idleAdminSalaryMult");
1385                        }
1386                        aNode.upkeep += salary * f;
1387                        aNode.custom = data;
1388                }
1389                
1390                //reportEconomyMonthEnd();
1391        }
1392        
1393//      public static class OverheadData {
1394//              public SupplierData max;
1395//              public float fraction;
1396//      }
1397//      
1398//      public static OverheadData computeOverhead(MarketAPI market) {
1399//              OverheadData result = new OverheadData();
1400//              
1401//              SupplierData max = null;
1402//              float maxValue = 0;
1403//              float total = 0f;
1404//              List<CommodityOnMarketAPI> comList = market.getCommoditiesCopy();
1405//              for (CommodityOnMarketAPI com : comList) {
1406//                      for (SupplierData sd : com.getExports()) {
1407//                              int income = sd.getExportValue(market);
1408//                              if (income <= 0) continue;
1409//                              
1410//                              total += income;
1411//                              if (income > maxValue) {
1412//                                      max = sd;
1413//                                      maxValue = income;
1414//                              }
1415//                      }
1416//              }
1417//              
1418//              if (max != null && maxValue > 0) {
1419//                      result.max = max;
1420//                      float units = total / maxValue;
1421//                      float mult = Misc.logOfBase(2f, units) + 1f;
1422//                      result.fraction = 1f - (mult / units);
1423//                      if (result.fraction < 0) result.fraction = 0;
1424//                      if (result.fraction > 0) {
1425//                              result.fraction = Math.round(result.fraction * 100f) / 100f;
1426//                              result.fraction = Math.max(result.fraction, 0.01f);
1427//                      }
1428//              }
1429//              return result;
1430//      }
1431        
1432        public static class ExportCommodityGroupData {
1433                public CommodityOnMarketAPI com;
1434                public int quantity;
1435        }
1436        
1437        protected void addExportsGroupedByCommodity(MonthlyReport report, FDNode parent, MarketAPI market, float f) {
1438                for (CommodityOnMarketAPI com : market.getCommoditiesCopy()) {
1439                        FDNode eNode = report.getNode(parent, com.getId());
1440                        eNode.name = com.getCommodity().getName();
1441                        eNode.income += com.getExportIncome() * f;
1442                        eNode.custom = com;
1443                        eNode.mapEntity = market.getPrimaryEntity();
1444                }
1445                
1446//              List<FDNode> sorted = new ArrayList<FDNode>(parent.getChildren().values());
1447//              Collections.sort(sorted, new Comparator<FDNode>() {
1448//                      public int compare(FDNode o1, FDNode o2) {
1449//                              return o2.income - o1.income;
1450//                      }
1451//              });
1452//              parent.getChildren().clear();
1453//              parent.getChildren().
1454        }
1455        
1456        
1457        
1458}
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476