001package com.fs.starfarer.api.impl.campaign.fleets;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import org.apache.log4j.Logger;
007
008import com.fs.starfarer.api.EveryFrameScript;
009import com.fs.starfarer.api.Global;
010import com.fs.starfarer.api.campaign.BaseCampaignEventListener;
011import com.fs.starfarer.api.campaign.BattleAPI;
012import com.fs.starfarer.api.campaign.CampaignFleetAPI;
013import com.fs.starfarer.api.campaign.FleetStubAPI;
014import com.fs.starfarer.api.campaign.SectorEntityToken;
015import com.fs.starfarer.api.campaign.econ.MarketAPI;
016import com.fs.starfarer.api.campaign.listeners.FleetEventListener;
017import com.fs.starfarer.api.impl.campaign.fleets.FleetFactory.PatrolType;
018import com.fs.starfarer.api.impl.campaign.ids.Conditions;
019import com.fs.starfarer.api.impl.campaign.ids.FleetTypes;
020import com.fs.starfarer.api.impl.campaign.ids.Industries;
021import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
022import com.fs.starfarer.api.impl.campaign.ids.Ranks;
023import com.fs.starfarer.api.util.IntervalUtil;
024import com.fs.starfarer.api.util.Misc;
025import com.fs.starfarer.api.util.RollingAverageTracker;
026import com.fs.starfarer.api.util.WeightedRandomPicker;
027
028public class PatrolFleetManagerV2 extends BaseCampaignEventListener implements 
029                                                EveryFrameScript, FleetEventListener {
030
031        public static Logger log = Global.getLogger(PatrolFleetManagerV2.class);
032        
033        public static class PatrolFleetData {
034                public float startingFleetPoints = 0;
035                public FleetStubAPI stub;
036                public PatrolType type;
037                public MarketAPI sourceMarket;
038                public PatrolFleetData(FleetStubAPI fleet, PatrolType type) {
039                        this.stub = fleet;
040                        this.type = type;
041                }
042        }
043        
044        //private float econInterval = Global.getSettings().getFloat("economyIntervalnGameDays");
045        
046        private MarketAPI market;
047        private List<PatrolFleetData> activePatrols = new ArrayList<PatrolFleetData>();
048        private IntervalUtil tracker;
049        private int maxPatrols;
050
051        private RollingAverageTracker patrolBattlesLost;
052        public PatrolFleetManagerV2(MarketAPI market) {
053                super(true);
054                this.market = market;
055                
056                float interval = Global.getSettings().getFloat("averagePatrolSpawnInterval");
057                tracker = new IntervalUtil(interval * 0.75f, interval * 1.25f);
058                
059                readResolve();
060        }
061        
062        protected Object readResolve() {
063                if (patrolBattlesLost == null) {
064                        float patrolStrengthCheckInterval = Global.getSettings().getFloat("economyIntervalnGameDays");
065                        float min = patrolStrengthCheckInterval - Math.min(patrolStrengthCheckInterval * 0.5f, 2f);
066                        float max = patrolStrengthCheckInterval + Math.min(patrolStrengthCheckInterval * 0.5f, 2f);
067                        patrolBattlesLost = new RollingAverageTracker(min, max, Misc.getGenericRollingAverageFactor());
068                }
069                return this;
070        }
071        
072        public void advance(float amount) {
073                //if (true) return;
074                float days = Global.getSector().getClock().convertToDays(amount);
075                
076                patrolBattlesLost.advance(days);
077                
078                float losses = patrolBattlesLost.getAverage();
079                
080                //tracker.advance(days);
081                tracker.advance(days * Math.max(1f, losses));
082                if (!tracker.intervalElapsed()) return;
083                
084                if (market.hasCondition(Conditions.DECIVILIZED)) return;
085                
086//              if (market.getId().equals("jangala")) {
087//                      System.out.println("fwefwefe");
088//              }
089                
090                List<PatrolFleetData> remove = new ArrayList<PatrolFleetData>();
091                for (PatrolFleetData data : activePatrols) {
092                        if (data.stub.getContainingLocation() == null ||
093                                (!data.stub.getContainingLocation().getFleets().contains(data.stub.getFleet()) &&
094                                !data.stub.getContainingLocation().getFleetStubs().contains(data.stub))) {
095                                remove.add(data);
096                                log.info("Cleaning up orphaned patrol [" + data.stub.getId() + "] for market [" + market.getName() + "]");
097                        }
098                }
099                activePatrols.removeAll(remove);
100                
101//              if (market.getId().equals("jangala")) {
102//                      System.out.println("23rwefwe");
103//              }
104                //maxPatrols = Math.max(1, market.getSize() - 3) + (int) (market.getStabilityValue() * 0.5f);
105                //float losses = patrolBattlesLost.getAverage();
106                
107                maxPatrols = (int) (Math.max(1, market.getSize() - 3) * (market.getStabilityValue() / 10f)) + 
108                                        (int) Math.max(0, Math.min(losses, 5));
109                if (maxPatrols < 1) maxPatrols = 1;
110                
111                boolean hasStationOrSpaceport = market.hasIndustry(Industries.ORBITALSTATION) ||
112                                                                                market.hasSpaceport() ||
113                                                                                market.hasIndustry(Industries.BATTLESTATION);
114                if (market.hasIndustry(Industries.MILITARYBASE)) {
115                        maxPatrols += 1;
116                        if (hasStationOrSpaceport) maxPatrols++;
117                }
118                if (hasStationOrSpaceport) maxPatrols++;
119//              if (market.getId().equals("jangala")) {
120//                      System.out.println("fwefewew");
121//              }
122                
123                log.debug("");
124                log.debug("Checking whether to spawn patrol for market [" + market.getName() + "]");
125                if (activePatrols.size() < maxPatrols) {
126                        log.info(activePatrols.size() + " out of a maximum " + maxPatrols + " patrols in play for market [" + market.getName() + "]");
127
128                        WeightedRandomPicker<PatrolType> picker = new WeightedRandomPicker<PatrolType>();
129                        picker.add(PatrolType.FAST, 
130                                        Math.max(1, maxPatrols - getCount(PatrolType.COMBAT, PatrolType.HEAVY)));
131                        picker.add(PatrolType.COMBAT, 
132                                        Math.max(1, maxPatrols - getCount(PatrolType.FAST, PatrolType.HEAVY) + market.getSize()) + losses * 0.5f);
133                        
134                        if (market.getSize() >= 5) {
135                                picker.add(PatrolType.HEAVY, 
136                                                Math.max(1, maxPatrols - getCount(PatrolType.FAST, PatrolType.COMBAT) + market.getSize() - 5) + losses);
137                        }
138                        
139                        
140                        PatrolType type = picker.pick();
141                        
142                        float combat = 0f;
143                        float tanker = 0f;
144                        float freighter = 0f;
145                        String fleetType = FleetTypes.PATROL_SMALL;
146                        switch (type) {
147                        case FAST:
148                                fleetType = FleetTypes.PATROL_SMALL;
149                                combat = Math.round(3f + (float) Math.random() * 2f);
150                                combat += Math.min(5f, losses * 2f);
151                                break;
152                        case COMBAT:
153                                fleetType = FleetTypes.PATROL_MEDIUM;
154                                combat = Math.round(6f + (float) Math.random() * 3f);
155                                combat += Math.min(15f, losses * 4f);
156                                
157                                tanker = Math.round((float) Math.random());
158                                break;
159                        case HEAVY:
160                                fleetType = FleetTypes.PATROL_LARGE;
161                                combat = Math.round(10f + (float) Math.random() * 5f);
162                                combat += Math.min(25f, losses * 6f);
163                                
164                                tanker = 2f;
165                                freighter = 2f;
166                                break;
167                        }
168                        combat *= 1f + (market.getStabilityValue() / 20f);
169                        //combat += Math.min(30f, losses * 3f);
170
171                        //CampaignFleetAPI fleet = FleetFactoryV2.createFleet(new FleetParams(
172                                        
173                        SectorEntityToken entity = market.getPrimaryEntity();
174                        //FleetOrStubAPI fleet = FleetFactoryV2.createFleetOrStub(new FleetParams(
175                        FleetStubAPI stub = null;
176//                              FleetFactoryV2.createStub(new FleetParams(
177//                                      null,
178//                                      market, 
179//                                      market.getFactionId(),
180//                                      null, // fleet's faction, if different from above, which is also used for source market picking
181//                                      fleetType,
182//                                      combat, // combatPts
183//                                      freighter, // freighterPts 
184//                                      tanker, // tankerPts
185//                                      0f, // transportPts
186//                                      0f, // linerPts
187//                                      0f, // civilianPts 
188//                                      0f, // utilityPts
189//                                      0f, // qualityBonus
190//                                      -1f, // qualityOverride
191//                                      1f + Math.min(1f, losses / 10f), // officer num mult
192//                                      0 + (int) losses// officer level bonus
193//                                      ), entity.getContainingLocation(),
194//                                      new Vector2f(entity.getLocation().x, entity.getLocation().y));
195//                      
196//                      stub.addEventListener(this);
197                        
198                        
199                        PatrolFleetData data = new PatrolFleetData(stub, type);
200                        data.startingFleetPoints = combat * 3;
201                        data.sourceMarket = market;
202                        activePatrols.add(data);
203                        
204                        PatrolAssignmentAIV2 ai = new PatrolAssignmentAIV2(stub, data);
205                        stub.addScript(ai);
206                        
207                        stub.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_PATROL_FLEET, true);
208
209//                      if ((type == PatrolType.FAST && (float) Math.random() > 0.25f) ||
210//                                      (type == PatrolType.COMBAT && (float) Math.random() > 0.5f)) {
211                        
212                        if (type == PatrolType.FAST || type == PatrolType.COMBAT) {
213                                stub.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_CUSTOMS_INSPECTOR, true);
214                        }
215                        
216                        stub.setAdmiralPost(Ranks.POST_PATROL_COMMANDER);
217                        
218                        switch (type) {
219                        case FAST:
220                                stub.setAdmiralRank(Ranks.SPACE_LIEUTENANT);
221                                break;
222                        case COMBAT:
223                                stub.setAdmiralRank(Ranks.SPACE_COMMANDER);
224                                break;
225                        case HEAVY:
226                                stub.setAdmiralRank(Ranks.SPACE_CAPTAIN);
227                                break;
228                        }
229                        
230//                      FleetStubConverterPlugin converter = stub.getConverter();
231//                      if (stub.isStub() && converter.shouldConvertFromStub(stub)) {
232//                              converter.convertToFleet(stub);
233//                      }
234                        
235                        
236                        log.info("Spawned patrol fleet [" + stub.getId() + "] from market " + market.getName());
237                } else {
238                        log.debug("Maximum number of " + maxPatrols + " patrols already in play for market [" + market.getName() + "]");
239                }
240        }
241        
242        private int getCount(PatrolType ... types) {
243                int count = 0;
244                for (PatrolType type : types) {
245                        for (PatrolFleetData data : activePatrols) {
246                                if (data.type == type) count++;
247                        }
248                }
249                return count;
250        }
251
252        public boolean isDone() {
253                return false;
254        }
255
256        public boolean runWhilePaused() {
257                return false;
258        }
259
260        
261        public void reportFleetDespawnedToListener(CampaignFleetAPI fleet, FleetDespawnReason reason, Object param) {
262                for (PatrolFleetData data : activePatrols) {
263                        if (data.stub.getId() == fleet.getId()) {
264                                activePatrols.remove(data);
265                                break;
266                        }
267                }
268        }
269
270        public void reportBattleOccurred(CampaignFleetAPI fleet, CampaignFleetAPI primaryWinner, BattleAPI battle) {
271                
272        }
273        
274        
275        /*
276        @Override
277        public void reportFleetDespawned(CampaignFleetAPI fleet, FleetDespawnReason reason, Object param) {
278                super.reportFleetDespawned(fleet, reason, param);
279                
280                for (PatrolFleetData data : activePatrols) {
281                        if (data.fleet == fleet) {
282                                activePatrols.remove(data);
283                                break;
284                        }
285                }
286        }
287
288        @Override
289        public void reportBattleOccurred(CampaignFleetAPI primaryWinner, BattleAPI battle) {
290                super.reportBattleOccurred(primaryWinner, battle);
291                
292                boolean playerWon = battle.isPlayerSide(battle.getSideFor(primaryWinner));
293                boolean playerLost = battle.isPlayerSide(battle.getOtherSideFor(primaryWinner));
294                if (primaryWinner.isInOrNearSystem(market.getStarSystem())) {
295                        // losing to pirates doesn't trigger patrol strength going up; don't want pirates wiped out
296                        if (primaryWinner.getFaction().getId().equals(Factions.PIRATES)) return;
297                        if (primaryWinner.getFaction().getId().equals(Factions.LUDDIC_PATH)) return;
298                        
299                        for (CampaignFleetAPI loser : battle.getOtherSideSnapshotFor(primaryWinner)) {
300                                if (loser.getFaction() == market.getFaction()) {
301                                        if (playerWon) {
302                                                patrolBattlesLost.add(1);
303                                        } else {
304                                                //patrolBattlesLost.add(1);
305                                        }
306                                } else if (primaryWinner.getFaction() == market.getFaction()) {
307                                        // winning vs pirates doesn't trigger strength getting smaller, might happen too easily                         
308                                        if (loser.getFaction().getId().equals(Factions.PIRATES)) return;
309                                        if (loser.getFaction().getId().equals(Factions.LUDDIC_PATH)) return;
310                                        if (playerLost) {
311                                                patrolBattlesLost.sub(1);
312                                        } else {
313                                                //patrolBattlesLost.sub(1);
314                                        }
315                                }
316                        }
317                }
318        }
319
320        */
321
322}
323
324
325
326
327
328
329
330
331
332
333
334
335
336