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