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