001package com.fs.starfarer.api.impl.campaign.intel.bases;
002
003import java.util.ArrayList;
004import java.util.LinkedHashMap;
005import java.util.List;
006import java.util.Set;
007
008import java.awt.Color;
009
010import org.apache.log4j.Logger;
011import org.json.JSONException;
012import org.json.JSONObject;
013
014import com.fs.starfarer.api.EveryFrameScript;
015import com.fs.starfarer.api.Global;
016import com.fs.starfarer.api.campaign.BattleAPI;
017import com.fs.starfarer.api.campaign.CampaignEventListener.FleetDespawnReason;
018import com.fs.starfarer.api.campaign.CampaignFleetAPI;
019import com.fs.starfarer.api.campaign.FactionAPI;
020import com.fs.starfarer.api.campaign.JumpPointAPI;
021import com.fs.starfarer.api.campaign.PersonImportance;
022import com.fs.starfarer.api.campaign.ReputationActionResponsePlugin.ReputationAdjustmentResult;
023import com.fs.starfarer.api.campaign.SectorEntityToken;
024import com.fs.starfarer.api.campaign.StarSystemAPI;
025import com.fs.starfarer.api.campaign.TextPanelAPI;
026import com.fs.starfarer.api.campaign.comm.IntelInfoPlugin;
027import com.fs.starfarer.api.campaign.econ.CommodityOnMarketAPI;
028import com.fs.starfarer.api.campaign.econ.EconomyAPI.EconomyUpdateListener;
029import com.fs.starfarer.api.campaign.econ.Industry;
030import com.fs.starfarer.api.campaign.econ.MarketAPI;
031import com.fs.starfarer.api.campaign.econ.MarketAPI.SurveyLevel;
032import com.fs.starfarer.api.campaign.listeners.FleetEventListener;
033import com.fs.starfarer.api.characters.PersonAPI;
034import com.fs.starfarer.api.combat.MutableStat.StatMod;
035import com.fs.starfarer.api.combat.ShipVariantAPI;
036import com.fs.starfarer.api.fleet.FleetMemberAPI;
037import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin;
038import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.CustomRepImpact;
039import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActionEnvelope;
040import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActions;
041import com.fs.starfarer.api.impl.campaign.DebugFlags;
042import com.fs.starfarer.api.impl.campaign.Tuning;
043import com.fs.starfarer.api.impl.campaign.fleets.RouteLocationCalculator;
044import com.fs.starfarer.api.impl.campaign.ids.Conditions;
045import com.fs.starfarer.api.impl.campaign.ids.Entities;
046import com.fs.starfarer.api.impl.campaign.ids.Factions;
047import com.fs.starfarer.api.impl.campaign.ids.Industries;
048import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
049import com.fs.starfarer.api.impl.campaign.ids.Ranks;
050import com.fs.starfarer.api.impl.campaign.ids.Stats;
051import com.fs.starfarer.api.impl.campaign.ids.Submarkets;
052import com.fs.starfarer.api.impl.campaign.ids.Tags;
053import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin;
054import com.fs.starfarer.api.impl.campaign.intel.PersonBountyIntel.BountyResult;
055import com.fs.starfarer.api.impl.campaign.intel.PersonBountyIntel.BountyResultType;
056import com.fs.starfarer.api.impl.campaign.intel.bar.PortsideBarData;
057import com.fs.starfarer.api.impl.campaign.intel.bar.events.PirateBaseRumorBarEvent;
058import com.fs.starfarer.api.impl.campaign.intel.deciv.DecivTracker;
059import com.fs.starfarer.api.impl.campaign.intel.events.PirateBasePirateActivityCause2;
060import com.fs.starfarer.api.impl.campaign.intel.raid.PirateRaidActionStage;
061import com.fs.starfarer.api.impl.campaign.intel.raid.PirateRaidAssembleStage;
062import com.fs.starfarer.api.impl.campaign.intel.raid.RaidIntel;
063import com.fs.starfarer.api.impl.campaign.intel.raid.RaidIntel.RaidDelegate;
064import com.fs.starfarer.api.impl.campaign.intel.raid.RaidIntel.RaidStageStatus;
065import com.fs.starfarer.api.impl.campaign.intel.raid.ReturnStage;
066import com.fs.starfarer.api.impl.campaign.intel.raid.TravelStage;
067import com.fs.starfarer.api.impl.campaign.procgen.MarkovNames;
068import com.fs.starfarer.api.impl.campaign.procgen.MarkovNames.MarkovNameResult;
069import com.fs.starfarer.api.impl.campaign.procgen.themes.BaseThemeGenerator;
070import com.fs.starfarer.api.impl.campaign.procgen.themes.BaseThemeGenerator.AddedEntity;
071import com.fs.starfarer.api.impl.campaign.procgen.themes.BaseThemeGenerator.EntityLocation;
072import com.fs.starfarer.api.impl.campaign.procgen.themes.BaseThemeGenerator.LocationType;
073import com.fs.starfarer.api.impl.campaign.rulecmd.HA_CMD;
074import com.fs.starfarer.api.ui.Alignment;
075import com.fs.starfarer.api.ui.LabelAPI;
076import com.fs.starfarer.api.ui.SectorMapAPI;
077import com.fs.starfarer.api.ui.TooltipMakerAPI;
078import com.fs.starfarer.api.util.IntervalUtil;
079import com.fs.starfarer.api.util.Misc;
080import com.fs.starfarer.api.util.WeightedRandomPicker;
081
082public class PirateBaseIntel extends BaseIntelPlugin implements EveryFrameScript, FleetEventListener,
083                                                                                                                                EconomyUpdateListener, RaidDelegate {
084        
085        public static final String PIRATE_BASE_COMMANDER = "$pirateBaseCommander";
086        public static final String HAS_DEAL_WITH_BASE_COMMANDER = "$playerHasDealWithPirateBaseCommander";
087        
088        public static String MEM_FLAG = "$core_pirateBase";
089        
090        public static PirateBaseIntel getIntelFor(StarSystemAPI system) {
091                for (IntelInfoPlugin intel : Global.getSector().getIntelManager().getIntel(PirateBaseIntel.class)) {
092                        if (((PirateBaseIntel)intel).getSystem() == system) {
093                                return (PirateBaseIntel) intel;
094                        }
095                }
096                return null;
097        }
098        public static PirateBaseIntel getIntelFor(SectorEntityToken station) {
099                for (IntelInfoPlugin intel : Global.getSector().getIntelManager().getIntel(PirateBaseIntel.class)) {
100                        if (((PirateBaseIntel)intel).getEntity() == station) {
101                                return (PirateBaseIntel) intel;
102                        }
103                }
104                return null;
105        }
106        
107        public static enum PirateBaseTier {
108                TIER_1_1MODULE,
109                TIER_2_1MODULE,
110                TIER_3_2MODULE,
111                TIER_4_3MODULE,
112                TIER_5_3MODULE,
113        }
114        
115        
116        public static Object DEAL_MADE_PARAM = new Object();
117        public static Object DEAL_BROKEN_PARAM = new Object();
118        public static Object DEAL_CANCELLED_PARAM = new Object();
119        
120        public static Object BOUNTY_EXPIRED_PARAM = new Object();
121        public static Object DISCOVERED_PARAM = new Object();
122        
123        public static class BaseBountyData {
124                public float bountyElapsedDays = 0f;
125                public float bountyDuration = 0;
126                public float baseBounty = 0;
127                public float repChange = 0;
128                public FactionAPI bountyFaction = null;
129        }
130        
131        public static Logger log = Global.getLogger(PirateBaseIntel.class);
132        
133        protected StarSystemAPI system;
134        protected MarketAPI market;
135        protected PersonAPI baseCommander;
136        protected SectorEntityToken entity;
137        
138        protected float elapsedDays = 0f;
139        protected float duration = 45f;
140        
141        protected BaseBountyData bountyData = null;
142        
143        protected PirateBaseTier tier;
144        protected PirateBaseTier matchedStationToTier = null;
145        
146        protected IntervalUtil monthlyInterval = new IntervalUtil(20f, 40f);
147        protected int raidTimeoutMonths = 0;
148        
149        public static PirateBaseIntel getIntelFor(MarketAPI market) {
150                for (IntelInfoPlugin p : Global.getSector().getIntelManager().getIntel(PirateBaseIntel.class)) {
151                        PirateBaseIntel intel = (PirateBaseIntel) p;
152                        if (intel.getMarket() == market) return intel;
153                }
154                return null;
155        }
156        
157        
158        public boolean playerHasDealWithBaseCommander() {
159                return baseCommander != null && baseCommander.getMemoryWithoutUpdate().getBoolean(HAS_DEAL_WITH_BASE_COMMANDER);
160        }
161        public void setPlayerHasDealWithBaseCommander(boolean hasDeal) {
162                if (baseCommander == null) return;
163                baseCommander.getMemoryWithoutUpdate().set(HAS_DEAL_WITH_BASE_COMMANDER, hasDeal);
164        }
165        
166        public PirateBaseIntel(StarSystemAPI system, String factionId, PirateBaseTier tier) {
167                this.system = system;
168                this.tier = tier;
169                
170                raidTimeoutMonths = Tuning.PIRATE_RAID_MIN_TIMEOUT_MONTHS + 
171                        Misc.random.nextInt(Tuning.PIRATE_RAID_MAX_TIMEOUT_MONTHS - Tuning.PIRATE_RAID_MIN_TIMEOUT_MONTHS + 1);
172        
173                market = Global.getFactory().createMarket(Misc.genUID(), "Pirate Base", 3);
174                market.setSize(3);
175                market.setHidden(true);
176                market.getMemoryWithoutUpdate().set(MEM_FLAG, true);
177                market.getMemoryWithoutUpdate().set(MemFlags.HIDDEN_BASE_MEM_FLAG, true);
178                //market.getMemoryWithoutUpdate().set(ContactIntel.NO_CONTACTS_ON_MARKET, true);
179                
180                market.setFactionId(Factions.PIRATES);
181                
182                market.setSurveyLevel(SurveyLevel.FULL);
183                
184                market.setFactionId(factionId);
185                market.addCondition(Conditions.POPULATION_3);
186                
187                market.addIndustry(Industries.POPULATION);
188                market.addIndustry(Industries.SPACEPORT);
189                market.addIndustry(Industries.MILITARYBASE);
190                
191                market.addSubmarket(Submarkets.SUBMARKET_OPEN);
192                market.addSubmarket(Submarkets.SUBMARKET_BLACK);
193                
194                market.getTariff().modifyFlat("default_tariff", market.getFaction().getTariffFraction());
195                
196                LinkedHashMap<LocationType, Float> weights = new LinkedHashMap<LocationType, Float>();
197                weights.put(LocationType.IN_ASTEROID_BELT, 10f);
198                weights.put(LocationType.IN_ASTEROID_FIELD, 10f);
199                weights.put(LocationType.IN_RING, 10f);
200                weights.put(LocationType.IN_SMALL_NEBULA, 10f);
201                weights.put(LocationType.GAS_GIANT_ORBIT, 10f);
202                weights.put(LocationType.PLANET_ORBIT, 10f);
203                WeightedRandomPicker<EntityLocation> locs = BaseThemeGenerator.getLocations(null, system, null, 100f, weights);
204                EntityLocation loc = locs.pick();
205                
206                if (loc == null) {
207                        endImmediately();
208                        return;
209                }
210                
211                AddedEntity added = BaseThemeGenerator.addNonSalvageEntity(system, loc, Entities.MAKESHIFT_STATION, factionId);
212                
213                if (added == null || added.entity == null) {
214                        endImmediately();
215                        return;
216                }
217                
218                entity = added.entity;
219                
220                
221                String name = generateName();
222                if (name == null) {
223                        endImmediately();
224                        return;
225                }
226                
227                market.setName(name);
228                entity.setName(name);
229                
230                
231//              boolean down = false;
232//              if (entity.getOrbitFocus() instanceof PlanetAPI) {
233//                      PlanetAPI planet = (PlanetAPI) entity.getOrbitFocus();
234//                      if (!planet.isStar()) {
235//                              down = true;
236//                      }
237//              }
238//              if (down) {
239//                      BaseThemeGenerator.convertOrbitPointingDown(entity);
240//              }
241                BaseThemeGenerator.convertOrbitWithSpin(entity, -5f);
242                
243                market.setPrimaryEntity(entity);
244                entity.setMarket(market);
245                
246                entity.setSensorProfile(1f);
247                entity.setDiscoverable(true);
248                entity.getDetectedRangeMod().modifyFlat("gen", 5000f);
249                
250                market.setEconGroup(market.getId());
251                market.getMemoryWithoutUpdate().set(DecivTracker.NO_DECIV_KEY, true);
252                
253                market.reapplyIndustries();
254                
255                Global.getSector().getEconomy().addMarket(market, true);
256
257                baseCommander = initBaseCommander();
258                
259                log.info(String.format("Added pirate base in [%s], tier: %s", system.getName(), tier.name()));
260                
261                Global.getSector().getIntelManager().addIntel(this, true);
262                timestamp = null;
263                
264                Global.getSector().getListenerManager().addListener(this);
265                Global.getSector().getEconomy().addUpdateListener(this);
266                
267                updateTarget();
268                
269//              if ((float) Math.random() > 0.067f) {
270//                      SectorEntityToken raidDest = Global.getSector().getEconomy().getMarket("yama").getPrimaryEntity();
271//                      startRaid(raidDest.getStarSystem(), getRaidFP());
272//              }
273                
274                PortsideBarData.getInstance().addEvent(new PirateBaseRumorBarEvent(this));
275        }
276        
277        private PersonAPI initBaseCommander() {
278                baseCommander = market.getFaction().createRandomPerson(Misc.random);
279                baseCommander.setRankId(Ranks.SPACE_CAPTAIN);
280                baseCommander.setPostId(Ranks.POST_STATION_COMMANDER);
281                baseCommander.setImportanceAndVoice(PersonImportance.HIGH, Misc.random);
282                baseCommander.addTag(Tags.CONTACT_UNDERWORLD);
283                baseCommander.getMemoryWithoutUpdate().set(PIRATE_BASE_COMMANDER, true);
284                market.getCommDirectory().addPerson(baseCommander);
285                return baseCommander;
286        }
287        @Override
288        public boolean isHidden() {
289                if (super.isHidden()) return true;
290                //if (true) return false;
291                return timestamp == null;
292        }
293
294
295        public float getRaidFP() {
296                float base = getBaseRaidFP();
297                return base * (0.75f + (float) Math.random() * 0.5f);
298        }
299        public float getBaseRaidFP() {
300                float base = 100f;
301                switch (tier) {
302                case TIER_1_1MODULE: base = 100f; break;
303                case TIER_2_1MODULE: base = 150f; break;
304                case TIER_3_2MODULE: base = 250f; break;
305                case TIER_4_3MODULE: base = 300f; break;
306                case TIER_5_3MODULE: base = 450f; break;
307                }
308                return base * (0.75f + (float) Math.random() * 0.5f);
309        }
310        
311        public void notifyRaidEnded(RaidIntel raid, RaidStageStatus status) {
312                if (status == RaidStageStatus.SUCCESS) {
313                        //raidTimeoutMonths = 0;
314                        raidTimeoutMonths = Math.max(raidTimeoutMonths, Tuning.PIRATE_RAID_MIN_TIMEOUT_MONTHS + 
315                                Misc.random.nextInt(Tuning.PIRATE_RAID_MAX_TIMEOUT_MONTHS - Tuning.PIRATE_RAID_MIN_TIMEOUT_MONTHS + 1));
316                } else {
317                        float base = getBaseRaidFP();
318                        float raidFP = raid.getAssembleStage().getOrigSpawnFP();
319                        raidTimeoutMonths += Math.min(Math.round(raidFP / base) * 2, Tuning.PIRATE_RAID_DEFEATED_TIMEOUT_MONTHS);
320                }
321        }
322        
323        public void startRaid(StarSystemAPI target, float raidFP) {
324                // if piracy respite: no raids against systems with player colonies
325                //if (PiracyRespiteScript.get() != null && target != null &&
326                // actually just no raids against the player, period - that's handled by Colony Crises
327                if (target != null && 
328                                !Misc.getMarketsInLocation(target, Factions.PLAYER).isEmpty()) {
329                        return;
330                }
331                
332                boolean hasTargets = false;
333                for (MarketAPI curr : Misc.getMarketsInLocation(target)) {
334                        if (curr.getFaction().isHostileTo(getFactionForUIColors())) {
335                                hasTargets = true;
336                                break;
337                        }
338                }
339                
340                if (!hasTargets) return;
341                
342                RaidIntel raid = new RaidIntel(target, getFactionForUIColors(), this);
343                
344                //float raidFP = 1000;
345                float successMult = 0.5f;
346                
347                JumpPointAPI gather = null;
348                List<JumpPointAPI> points = system.getEntities(JumpPointAPI.class);
349                float min = Float.MAX_VALUE;
350                for (JumpPointAPI curr : points) {
351                        float dist = Misc.getDistance(entity.getLocation(), curr.getLocation());
352                        if (dist < min) {
353                                min = dist;
354                                gather = curr;
355                        }
356                }
357                
358                SectorEntityToken raidJump = RouteLocationCalculator.findJumpPointToUse(getFactionForUIColors(), target.getCenter());
359                if (gather == null || raidJump == null) return;
360                
361                PirateRaidAssembleStage assemble = new PirateRaidAssembleStage(raid, gather, this);
362                assemble.addSource(market);
363                assemble.setSpawnFP(raidFP);
364                assemble.setAbortFP(raidFP * successMult);
365                raid.addStage(assemble);
366                
367                TravelStage travel = new TravelStage(raid, gather, raidJump, false);
368                travel.setAbortFP(raidFP * successMult);
369                raid.addStage(travel);
370                
371                PirateRaidActionStage action = new PirateRaidActionStage(raid, target);
372                action.setAbortFP(raidFP * successMult);
373                raid.addStage(action);
374                
375                raid.addStage(new ReturnStage(raid));
376                
377                boolean shouldNotify = raid.shouldSendUpdate();
378                Global.getSector().getIntelManager().addIntel(raid, !shouldNotify);
379//              if (!Misc.getMarketsInLocation(target, Factions.PLAYER).isEmpty() || true) {
380//                      Global.getSector().getIntelManager().addIntel(raid);
381//              } else {
382//                      Global.getSector().getIntelManager().queueIntel(raid);
383//              }
384        }
385        
386        public StarSystemAPI getSystem() {
387                return system;
388        }
389
390        protected String pickStationType() {
391                WeightedRandomPicker<String> stations = new WeightedRandomPicker<String>();
392                
393                if (getFactionForUIColors().getCustom().has(Factions.CUSTOM_PIRATE_BASE_STATION_TYPES)) {
394                        try {
395                                JSONObject json = getFactionForUIColors().getCustom().getJSONObject(Factions.CUSTOM_PIRATE_BASE_STATION_TYPES);
396                                for (String key : JSONObject.getNames(json)) {
397                                        stations.add(key, (float) json.optDouble(key, 0f));
398                                }
399                        } catch (JSONException e) {
400                                stations.clear();
401                        }
402                }
403                
404                if (stations.isEmpty()) {
405                        stations.add(Industries.ORBITALSTATION, 5f);
406                        stations.add(Industries.ORBITALSTATION_MID, 3f);
407                        stations.add(Industries.ORBITALSTATION_HIGH, 1f);
408                }
409                
410                //stations.add(Industries.STARFORTRESS, 100000f);
411                return stations.pick();
412        }
413        
414        protected Industry getStationIndustry() {
415                for (Industry curr : market.getIndustries()) {
416                        if (curr.getSpec().hasTag(Industries.TAG_STATION)) {
417                                return curr;
418                        }
419                }
420                return null;
421        }
422        
423        protected void updateStationIfNeeded() {
424                if (matchedStationToTier == tier) return;
425                
426                matchedStationToTier = tier;
427                monthsAtCurrentTier = 0;
428                
429                Industry stationInd = getStationIndustry();
430                
431                String currIndId = null;
432                if (stationInd != null) {
433                        currIndId = stationInd.getId();
434                        market.removeIndustry(stationInd.getId(), null, false);
435                        stationInd = null;
436                }
437                
438                if (currIndId == null) {
439                        currIndId = pickStationType();
440                }
441                
442                if (currIndId == null) return;
443                
444                market.addIndustry(currIndId);
445                stationInd = getStationIndustry();
446                if (stationInd == null) return;
447                
448                stationInd.finishBuildingOrUpgrading();
449                
450
451                CampaignFleetAPI fleet = Misc.getStationFleet(entity);
452                if (fleet == null) return;
453                
454                List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy();
455                if (members.size() < 1) return;
456                
457                fleet.inflateIfNeeded();
458                
459                FleetMemberAPI station = members.get(0);
460                
461                WeightedRandomPicker<Integer> picker = new WeightedRandomPicker<Integer>();
462                int index = 1; // index 0 is station body
463                for (String slotId : station.getVariant().getModuleSlots()) {
464                        ShipVariantAPI mv = station.getVariant().getModuleVariant(slotId);
465                        if (Misc.isActiveModule(mv)) {
466                                picker.add(index, 1f);
467                        }
468                        index++;
469                }
470                
471                float removeMult = 0f;
472                
473                switch (tier) {
474                case TIER_1_1MODULE:
475                case TIER_2_1MODULE:
476                        removeMult = 0.67f;
477                        break;
478                case TIER_3_2MODULE:
479                        removeMult = 0.33f;
480                        break;
481                case TIER_4_3MODULE:
482                case TIER_5_3MODULE:
483                        removeMult = 0;
484                        break;
485                }
486                
487                int remove = Math.round(picker.getItems().size() * removeMult);
488                if (remove < 1 && removeMult > 0) remove = 1;
489                
490                // one-module bases are a bit too boring to fight
491                if (remove >= picker.getItems().size() - 1) {
492                        remove = picker.getItems().size() - 2;
493                }
494                
495                for (int i = 0; i < remove; i++) {
496                        Integer pick = picker.pickAndRemove();
497                        if (pick != null) {
498                                station.getStatus().setHullFraction(pick, 0f);
499                                station.getStatus().setDetached(pick, true);
500                                station.getStatus().setPermaDetached(pick, true);
501                        }
502                }
503                
504                if (baseCommander != null) {
505                        if (tier == PirateBaseTier.TIER_5_3MODULE || tier == PirateBaseTier.TIER_4_3MODULE) {
506                                baseCommander.setImportance(PersonImportance.VERY_HIGH);
507                        } else {
508                                baseCommander.setImportance(PersonImportance.HIGH);
509                        }
510                }
511        }
512        
513        public CampaignFleetAPI getAddedListenerTo() {
514                return addedListenerTo;
515        }
516
517
518
519        protected CampaignFleetAPI addedListenerTo = null;
520        @Override
521        protected void advanceImpl(float amount) {
522                //makeKnown();
523                float days = Global.getSector().getClock().convertToDays(amount);
524                //days *= 1000f;
525                //Global.getSector().getCurrentLocation().getName()
526                //entity.getContainingLocation().getName()
527                if (getPlayerVisibleTimestamp() == null && entity.isInCurrentLocation() && isHidden()) {
528                        makeKnown();
529                        sendUpdateIfPlayerHasIntel(DISCOVERED_PARAM, false);
530                }
531                
532                
533                //System.out.println("Name: " + market.getName());
534                
535                if (!sentBountyUpdate && bountyData != null && 
536                                (Global.getSector().getIntelManager().isPlayerInRangeOfCommRelay() ||
537                                                (!isHidden() && DebugFlags.SEND_UPDATES_WHEN_NO_COMM))) {
538                        makeKnown();
539                        sendUpdateIfPlayerHasIntel(bountyData, false);
540                        sentBountyUpdate = true;
541                }
542                
543                CampaignFleetAPI fleet = Misc.getStationFleet(market);
544                if (fleet != null && addedListenerTo != fleet) {
545                        if (addedListenerTo != null) {
546                                addedListenerTo.removeEventListener(this);
547                        }
548                        fleet.addEventListener(this);
549                        addedListenerTo = fleet;                        
550                }
551                
552                
553                if (target != null) {
554                        if (getAffectedMarkets(target).isEmpty()) {
555                                clearTarget();
556                        }
557                }
558                
559                if (DebugFlags.RAID_DEBUG) {
560                        days *= 100f;
561                }
562                
563                monthlyInterval.advance(days);
564                if (monthlyInterval.intervalElapsed()) {
565//                      if (targetPlayerColonies) {
566//                              System.out.println("wefwefwe");
567//                      }
568                        monthsWithSameTarget++;
569                        raidTimeoutMonths--;
570                        if (raidTimeoutMonths < 0) raidTimeoutMonths = 0;
571                        
572                        if ((monthsWithSameTarget > 6 && (float) Math.random() < 0.2f) || target == null) {
573                                updateTarget();
574                        }
575                        if (target != null && 
576                                        (float) Math.random() < monthsWithSameTarget * 0.05f && 
577                                        bountyData == null) {
578                                setBounty();
579                        }
580                        //if (target != null && (float) Math.random() < 0.2f && raidTimeoutMonths <= 0) {
581                        boolean allowRandomRaids = PirateBaseManager.getInstance().getDaysSinceStart() > Tuning.NO_PIRATE_RAID_DAYS_FROM_GAME_START;
582//                      if (target != null && !Misc.getMarketsInLocation(target).isEmpty() && 
583//                                      Misc.getMarketsInLocation(target).get(0).isPlayerOwned()) {
584//                              System.out.println("wefwefew");
585//                      }
586                        if (target != null && 
587                                        (((float) Math.random() < 0.2f && allowRandomRaids) || 
588                                                        targetPlayerColoniesOnly) && raidTimeoutMonths <= 0) {
589                                startRaid(target, getRaidFP());
590                                raidTimeoutMonths = Tuning.PIRATE_RAID_MIN_TIMEOUT_MONTHS + 
591                                                Misc.random.nextInt(Tuning.PIRATE_RAID_MAX_TIMEOUT_MONTHS - Tuning.PIRATE_RAID_MIN_TIMEOUT_MONTHS + 1);
592//                              raidTimeoutMonths = Tuning.PIRATE_RAID_MIN_TIMEOUT_MONTHS + 
593//                                              (int)Math.round((float) Math.random() * (Tuning.PIRATE_RAID_MIN_TIMEOUT_MONTHS=_TIMEOUT_MONTHSf);
594                        }
595                        
596                        checkForTierChange();
597                }
598
599//              if (bountyData == null && target != null) {
600//                      setBounty();
601//              }
602                
603                if (bountyData != null) {
604                        boolean canEndBounty = !entity.isInCurrentLocation();
605                        bountyData.bountyElapsedDays += days;
606                        if (bountyData.bountyElapsedDays > bountyData.bountyDuration && canEndBounty) {
607                                endBounty();
608                        }
609                }
610                
611                //elapsedDays += days;
612//              if (elapsedDays >= duration && !isDone()) {
613//                      endAfterDelay();
614//                      boolean current = market.getContainingLocation() == Global.getSector().getCurrentLocation();
615//                      sendUpdateIfPlayerHasIntel(new Object(), !current);
616//                      return;
617//              }
618                
619                updateStationIfNeeded();
620        }
621        
622        protected void checkForTierChange() {
623                if (bountyData != null) return;
624                if (entity.isInCurrentLocation()) return;
625                
626                float minMonths = Global.getSettings().getFloat("pirateBaseMinMonthsForNextTier");
627                if (monthsAtCurrentTier > minMonths) {
628                        float prob = (monthsAtCurrentTier - minMonths) * 0.1f;
629                        if ((float) Math.random() < prob) {
630                                PirateBaseTier next = getNextTier(tier);
631                                if (next != null) {
632                                        tier = next;
633                                        updateStationIfNeeded();
634                                        monthsAtCurrentTier = 0;
635                                        return;
636                                }
637                        }
638                }
639                
640                monthsAtCurrentTier++;
641        }
642        
643        protected PirateBaseTier getNextTier(PirateBaseTier tier) {
644                switch (tier) {
645                case TIER_1_1MODULE: return PirateBaseTier.TIER_2_1MODULE;
646                case TIER_2_1MODULE: return PirateBaseTier.TIER_3_2MODULE;
647                case TIER_3_2MODULE: return PirateBaseTier.TIER_4_3MODULE;
648                case TIER_4_3MODULE: return PirateBaseTier.TIER_5_3MODULE;
649                case TIER_5_3MODULE: return null;
650                }
651                return null;
652        }
653        
654        protected PirateBaseTier getPrevTier(PirateBaseTier tier) {
655                switch (tier) {
656                case TIER_1_1MODULE: return null;
657                case TIER_2_1MODULE: return PirateBaseTier.TIER_1_1MODULE;
658                case TIER_3_2MODULE: return PirateBaseTier.TIER_2_1MODULE;
659                case TIER_4_3MODULE: return PirateBaseTier.TIER_3_2MODULE;
660                case TIER_5_3MODULE: return PirateBaseTier.TIER_4_3MODULE;
661                }
662                return null;
663        }
664
665        public void makeKnown() {
666                makeKnown(null);
667        }
668        public void makeKnown(TextPanelAPI text) {
669//              entity.setDiscoverable(null);
670//              entity.setSensorProfile(null);
671//              entity.getDetectedRangeMod().unmodify("gen");
672                
673                if (getPlayerVisibleTimestamp() == null) {
674                        Global.getSector().getIntelManager().removeIntel(this);
675                        Global.getSector().getIntelManager().addIntel(this, text == null, text);
676                }
677        }
678        
679        public float getTimeRemainingFraction() {
680                float f = 1f - elapsedDays / duration;
681                return f;
682        }
683        
684        
685
686        @Override
687        protected void notifyEnding() {
688                super.notifyEnding();
689                log.info(String.format("Removing pirate base at [%s]", system.getName()));
690                Global.getSector().getListenerManager().removeListener(this);
691                clearTarget();
692                
693                Global.getSector().getEconomy().removeMarket(market);
694                Global.getSector().getEconomy().removeUpdateListener(this);
695                Misc.removeRadioChatter(market);
696                market.advance(0f);
697        }
698        
699        @Override
700        protected void notifyEnded() {
701                super.notifyEnded();
702        }
703
704
705
706        protected BountyResult result = null;
707        public void reportFleetDespawnedToListener(CampaignFleetAPI fleet, FleetDespawnReason reason, Object param) {
708                if (isEnding()) return;
709                
710                //CampaignFleetAPI station = Misc.getStationFleet(market); // null here since it's the skeleton station at this point
711                if (addedListenerTo != null && fleet == addedListenerTo) {
712                        Misc.fadeAndExpire(entity);
713                        endAfterDelay();
714                        
715                        result = new BountyResult(BountyResultType.END_OTHER, 0, null);
716                        
717                        if (reason == FleetDespawnReason.DESTROYED_BY_BATTLE && 
718                                        param instanceof BattleAPI) {
719                                BattleAPI battle = (BattleAPI) param;
720                                if (battle.isPlayerInvolved()) {
721                                        int payment = 0;
722                                        if (bountyData != null) {
723                                                payment = (int) (bountyData.baseBounty * battle.getPlayerInvolvementFraction());
724                                        }
725                                        if (payment > 0) {
726                                                Global.getSector().getPlayerFleet().getCargo().getCredits().add(payment);
727                                                
728                                                CustomRepImpact impact = new CustomRepImpact();
729                                                impact.delta = bountyData.repChange * battle.getPlayerInvolvementFraction();
730                                                if (impact.delta < 0.01f) impact.delta = 0.01f;
731                                                ReputationAdjustmentResult rep = Global.getSector().adjustPlayerReputation(
732                                                                new RepActionEnvelope(RepActions.CUSTOM, 
733                                                                                impact, null, null, false, true),
734                                                                                bountyData.bountyFaction.getId());
735                                                
736                                                result = new BountyResult(BountyResultType.END_PLAYER_BOUNTY, payment, rep);
737                                        } else {
738                                                result = new BountyResult(BountyResultType.END_PLAYER_NO_REWARD, 0, null);
739                                        }
740                                }
741                        }
742                        
743                        boolean sendUpdate = DebugFlags.SEND_UPDATES_WHEN_NO_COMM ||
744                                                                 result.type != BountyResultType.END_OTHER ||
745                                                                 Global.getSector().getIntelManager().isPlayerInRangeOfCommRelay();
746                        sendUpdate = true;
747                        if (sendUpdate) {
748                                sendUpdateIfPlayerHasIntel(result, false);
749                        }
750                        
751                        PirateBaseManager.getInstance().incrDestroyed();
752                        PirateBaseManager.markRecentlyUsedForBase(system);
753                }
754        }
755
756        public void reportBattleOccurred(CampaignFleetAPI fleet, CampaignFleetAPI primaryWinner, BattleAPI battle) {
757                
758        }
759        
760        public boolean runWhilePaused() {
761                return false;
762        }
763        protected void addBulletPoints(TooltipMakerAPI info, ListInfoMode mode) {
764                
765                Color h = Misc.getHighlightColor();
766                Color g = Misc.getGrayColor();
767                float pad = 3f;
768                float opad = 10f;
769                
770                float initPad = pad;
771                if (mode == ListInfoMode.IN_DESC) initPad = opad;
772                
773                Color tc = getBulletColorForMode(mode);
774                
775                bullet(info);
776                boolean isUpdate = getListInfoParam() != null;
777                
778                if (getListInfoParam() == DEAL_BROKEN_PARAM) {
779                        info.addPara("Agreement broken", initPad, tc, 
780                                        Misc.getNegativeHighlightColor(), "Agreement broken");
781                        initPad = 0f;
782                } else if (getListInfoParam() == DEAL_CANCELLED_PARAM) {
783                        info.addPara("Agreement dissolved", tc, initPad);
784                        initPad = 0f;
785                } else if (mode == ListInfoMode.INTEL || getListInfoParam() == DEAL_MADE_PARAM) {
786                        if (playerHasDealWithBaseCommander()) {
787                                info.addPara("Agreement made with base commander", initPad, tc, 
788                                                Misc.getPositiveHighlightColor(), "Agreement");
789                                initPad = 0f;
790                        }
791                }
792                
793                if (bountyData != null && result == null) {
794                        if (getListInfoParam() != BOUNTY_EXPIRED_PARAM) {
795                                if (isUpdate || mode != ListInfoMode.IN_DESC) {
796                                        FactionAPI faction = bountyData.bountyFaction;
797                                        info.addPara("Bounty faction: " + faction.getDisplayName(), initPad, tc,
798                                                        faction.getBaseUIColor(), faction.getDisplayName());
799                                        initPad = 0f;
800                                }
801                                info.addPara("%s reward", initPad, tc, h, Misc.getDGSCredits(bountyData.baseBounty));
802                                addDays(info, "remaining", bountyData.bountyDuration - bountyData.bountyElapsedDays, tc);
803                        }
804                }
805                
806                if (result != null && bountyData != null) {
807                        switch (result.type) {
808                        case END_PLAYER_BOUNTY:
809                                info.addPara("%s received", initPad, tc, h, Misc.getDGSCredits(result.payment));
810                                CoreReputationPlugin.addAdjustmentMessage(result.rep.delta, bountyData.bountyFaction, null, 
811                                                null, null, info, tc, isUpdate, 0f);
812                                break;
813                        case END_TIME:
814                                break;
815                        case END_OTHER:
816                                break;
817                        
818                        }
819                }
820
821                unindent(info);
822        }
823        
824        @Override
825        public void createIntelInfo(TooltipMakerAPI info, ListInfoMode mode) {
826                Color c = getTitleColor(mode);
827                info.addPara(getName(), c, 0f);
828                addBulletPoints(info, mode);
829        }
830        
831        public String getSortString() {
832                if (getTagsForSort().contains(Tags.INTEL_FLEET_LOG) || getTagsForSort().contains(Tags.INTEL_EXPLORATION)) {
833                        return getSortStringNewestFirst();
834                }
835                String base = Misc.ucFirst(getFactionForUIColors().getPersonNamePrefix());
836                return base + " Base";
837                //return "Pirate Base";
838        }
839        
840        public String getName() {
841                String base = Misc.ucFirst(getFactionForUIColors().getPersonNamePrefix());
842                
843                if (getListInfoParam() == bountyData && bountyData != null) {
844                        return base + " Base - Bounty Posted";
845                } else if (getListInfoParam() == BOUNTY_EXPIRED_PARAM) {
846                        return base + " Base - Bounty Expired";
847                }
848                
849                if (result != null) {
850                        if (result.type == BountyResultType.END_PLAYER_BOUNTY) {
851                                return base + " Base - Bounty Completed";
852                        } else if (result.type == BountyResultType.END_PLAYER_NO_REWARD) {
853                                return base + " Base - Destroyed";
854                        }
855                }
856                
857                String name = market.getName();
858                if (isEnding()) {
859                        //return "Base Abandoned - " + name;
860                        return base + " Base - Abandoned";
861                }
862                if (getListInfoParam() == DISCOVERED_PARAM) {
863                        return base + " Base - Discovered";
864                }
865                if (entity.isDiscoverable()) {
866                        return base + " Base - Exact Location Unknown";
867                }
868                return base + " Base - " + name;
869        }
870        
871        @Override
872        public FactionAPI getFactionForUIColors() {
873                return market.getFaction();
874        }
875
876        public String getSmallDescriptionTitle() {
877                return getName();
878        }
879        
880        public void createSmallDescription(TooltipMakerAPI info, float width, float height) {
881                
882                Color h = Misc.getHighlightColor();
883                Color g = Misc.getGrayColor();
884                Color tc = Misc.getTextColor();
885                float pad = 3f;
886                float opad = 10f;
887
888                //info.addPara(getName(), c, 0f);
889                
890                //info.addSectionHeading(getName(), Alignment.MID, 0f);
891                
892                FactionAPI faction = market.getFaction();
893                
894                info.addImage(faction.getLogo(), width, 128, opad);
895                
896                String has = faction.getDisplayNameHasOrHave();
897                
898                info.addPara(Misc.ucFirst(faction.getDisplayNameWithArticle()) + " " + has + 
899                                " established a base in the " + 
900                                market.getContainingLocation().getNameWithLowercaseType() + ". " +
901                                                "The base serves as a staging ground for raids against nearby colonies.",
902                                opad, faction.getBaseUIColor(), faction.getDisplayNameWithArticleWithoutArticle());
903                
904                if (!entity.isDiscoverable()) {
905                        switch (tier) {
906                        case TIER_1_1MODULE:
907                                info.addPara("It has limited defensive capabilities " +
908                                                        "and is protected by a few fleets.", opad);
909                                break;
910                        case TIER_2_1MODULE:
911                                info.addPara("It has limited defensive capabilities " +
912                                                        "and is protected by a small number of fleets.", opad);
913                                break;
914                        case TIER_3_2MODULE:
915                                info.addPara("It has fairly well-developed defensive capabilities " +
916                                                         "and is protected by a considerable number of fleets.", opad);
917                                break;
918                        case TIER_4_3MODULE:
919                                info.addPara("It has very well-developed defensive capabilities " +
920                                                         "and is protected by a large number of fleets.", opad);
921                                break;
922                        case TIER_5_3MODULE:
923                                info.addPara("It has very well-developed defensive capabilities " +
924                                                         "and is protected by a large number of fleets. Both the " +
925                                                         "base and the fleets have elite-level equipment, at least by pirate standards.", opad);
926                                break;
927                        
928                        }
929                } else {
930                        info.addPara("You have not yet discovered the exact location or capabilities of this base.", opad);
931                }
932                
933                boolean deal = playerHasDealWithBaseCommander();
934                if (deal) {
935//                      FactionAPI pf = Global.getSector().getPlayerFaction();
936//                      info.addSectionHeading("Protection payments", 
937//                                      pf.getBaseUIColor(), pf.getDarkUIColor(), Alignment.MID, opad);
938                        float feeFraction = Global.getSettings().getFloat("pirateProtectionPaymentFraction");
939                        String fee = Misc.getDGSCredits(HA_CMD.computePirateProtectionPaymentPerMonth(this));
940                        LabelAPI label = info.addPara("You have an %s with "
941                                        + "the base commander, and fleets from this base do not, as a rule, "
942                                        + "harass your colonies or shipping. The protection payment is %s of "
943                                        + "the gross income of all of your affected colonies, which "
944                                        + "amounts to %s per month at their current level of income.", opad, 
945                                        Misc.getPositiveHighlightColor(),
946                                        "agreement",
947                                        "" + (int)Math.round(feeFraction * 100f) + "%", fee
948                                        );
949                        label.setHighlightColors(Misc.getPositiveHighlightColor(), h, h);
950                        label.setHighlight("agreement", "" + (int)Math.round(feeFraction * 100f) + "%", fee);
951                }
952                
953                        
954                info.addSectionHeading("Recent events", 
955                                                           faction.getBaseUIColor(), faction.getDarkUIColor(), Alignment.MID, opad);
956                        
957                if (target != null && !getAffectedMarkets(target).isEmpty() && !isEnding()) {
958                        info.addPara("Pirates operating from this base have been targeting the " + 
959                                        target.getNameWithLowercaseType() + ".", opad); 
960                }
961                
962                if (bountyData != null) {
963                        info.addPara(Misc.ucFirst(bountyData.bountyFaction.getDisplayNameWithArticle()) + " " +
964                                        bountyData.bountyFaction.getDisplayNameHasOrHave() + 
965                                        " posted a bounty for the destruction of this base.",
966                                        opad, bountyData.bountyFaction.getBaseUIColor(), 
967                                        bountyData.bountyFaction.getDisplayNameWithArticleWithoutArticle());
968                        
969                        if (result != null && result.type == BountyResultType.END_PLAYER_BOUNTY) {
970                                info.addPara("You have successfully completed this bounty.", opad);
971                        }
972                        
973                        addBulletPoints(info, ListInfoMode.IN_DESC);
974                }
975                
976                if (result != null) {
977                        if (result.type == BountyResultType.END_PLAYER_NO_REWARD) {
978                                info.addPara("You have destroyed this base.", opad);                            
979                        } else if (result.type == BountyResultType.END_OTHER) {
980                                info.addPara("It is rumored that this base is no longer operational.", opad);                           
981                        }
982                }
983
984        }
985        
986        public String getIcon() {
987                return Global.getSettings().getSpriteName("intel", "pirate_base");
988                //return market.getFaction().getCrest();
989        }
990        
991        public Set<String> getIntelTags(SectorMapAPI map) {
992                Set<String> tags = super.getIntelTags(map);
993                if (bountyData != null) {
994                        tags.add(Tags.INTEL_BOUNTY);
995                }
996                tags.add(Tags.INTEL_EXPLORATION);
997                
998                // old way, when pirate activity applied to player colonies
999                // not needed anymore but shouldn't hurt
1000                // aaand, needed again since HA is now "Colony Crises" and works differently
1001                if (target != null && !Misc.getMarketsInLocation(target, Factions.PLAYER).isEmpty()) {
1002                        tags.add(Tags.INTEL_COLONIES);
1003                }
1004                
1005                // new hostile activity
1006                if (!PirateBasePirateActivityCause2.getColoniesAffectedBy(this).isEmpty()) {
1007                        tags.add(Tags.INTEL_COLONIES);
1008                }
1009                
1010                tags.add(market.getFactionId());
1011                if (bountyData != null) {
1012                        tags.add(bountyData.bountyFaction.getId());
1013                }
1014                return tags;
1015        }
1016
1017        @Override
1018        public SectorEntityToken getMapLocation(SectorMapAPI map) {
1019                //return market.getPrimaryEntity();
1020                if (market.getPrimaryEntity().isDiscoverable()) {
1021                        return system.getCenter();
1022                }
1023                return market.getPrimaryEntity();
1024        }
1025
1026
1027        
1028        
1029        
1030        protected String generateName() {
1031                MarkovNames.loadIfNeeded();
1032                
1033                MarkovNameResult gen = null;
1034                for (int i = 0; i < 10; i++) {
1035                        gen = MarkovNames.generate(null);
1036                        if (gen != null) {
1037                                String test = gen.name;
1038                                if (test.toLowerCase().startsWith("the ")) continue;
1039                                String p = pickPostfix();
1040                                if (p != null && !p.isEmpty()) {
1041                                        test += " " + p;
1042                                }
1043                                if (test.length() > 22) continue;
1044                                
1045                                return test;
1046                        }
1047                }
1048                return null;
1049        }
1050        
1051        protected String pickPostfix() {
1052                WeightedRandomPicker<String> post = new WeightedRandomPicker<String>();
1053                post.add("Asylum");
1054                post.add("Astrome");
1055                post.add("Barrage");
1056                post.add("Briganderie");
1057                post.add("Camp");
1058                post.add("Cover");
1059                post.add("Citadel");
1060                post.add("Den");
1061                post.add("Donjon");
1062                post.add("Depot");
1063                post.add("Fort");
1064                post.add("Freehold");
1065                post.add("Freeport");
1066                post.add("Freehaven");
1067                post.add("Free Orbit");
1068                post.add("Galastat");
1069                post.add("Garrison");
1070                post.add("Harbor");
1071                post.add("Haven");
1072                post.add("Headquarters");
1073                post.add("Hideout");
1074                post.add("Hideaway");
1075                post.add("Hold");
1076                post.add("Lair");
1077                post.add("Locus");
1078                post.add("Main");
1079                post.add("Mine Depot");
1080                post.add("Nexus");
1081                post.add("Orbit");
1082                post.add("Port");
1083                post.add("Post");
1084                post.add("Presidio");
1085                post.add("Prison");
1086                post.add("Platform");
1087                post.add("Corsairie");
1088                post.add("Refuge");
1089                post.add("Retreat");
1090                post.add("Refinery");
1091                post.add("Shadow");
1092                post.add("Safehold");
1093                post.add("Starhold");
1094                post.add("Starport");
1095                post.add("Stardock");
1096                post.add("Sanctuary");
1097                post.add("Station");
1098                post.add("Spacedock");
1099                post.add("Tertiary");
1100                post.add("Terminus");
1101                post.add("Terminal");
1102                post.add("Tortuga");
1103                post.add("Ward");
1104                post.add("Warsat");
1105                return post.pick();
1106        }
1107
1108        public void commodityUpdated(String commodityId) {
1109                CommodityOnMarketAPI com = market.getCommodityData(commodityId);
1110                int curr = 0;
1111                String modId = market.getId();
1112                StatMod mod = com.getAvailableStat().getFlatStatMod(modId);
1113                if (mod != null) {
1114                        curr = Math.round(mod.value);
1115                }
1116                
1117                int avWithoutPenalties = (int) Math.round(com.getAvailableStat().getBaseValue());
1118                for (StatMod m : com.getAvailableStat().getFlatMods().values()) {
1119                        if (m.value < 0) continue;
1120                        avWithoutPenalties += (int) Math.round(m.value);
1121                }
1122                
1123                int a = com.getAvailable() - curr;
1124                a = avWithoutPenalties - curr;
1125                int d = com.getMaxDemand();
1126                if (d > a) {
1127                        //int supply = Math.max(1, d - a - 1);
1128                        int supply = Math.max(1, d - a);
1129                        com.getAvailableStat().modifyFlat(modId, supply, "Brought in by raiders");
1130                }
1131        }
1132
1133        public void economyUpdated() {
1134
1135                float fleetSizeBonus = 1f;
1136                float qualityBonus = 0f;
1137                int light = 0;
1138                int medium = 0;
1139                int heavy = 0;
1140                
1141                switch (tier) {
1142                case TIER_1_1MODULE:
1143                        qualityBonus = 0f;
1144                        fleetSizeBonus = 0.2f;
1145                        break;
1146                case TIER_2_1MODULE:
1147                        qualityBonus = 0.2f;
1148                        fleetSizeBonus = 0.3f;
1149                        light = 2;
1150                        break;
1151                case TIER_3_2MODULE:
1152                        qualityBonus = 0.3f;
1153                        fleetSizeBonus = 0.4f;
1154                        light = 2;
1155                        medium = 1;
1156                        break;
1157                case TIER_4_3MODULE:
1158                        qualityBonus = 0.4f;
1159                        fleetSizeBonus = 0.5f;
1160                        light = 2;
1161                        medium = 2;
1162                        break;
1163                case TIER_5_3MODULE:
1164                        qualityBonus = 0.5f;
1165                        fleetSizeBonus = 0.75f;
1166                        light = 2;
1167                        medium = 2;
1168                        heavy = 2;
1169                        break;
1170                }
1171                
1172                market.getStats().getDynamic().getMod(Stats.FLEET_QUALITY_MOD).
1173                                                                        modifyFlatAlways(market.getId(), qualityBonus,
1174                                                                        "Development level");
1175                
1176                market.getStats().getDynamic().getMod(Stats.COMBAT_FLEET_SIZE_MULT).modifyFlatAlways(market.getId(),
1177                                                                        fleetSizeBonus, 
1178                                                                        "Development level");
1179                
1180                
1181                String modId = market.getId();
1182                market.getStats().getDynamic().getMod(Stats.PATROL_NUM_LIGHT_MOD).modifyFlat(modId, light);
1183                market.getStats().getDynamic().getMod(Stats.PATROL_NUM_MEDIUM_MOD).modifyFlat(modId, medium);
1184                market.getStats().getDynamic().getMod(Stats.PATROL_NUM_HEAVY_MOD).modifyFlat(modId, heavy);
1185        }
1186
1187        public boolean isEconomyListenerExpired() {
1188                return isEnded();
1189        }
1190        
1191        public MarketAPI getMarket() {
1192                return market;
1193        }
1194
1195
1196        protected void setBounty() {
1197                bountyData = new BaseBountyData();
1198                float base = 100000f;
1199                switch (tier) {
1200                case TIER_1_1MODULE:
1201                        base = Global.getSettings().getFloat("pirateBaseBounty1");
1202                        bountyData.repChange = 0.02f;
1203                        break;
1204                case TIER_2_1MODULE:
1205                        base = Global.getSettings().getFloat("pirateBaseBounty2");
1206                        bountyData.repChange = 0.05f;
1207                        break;
1208                case TIER_3_2MODULE:
1209                        base = Global.getSettings().getFloat("pirateBaseBounty3");
1210                        bountyData.repChange = 0.06f;
1211                        break;
1212                case TIER_4_3MODULE:
1213                        base = Global.getSettings().getFloat("pirateBaseBounty4");
1214                        bountyData.repChange = 0.07f;
1215                        break;
1216                case TIER_5_3MODULE:
1217                        base = Global.getSettings().getFloat("pirateBaseBounty5");
1218                        bountyData.repChange = 0.1f;
1219                        break;
1220                }
1221                
1222                bountyData.baseBounty = base * (0.9f + (float) Math.random() * 0.2f);
1223                
1224                bountyData.baseBounty = (int)(bountyData.baseBounty / 10000) * 10000;
1225                
1226                
1227                WeightedRandomPicker<FactionAPI> picker = new WeightedRandomPicker<FactionAPI>();
1228                for (MarketAPI curr : Global.getSector().getEconomy().getMarkets(target)) {
1229                        if (curr.getFaction().isPlayerFaction()) continue;
1230                        if (curr.getFaction().getCustom().optBoolean(Factions.CUSTOM_POSTS_NO_BOUNTIES)) continue;
1231                        
1232                        if (affectsMarket(curr)) {
1233                                picker.add(curr.getFaction(), (float) Math.pow(2f, curr.getSize()));
1234                        }
1235                }
1236                
1237                FactionAPI faction = picker.pick();
1238                if (faction == null) {
1239                        bountyData = null;
1240                        return;
1241                }
1242                
1243                bountyData.bountyFaction = faction;
1244                bountyData.bountyDuration = 180f;
1245                bountyData.bountyElapsedDays = 0f;
1246                
1247                Misc.makeImportant(entity, "baseBounty");
1248                
1249                sentBountyUpdate = false;
1250//              makeKnown();
1251//              sendUpdateIfPlayerHasIntel(bountyData, false);
1252        }
1253        
1254        protected boolean sentBountyUpdate = false;
1255        protected void endBounty() {
1256                sendUpdateIfPlayerHasIntel(BOUNTY_EXPIRED_PARAM, false);
1257                bountyData = null;
1258                sentBountyUpdate = false;
1259                Misc.makeUnimportant(entity, "baseBounty");
1260        }
1261        
1262        protected int monthsWithSameTarget = 0;
1263        protected int monthsAtCurrentTier = 0;
1264        protected StarSystemAPI target = null;
1265        public void updateTarget() {
1266                StarSystemAPI newTarget = pickTarget();
1267                if (newTarget == target) return;
1268
1269                clearTarget();
1270                
1271                target = newTarget;
1272                monthsWithSameTarget = 0;
1273                
1274                if (target != null) {
1275//                      for (MarketAPI curr : Global.getSector().getEconomy().getMarkets(system)) {
1276//                              curr.addCondition(Conditions.PIRATE_ACTIVITY, this);
1277//                      }
1278                        new PirateActivityIntel(target, this);
1279//                      PirateActivityIntel intel = new PirateActivityIntel(target, this);
1280//                      if (!isPlayerVisible()) {
1281//                              Global.getSector().getIntelManager().queueIntel(intel);
1282//                      } else {
1283//                              Global.getSector().getIntelManager().addIntel(intel);
1284//                      }
1285                }
1286        }
1287        
1288        public StarSystemAPI getTarget() {
1289                return target;
1290        }
1291
1292        protected void clearTarget() {
1293                if (target != null) {
1294                        target = null;
1295                        monthsWithSameTarget = 0;
1296                }
1297        }
1298        
1299        public List<MarketAPI> getAffectedMarkets(StarSystemAPI system) {
1300                List<MarketAPI> result = new ArrayList<MarketAPI>();
1301                for (MarketAPI curr : Global.getSector().getEconomy().getMarkets(system)) {
1302                        if (!affectsMarket(curr)) continue;
1303                        result.add(curr);
1304                }
1305                return result;
1306        }
1307        
1308        public boolean affectsMarket(MarketAPI market) {
1309                if (market == null) return false;
1310                if (market.isHidden()) return false;
1311                if (market.getFaction() == this.market.getFaction()) return false;
1312                
1313                // player colonies affected by "Hostile Activity" instead
1314                // not anymore
1315                //if (market.getFaction().isPlayerFaction()) return false;
1316                
1317                if (market.getFaction().isPlayerFaction() && playerHasDealWithBaseCommander()) {
1318                        return false;
1319                }
1320                
1321                return true;
1322        }
1323        
1324        
1325        public void setTargetPlayerColoniesOnly(boolean targetPlayerColonies) {
1326                this.targetPlayerColoniesOnly = targetPlayerColonies;
1327        }
1328        public boolean isTargetPlayerColoniesOnly() {
1329                return targetPlayerColoniesOnly;
1330        }
1331        public StarSystemAPI getForceTarget() {
1332                return forceTarget;
1333        }
1334        public void setForceTarget(StarSystemAPI forceTarget) {
1335                this.forceTarget = forceTarget;
1336        }
1337        protected boolean targetPlayerColoniesOnly = false;
1338        protected StarSystemAPI forceTarget = null;
1339        
1340        protected StarSystemAPI pickTarget() {
1341                
1342                WeightedRandomPicker<StarSystemAPI> picker = new WeightedRandomPicker<StarSystemAPI>();
1343                boolean forceTargetIsValid = false;
1344                for (StarSystemAPI system : Global.getSector().getEconomy().getStarSystemsWithMarkets()) {
1345                        float score = 0f;
1346                        for (MarketAPI curr : Global.getSector().getEconomy().getMarkets(system)) {
1347                                if (!affectsMarket(curr)) continue;
1348                                if (targetPlayerColoniesOnly && !curr.getFaction().isPlayerFaction()) continue;
1349                                
1350                                if (system == forceTarget) {
1351                                        forceTargetIsValid = true;
1352                                }
1353                                if (curr.hasCondition(Conditions.PIRATE_ACTIVITY)) continue;
1354                                
1355//                              if (curr.getId().equals("jangala")) {
1356//                                      score += 10000000f;
1357//                              }
1358                                
1359                                float w = curr.getSize();
1360                                
1361                                //float dist = Misc.getDistance(curr.getPrimaryEntity(), market.getPrimaryEntity());
1362                                float dist = Misc.getDistanceLY(curr.getLocationInHyperspace(), market.getLocationInHyperspace());
1363                                
1364                                float mult = 1f - Math.max(0f, dist - 10f) / 10f;
1365                                if (mult < 0.1f) mult = 0.1f;
1366                                if (mult > 1) mult = 1;
1367                                
1368                                if (!targetPlayerColoniesOnly && curr.getFaction().isPlayerFaction()) {
1369                                        if (dist > 15f) continue;
1370                                }
1371                                
1372                                score += w * mult;
1373                                
1374                        }
1375                        picker.add(system, score);
1376                }
1377                
1378                if (forceTargetIsValid) {
1379                        return forceTarget;
1380                }
1381                
1382                return picker.pick();
1383        }
1384        
1385        public List<ArrowData> getArrowData(SectorMapAPI map) {
1386                if (target == null|| target == entity.getContainingLocation()) return null;
1387                
1388                List<ArrowData> result = new ArrayList<ArrowData>();
1389                
1390                SectorEntityToken entityFrom = entity;
1391                if (map != null) {
1392                        SectorEntityToken iconEntity = map.getIntelIconEntity(this);
1393                        if (iconEntity != null) {
1394                                entityFrom = iconEntity;
1395                        }
1396                }
1397                
1398                ArrowData arrow = new ArrowData(entityFrom, target.getCenter());
1399                arrow.color = getFactionForUIColors().getBaseUIColor();
1400                result.add(arrow);
1401                
1402                return result;
1403        }
1404        
1405        public float getAccessibilityPenalty() {
1406                switch (tier) {
1407                case TIER_1_1MODULE: return 0.1f;
1408                case TIER_2_1MODULE: return 0.2f;
1409                case TIER_3_2MODULE: return 0.3f;
1410                case TIER_4_3MODULE: return 0.4f;
1411                case TIER_5_3MODULE: return 0.5f;
1412                }
1413                return 0f;
1414        }
1415        
1416        public float getStabilityPenalty() {
1417                switch (tier) {
1418                case TIER_1_1MODULE: return 1f;
1419                case TIER_2_1MODULE: return 1f;
1420                case TIER_3_2MODULE: return 2f;
1421                case TIER_4_3MODULE: return 2f;
1422                case TIER_5_3MODULE: return 3f;
1423                }
1424                return 0f;
1425        }
1426
1427        public PirateBaseTier getTier() {
1428                return tier;
1429        }
1430
1431        public SectorEntityToken getEntity() {
1432                return entity;
1433        }
1434        public PersonAPI getBaseCommander() {
1435                if (baseCommander == null) {
1436                        baseCommander = initBaseCommander();
1437                }
1438                return baseCommander;
1439        }
1440        public void setBaseCommander(PersonAPI baseCommander) {
1441                this.baseCommander = baseCommander;
1442        }
1443
1444}
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454