001package com.fs.starfarer.api.impl.campaign.intel.bases;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.Comparator;
006import java.util.LinkedHashMap;
007import java.util.LinkedHashSet;
008import java.util.List;
009import java.util.Random;
010
011import com.fs.starfarer.api.EveryFrameScript;
012import com.fs.starfarer.api.Global;
013import com.fs.starfarer.api.campaign.StarSystemAPI;
014import com.fs.starfarer.api.campaign.econ.Industry;
015import com.fs.starfarer.api.campaign.econ.MarketAPI;
016import com.fs.starfarer.api.impl.campaign.DebugFlags;
017import com.fs.starfarer.api.impl.campaign.Tuning;
018import com.fs.starfarer.api.impl.campaign.ids.Factions;
019import com.fs.starfarer.api.impl.campaign.ids.Tags;
020import com.fs.starfarer.api.impl.campaign.intel.BaseEventManager;
021import com.fs.starfarer.api.impl.campaign.intel.events.LuddicPathHostileActivityFactor;
022import com.fs.starfarer.api.util.IntervalUtil;
023import com.fs.starfarer.api.util.Misc;
024import com.fs.starfarer.api.util.Pair;
025import com.fs.starfarer.api.util.WeightedRandomPicker;
026
027public class LuddicPathBaseManager extends BaseEventManager {
028
029        public static float LUDDIC_CHURCH_INTEREST_MULT = 0.1f;
030        public static float PLAYER_DEFEATED_PATHER_CRISIS_INTEREST_MULT = 0.5f;
031        
032        public static float AI_CORE_ADMIN_INTEREST = 10f;
033
034        public static final String KEY = "$core_luddicPathBaseManager";
035        
036        public static final float INERTIA_DAYS_MAX = 30f;
037        public static final float INERTIA_DAYS_MIN = 10f;
038        
039        public static final float CHECK_DAYS = 10f;
040        public static final float CHECK_PROB = 0.5f;
041        
042        
043        public static LuddicPathBaseManager getInstance() {
044                Object test = Global.getSector().getMemoryWithoutUpdate().get(KEY);
045                return (LuddicPathBaseManager) test; 
046        }
047        
048        protected long start = 0;
049        
050        public LuddicPathBaseManager() {
051                super();
052                Global.getSector().getMemoryWithoutUpdate().set(KEY, this);
053                start = Global.getSector().getClock().getTimestamp();
054        }
055        
056        @Override
057        protected int getMinConcurrent() {
058                return Global.getSettings().getInt("minLPBases");
059        }
060        @Override
061        protected int getMaxConcurrent() {
062                return Global.getSettings().getInt("maxLPBases");
063        }
064        
065        @Override
066        protected float getBaseInterval() {
067                return CHECK_DAYS;
068        }
069        
070        @Override
071        protected Object readResolve() {
072                super.readResolve();
073                if (cellChecker == null) {
074                        cellChecker = new IntervalUtil(1f, 3f);
075                }
076                if (cells == null) {
077                        cells = new LinkedHashMap<MarketAPI, LuddicPathCellsIntel>();
078                }
079                return this;
080        }
081        
082        protected IntervalUtil cellChecker = new IntervalUtil(1f, 3f);
083        protected int timesSinceLastChange = 10000;
084        protected int activeMod = 0;
085        protected int sleeperMod = 0;
086        
087        @Override
088        public void advance(float amount) {
089                super.advance(amount);
090                
091                float days = Misc.getDays(amount);
092                cellChecker.advance(days);
093                if (cellChecker.intervalElapsed()) {
094                        timesSinceLastChange++;
095                        if (timesSinceLastChange > 50) {
096                                activeMod = Misc.random.nextInt(3);
097                                sleeperMod = Misc.random.nextInt(3);
098                                timesSinceLastChange = 0;
099                        }
100                        
101                        updateCellStatus();
102                }
103        }
104
105        protected LinkedHashMap<MarketAPI, LuddicPathCellsIntel> cells = new LinkedHashMap<MarketAPI, LuddicPathCellsIntel>();
106        
107        protected void updateCellStatus() {
108                
109                float fraction = Global.getSettings().getFloat("basePatherCellFraction");
110                float minInterest = Global.getSettings().getFloat("minInterestForPatherCells");
111                
112                List<Pair<MarketAPI, Float>> marketAndScore = new ArrayList<Pair<MarketAPI,Float>>();
113                int total = 0;
114                for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
115                        if (market.getEconGroup() != null) continue;
116                        if (market.getSize() < 4) continue;
117                        float score = getLuddicPathMarketInterest(market);
118                        total++;
119                        if (score >= minInterest) {
120                                marketAndScore.add(new Pair<MarketAPI, Float>(market, score));
121                        }
122                }
123                
124                int numActive = Math.round(total * fraction);
125                int numSleeper = numActive;
126                
127                numActive += activeMod;
128                numSleeper += sleeperMod;
129                
130                LinkedHashSet<MarketAPI> active = new LinkedHashSet<MarketAPI>();
131                LinkedHashSet<MarketAPI> sleeper = new LinkedHashSet<MarketAPI>();
132                
133                
134                Collections.sort(marketAndScore, new Comparator<Pair<MarketAPI, Float>>() {
135                        public int compare(Pair<MarketAPI, Float> o1, Pair<MarketAPI, Float> o2) {
136                                return (int) Math.signum(o2.two - o1.two);
137                        }
138                });
139                
140                int count = 0;
141                for (Pair<MarketAPI, Float> p : marketAndScore) {
142                        if (count < numActive) {
143                                active.add(p.one);
144                        } else if (count < numActive + numSleeper) {
145                                sleeper.add(p.one);
146                        } else {
147                                break;
148                        }
149                        count++;
150                }
151                
152                
153                for (MarketAPI market : new ArrayList<MarketAPI>(cells.keySet())) {
154                        LuddicPathCellsIntel intel = cells.get(market);
155                        
156                        if (!active.contains(market) && !sleeper.contains(market)) {
157                                
158                                float score = getLuddicPathMarketInterest(market);
159                                
160                                if (intel.getInertiaTime() >= INERTIA_DAYS_MAX ||
161                                                (intel.getInertiaTime() >= INERTIA_DAYS_MIN && score < minInterest)) {
162                                        if (!intel.isEnding()) {
163                                                intel.endAfterDelay();
164                                                if (market.isPlayerOwned() || DebugFlags.PATHER_BASE_DEBUG) {
165                                                        intel.sendUpdateIfPlayerHasIntel(LuddicPathCellsIntel.UPDATE_DISSOLVED, false);
166                                                }
167                                        }
168                                        cells.remove(market);
169                                } else { // keep already-established cells for up to INERTIA_DAYS_MAX, at the expense of other potential cells
170                                        if (intel.isSleeper()) {
171                                                List<MarketAPI> sleeperList = new ArrayList<MarketAPI>(sleeper);
172                                                for (int i = sleeperList.size() - 1; i >= 0; i--) {
173                                                        MarketAPI other = sleeperList.get(i);
174                                                        LuddicPathCellsIntel otherIntel = cells.get(other);
175                                                        if (otherIntel != null) continue;
176                                                        
177                                                        sleeper.remove(other);
178                                                        break;
179                                                }
180                                                sleeper.add(market);
181                                        } else {
182                                                List<MarketAPI> activeList = new ArrayList<MarketAPI>(active);
183                                                for (int i = activeList.size() - 1; i >= 0; i--) {
184                                                        MarketAPI other = activeList.get(i);
185                                                        LuddicPathCellsIntel otherIntel = cells.get(other);
186                                                        if (otherIntel != null) continue;
187                                                        
188                                                        active.remove(other);
189                                                        break;
190                                                }
191                                                active.add(market);
192                                        }
193                                }
194                        } else {
195                                intel.setInertiaTime(0f);
196                        }
197                }
198                
199                for (MarketAPI market : active) {
200                        LuddicPathCellsIntel intel = cells.get(market);
201                        LuddicPathBaseIntel base = LuddicPathCellsIntel.getClosestBase(market);
202                        if (intel == null) {
203                                intel = new LuddicPathCellsIntel(market, base == null);
204                                cells.put(market, intel);
205                        }
206                        if (base != null) {
207                                intel.makeActiveIfPossible();
208                        } else {
209                                intel.makeSleeper();
210                        }
211                }
212                
213                for (MarketAPI market : sleeper) {
214                        LuddicPathCellsIntel intel = cells.get(market);
215                        if (intel == null) {
216                                intel = new LuddicPathCellsIntel(market, true);
217                                cells.put(market, intel);
218                        }
219                        intel.makeSleeper();
220                }
221        }
222
223        public static float getLuddicPathMarketInterest(MarketAPI market) {
224                if (market.getFactionId().equals(Factions.LUDDIC_PATH)) return 0f;
225                float total = 0f;
226                
227                String aiCoreId = market.getAdmin().getAICoreId();
228                if (aiCoreId != null) {
229                        total += AI_CORE_ADMIN_INTEREST;
230                }
231                
232                for (Industry ind : market.getIndustries()) {
233                        total += ind.getPatherInterest();
234                }
235                
236                if (total > 0) {
237                        total += new Random(market.getName().hashCode()).nextFloat() * 0.1f;
238                }
239                
240                if (market.getFactionId().equals(Factions.LUDDIC_CHURCH)) {
241                        total *= LUDDIC_CHURCH_INTEREST_MULT;
242                }
243                
244                if (market.isPlayerOwned() && LuddicPathHostileActivityFactor.isPlayerDefeatedPatherExpedition()) {
245                        total *= PLAYER_DEFEATED_PATHER_CRISIS_INTEREST_MULT;
246                }
247                
248                return total;
249        }
250        
251        
252        
253        protected Random random = new Random();
254        @Override
255        protected EveryFrameScript createEvent() {
256                if (numSpawnChecksToSkip > 0) {
257                        numSpawnChecksToSkip--;
258                        return null;
259                }
260                
261                if (random.nextFloat() < CHECK_PROB) return null;
262                
263                StarSystemAPI system = pickSystemForLPBase();
264                if (system == null) return null;
265                
266                String factionId = Factions.LUDDIC_PATH;
267                
268                LuddicPathBaseIntel intel = new LuddicPathBaseIntel(system, factionId);
269                if (intel.isDone()) intel = null;
270
271                return intel;
272        }
273        
274        
275        protected StarSystemAPI pickSystemForLPBase() {
276                WeightedRandomPicker<StarSystemAPI> far = new WeightedRandomPicker<StarSystemAPI>(random);
277                WeightedRandomPicker<StarSystemAPI> picker = new WeightedRandomPicker<StarSystemAPI>(random);
278                
279                for (StarSystemAPI system : Global.getSector().getStarSystems()) {
280                        if (system.hasTag(Tags.THEME_SPECIAL)) continue;
281                        if (system.hasTag(Tags.THEME_HIDDEN)) continue;
282                        
283                        float days = Global.getSector().getClock().getElapsedDaysSince(system.getLastPlayerVisitTimestamp());
284                        if (days < 45f) continue;
285                        
286                        if (system.getCenter().getMemoryWithoutUpdate().contains(PirateBaseManager.RECENTLY_USED_FOR_BASE)) continue;
287                        
288                        float weight = 0f;
289                        if (system.hasTag(Tags.THEME_MISC_SKIP)) {
290                                weight = 1f;
291                        } else if (system.hasTag(Tags.THEME_MISC)) {
292                                weight = 3f;
293                        } else if (system.hasTag(Tags.THEME_REMNANT_NO_FLEETS)) {
294                                weight = 3f;
295                        } else if (system.hasTag(Tags.THEME_RUINS)) {
296                                weight = 5f;
297                        } else if (system.hasTag(Tags.THEME_CORE_UNPOPULATED)) {
298                                weight = 1f;
299                        }
300                        if (weight <= 0f) continue;
301                        
302                        float usefulStuff = system.getCustomEntitiesWithTag(Tags.OBJECTIVE).size() +
303                                                                system.getCustomEntitiesWithTag(Tags.STABLE_LOCATION).size();
304                        if (usefulStuff <= 0) continue;
305                        
306                        if (Misc.hasPulsar(system)) continue;
307                        if (Misc.getMarketsInLocation(system).size() > 0) continue;
308                        
309                        float dist = system.getLocation().length();
310                        
311                        
312//                      float distMult = 1f - dist / 20000f;
313//                      if (distMult > 1f) distMult = 1f;
314//                      if (distMult < 0.1f) distMult = 0.1f;
315                        
316                        float distMult = 1f;
317                        
318                        if (dist > 36000f) {
319                                far.add(system, weight * usefulStuff * distMult);
320                        } else {
321                                picker.add(system, weight * usefulStuff * distMult);
322                        }
323                }
324                
325                if (picker.isEmpty()) {
326                        picker.addAll(far);
327                }
328                
329                return picker.pick();
330        }
331
332        
333        protected int numDestroyed = 0;
334        protected int numSpawnChecksToSkip = 0;
335        
336        public void incrDestroyed() {
337                numDestroyed++;
338                numSpawnChecksToSkip = Math.max(numSpawnChecksToSkip, (Tuning.PATHER_BASE_MIN_TIMEOUT_MONTHS + 
339                                        Misc.random.nextInt(Tuning.PATHER_BASE_MAX_TIMEOUT_MONTHS - Tuning.PATHER_BASE_MIN_TIMEOUT_MONTHS + 1))
340                                        * 3); // checks happen every 10 days on average, *3 to get months
341        }
342}
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357