001package com.fs.starfarer.api.impl.campaign.missions.hub;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.HashSet;
006import java.util.Iterator;
007import java.util.LinkedHashMap;
008import java.util.LinkedHashSet;
009import java.util.List;
010import java.util.Map;
011import java.util.Random;
012import java.util.Set;
013
014import java.awt.Color;
015
016import org.lwjgl.input.Keyboard;
017import org.lwjgl.util.vector.Vector2f;
018
019import com.fs.starfarer.api.Global;
020import com.fs.starfarer.api.campaign.CampaignFleetAPI;
021import com.fs.starfarer.api.campaign.CampaignTerrainAPI;
022import com.fs.starfarer.api.campaign.CampaignTerrainPlugin;
023import com.fs.starfarer.api.campaign.CargoAPI;
024import com.fs.starfarer.api.campaign.CargoStackAPI;
025import com.fs.starfarer.api.campaign.CustomEntitySpecAPI;
026import com.fs.starfarer.api.campaign.FactionAPI;
027import com.fs.starfarer.api.campaign.InteractionDialogAPI;
028import com.fs.starfarer.api.campaign.LocationAPI;
029import com.fs.starfarer.api.campaign.PersonImportance;
030import com.fs.starfarer.api.campaign.PlanetAPI;
031import com.fs.starfarer.api.campaign.RepLevel;
032import com.fs.starfarer.api.campaign.ReputationActionResponsePlugin.ReputationAdjustmentResult;
033import com.fs.starfarer.api.campaign.SectorEntityToken;
034import com.fs.starfarer.api.campaign.SpecialItemData;
035import com.fs.starfarer.api.campaign.StarSystemAPI;
036import com.fs.starfarer.api.campaign.TextPanelAPI;
037import com.fs.starfarer.api.campaign.econ.Industry;
038import com.fs.starfarer.api.campaign.econ.MarketAPI;
039import com.fs.starfarer.api.campaign.rules.HasMemory;
040import com.fs.starfarer.api.campaign.rules.MemKeys;
041import com.fs.starfarer.api.campaign.rules.MemoryAPI;
042import com.fs.starfarer.api.characters.FullName.Gender;
043import com.fs.starfarer.api.characters.ImportantPeopleAPI;
044import com.fs.starfarer.api.characters.ImportantPeopleAPI.PersonDataAPI;
045import com.fs.starfarer.api.characters.PersonAPI;
046import com.fs.starfarer.api.combat.StatBonus;
047import com.fs.starfarer.api.fleet.FleetMemberAPI;
048import com.fs.starfarer.api.impl.MusicPlayerPluginImpl;
049import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin;
050import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.MissionCompletionRep;
051import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActionEnvelope;
052import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActions;
053import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepRewards;
054import com.fs.starfarer.api.impl.campaign.DerelictShipEntityPlugin;
055import com.fs.starfarer.api.impl.campaign.DerelictShipEntityPlugin.DerelictShipData;
056import com.fs.starfarer.api.impl.campaign.DerelictShipEntityPlugin.DerelictType;
057import com.fs.starfarer.api.impl.campaign.ids.Entities;
058import com.fs.starfarer.api.impl.campaign.ids.Factions;
059import com.fs.starfarer.api.impl.campaign.ids.Ranks;
060import com.fs.starfarer.api.impl.campaign.ids.Stats;
061import com.fs.starfarer.api.impl.campaign.ids.Tags;
062import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin;
063import com.fs.starfarer.api.impl.campaign.intel.contacts.ContactIntel;
064import com.fs.starfarer.api.impl.campaign.missions.CheapCommodityMission;
065import com.fs.starfarer.api.impl.campaign.missions.hub.MissionTrigger.TriggerAction;
066import com.fs.starfarer.api.impl.campaign.missions.hub.MissionTrigger.TriggerActionContext;
067import com.fs.starfarer.api.impl.campaign.plog.PlaythroughLog;
068import com.fs.starfarer.api.impl.campaign.procgen.Constellation;
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.procgen.themes.SalvageSpecialAssigner;
074import com.fs.starfarer.api.impl.campaign.rulecmd.AddRemoveCommodity;
075import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.MarketCMD;
076import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.MarketCMD.RaidDangerLevel;
077import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BaseSalvageSpecial;
078import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BreadcrumbSpecial;
079import com.fs.starfarer.api.impl.campaign.terrain.BaseRingTerrain;
080import com.fs.starfarer.api.impl.campaign.terrain.BaseTiledTerrain;
081import com.fs.starfarer.api.impl.campaign.terrain.DebrisFieldTerrainPlugin.DebrisFieldParams;
082import com.fs.starfarer.api.impl.campaign.terrain.DebrisFieldTerrainPlugin.DebrisFieldSource;
083import com.fs.starfarer.api.ui.ButtonAPI;
084import com.fs.starfarer.api.ui.IntelUIAPI;
085import com.fs.starfarer.api.ui.LabelAPI;
086import com.fs.starfarer.api.ui.SectorMapAPI;
087import com.fs.starfarer.api.ui.TooltipMakerAPI;
088import com.fs.starfarer.api.util.IntervalUtil;
089import com.fs.starfarer.api.util.Misc;
090import com.fs.starfarer.api.util.Misc.Token;
091import com.fs.starfarer.api.util.Pair;
092import com.fs.starfarer.api.util.WeightedRandomPicker;
093
094public abstract class BaseHubMission extends BaseIntelPlugin implements HubMission {
095        
096        public static float DEBRIS_SMALL = 200f;
097        public static float DEBRIS_MEDIUM = 350f;
098        public static float DEBRIS_LARGE = 500f;
099        
100        public static float DEBRIS_SPARSE = 0.25f;
101        public static float DEBRIS_AVERAGE = 0.5f;
102        public static float DEBRIS_DENSE = 1f;
103        
104        public static float GLOBAL_MISSION_REWARD_MULT = 1f;
105        
106        public static int EXTRA_REWARD_PER_MARINE = 50;
107        
108        public static enum CreditReward {
109                VERY_LOW(20000, 25000, 2000),
110                LOW(30000, 40000, 5000),
111                AVERAGE(50000, 60000, 10000),
112                HIGH(70000, 80000, 15000),
113                VERY_HIGH(90000, 100000, 20000),
114//              VERY_LOW(10000, 15000, 2000),
115//              LOW(20000, 25000, 5000),
116//              AVERAGE(30000, 40000, 10000),
117//              HIGH(50000, 60000, 15000),
118//              VERY_HIGH(80000, 100000, 20000),
119                ;
120                public int min;
121                public int max;
122                public int perMarketSize;
123                private CreditReward(int min, int max, int perMarketSize) {
124                        this.min = (int) (min * GLOBAL_MISSION_REWARD_MULT);
125                        this.max = (int) (max * GLOBAL_MISSION_REWARD_MULT);
126                        this.perMarketSize = (int) (perMarketSize * GLOBAL_MISSION_REWARD_MULT);
127                }
128        }
129        
130        
131        
132        public static String BUTTON_ABANDON = "Abandon";
133        
134//      public static int MIN_LEVEL_TO_SCALE_XP_GAIN_AT = 1;
135//      public static int MAX_LEVEL_TO_SCALE_XP_GAIN_AT = 5;
136//      public static enum XPReward {
137//              VERY_LOW(0.05f),
138//              LOW(0.1f),
139//              MEDIUM(0.15f),
140//              HIGH(0.2f),
141//              VERY_HIGH(0.3f),
142//              ;
143//              
144//              float fractionOfLevel;
145//              private XPReward(float fractionOfLevel) {
146//                      this.fractionOfLevel = fractionOfLevel;
147//              }
148//              public float getFractionOfLevel() {
149//                      return fractionOfLevel;
150//              }
151//      }
152        
153        public static enum Abandon {
154                ABANDON
155        }
156        
157        public static enum EntityLocationType {
158                HIDDEN,
159                HIDDEN_NOT_NEAR_STAR,
160                ORBITING_PLANET,
161                ORBITING_PLANET_OR_STAR,
162                UNCOMMON, /* similar to HIDDEN, but can also orbit jump-points */
163                ANY,
164                ORBITING_PARAM,
165        }
166        
167        public static class LocData {
168                public CampaignTerrainAPI terrain;
169                public EntityLocation loc;
170                public EntityLocationType type;
171                public SectorEntityToken centerOn;
172                public LocationAPI system; // can be hyperspace when type is ORBITING_PARAM or EntityLocation is set
173                public boolean removeOnMissionOver;
174                
175                /**
176                 * At provided EntityLocation.
177                 */
178                public LocData(EntityLocation loc, LocationAPI system, boolean removeOnMissionOver) {
179                        this.loc = loc;
180                        this.removeOnMissionOver = removeOnMissionOver;
181                        this.system = system;
182                }
183                
184                /**
185                 * Generate an EntityLocation based on the type. centerOn is used if type is ORBITING_PARAM.
186                 */
187                public LocData(EntityLocationType type, SectorEntityToken centerOn, LocationAPI system,
188                                                                 boolean removeOnMissionOver) {
189                        this.type = type;
190                        this.centerOn = centerOn;
191                        this.system = system;
192                        this.removeOnMissionOver = removeOnMissionOver;
193                }
194                
195                /**
196                 * Set to a zero-radius orbit around centerOn.
197                 */
198                public LocData(SectorEntityToken centerOn, boolean removeOnMissionOver) {
199                        if (centerOn instanceof CampaignTerrainAPI) {
200                                this.centerOn = centerOn;
201                        } else {
202                                loc = new EntityLocation();
203                                loc.type = LocationType.OUTER_SYSTEM;
204                                loc.orbit = Global.getFactory().createCircularOrbit(centerOn, 0f, 0f, 1000f);
205                        }
206                        system = centerOn.getContainingLocation();
207                        this.removeOnMissionOver = removeOnMissionOver;
208                }
209                
210                /**
211                 * At fixed coordinates, no orbit.
212                 */
213                public LocData(Vector2f loc, LocationAPI system, boolean removeOnMissionOver) {
214                        this.loc = new EntityLocation();
215                        this.loc.type = LocationType.OUTER_SYSTEM;
216                        this.loc.location = loc;
217                        this.system = system;
218                        this.removeOnMissionOver = removeOnMissionOver;
219                }
220                
221                /**
222                 * At provided EntityLocation.
223                 */
224                public LocData(EntityLocation loc, LocationAPI system) {
225                        this(loc, system, true);
226                }
227                
228                /**
229                 * Generate an EntityLocation based on the type. centerOn is used if type is ORBITING_PARAM.
230                 */
231                public LocData(EntityLocationType type, SectorEntityToken centerOn, LocationAPI system) {
232                        this(type, centerOn, system, true);
233                }
234                
235                /**
236                 * Set to a zero-radius orbit around centerOn.
237                 */
238                public LocData(SectorEntityToken centerOn) {
239                        this(centerOn, true);
240                }
241                
242                /**
243                 * At fixed coordinates, no orbit.
244                 */
245                public LocData(Vector2f loc, LocationAPI system) {
246                        this(loc, system, true);
247                }
248                
249                public boolean updateLocIfNeeded(BaseHubMission mission, String entityId) {
250                        if (centerOn instanceof CampaignTerrainAPI) {
251                                CampaignTerrainAPI terrain = (CampaignTerrainAPI) centerOn;
252                                loc = mission.generateLocationInsideTerrain(terrain);
253                                if (loc == null) return false;
254                        } else if (type != null) {
255                                loc = mission.generateLocation(entityId, type, centerOn, system);
256                                if (loc == null) return false;
257                        }
258                        return true;
259                }
260                
261                public void placeEntity(SectorEntityToken entity) {
262                        //if (!updateLocIfNeeded(mission, entity.getCustomEntityType())) return false;
263                        
264                        if (loc.orbit != null) {
265                                entity.setOrbit(loc.orbit);
266                                loc.orbit.setEntity(entity);
267                        } else {
268                                entity.setOrbit(null);
269                                entity.getLocation().set(loc.location);
270                        }
271                        
272                        if (removeOnMissionOver) {
273                                entity.addTag(REMOVE_ON_MISSION_OVER);
274                        }
275                }
276        }
277        
278        
279        
280        
281        public static class HubMissionResult {
282                public boolean success;
283                public int reward;
284                public int xp;
285                public ReputationAdjustmentResult repPerson;
286                public ReputationAdjustmentResult repFaction;
287                public Object custom;
288        }
289        
290        public static enum MapLocationType {
291                NORMAL,
292                CONSTELLATION;
293        }
294        public static class ImportanceData {
295                public MemoryAPI memory;
296                public String flag;
297                public MapLocationType locType = null;
298                public SectorEntityToken entity;
299                public MarketAPI market;
300                public PersonAPI person;
301                
302                public ImportanceData() {
303                }
304        }
305        
306        public static class FlagData {
307                public MemoryAPI memory;
308                public String flag;
309                public Object value;
310                public LinkedHashSet<Object> stages = new LinkedHashSet<Object>();
311        }
312        
313        public static class StageData {
314                public Object id;
315                public float elapsed = 0f;
316                //public LinkedHashMap<MemoryAPI, String> important = new LinkedHashMap<MemoryAPI, String>();
317                public List<ImportanceData> important = new ArrayList<ImportanceData>();
318        }
319        
320        public static interface ConditionChecker {
321                boolean conditionsMet();
322        }
323        
324        public static class GlobalBooleanChecker implements ConditionChecker {
325                public String flag;
326                public GlobalBooleanChecker(String flag) {
327                        this.flag = flag;
328                }
329                public boolean conditionsMet() {
330                        return Global.getSector().getMemoryWithoutUpdate().getBoolean(flag);
331                }
332        }
333        
334        public static class MemoryBooleanChecker implements ConditionChecker {
335                public String flag;
336                public MemoryAPI memory;
337                public MemoryBooleanChecker(MemoryAPI memory, String flag) {
338                        this.memory = memory;
339                        this.flag = flag;
340                }
341                public boolean conditionsMet() {
342                        return memory.getBoolean(flag);
343                }
344        }
345        
346        public static class EntityNotAliveChecker implements ConditionChecker {
347                public SectorEntityToken entity;
348                public EntityNotAliveChecker(SectorEntityToken entity) {
349                        this.entity = entity;
350                }
351                public boolean conditionsMet() {
352                        return !entity.isAlive();
353                }
354        }
355        
356        public static class MarketDecivChecker implements ConditionChecker {
357                public MarketAPI market;
358                public MarketDecivChecker(MarketAPI market) {
359                        this.market = market;
360                }
361                public boolean conditionsMet() {
362                        return market.isPlanetConditionMarketOnly() ||
363                                   (market.getPrimaryEntity() != null && !market.getPrimaryEntity().isAlive());
364                }
365        }
366        
367        public static class HostilitiesEndedChecker implements ConditionChecker {
368                public PersonAPI person;
369                public MarketAPI market;
370                public HostilitiesEndedChecker(PersonAPI person, MarketAPI market) {
371                        this.person = person;
372                        this.market = market;
373                }
374                public boolean conditionsMet() {
375                        return !person.getFaction().isHostileTo(market.getFaction());
376                }
377        }
378        
379        public static class HostilitiesStartedChecker implements ConditionChecker {
380                public PersonAPI person;
381                public MarketAPI market;
382                public HostilitiesStartedChecker(PersonAPI person, MarketAPI market) {
383                        this.person = person;
384                        this.market = market;
385                }
386                public boolean conditionsMet() {
387                        return person.getFaction().isHostileTo(market.getFaction());
388                }
389        }
390        
391        public static class DaysElapsedChecker implements ConditionChecker {
392                public float days;
393                public StageData stage;
394                public BaseHubMission mission;
395                public DaysElapsedChecker(float days, StageData stage) {
396                        this.days = days;
397                        this.stage = stage;
398                }
399                
400                public DaysElapsedChecker(float days, BaseHubMission mission) {
401                        this.days = days;
402                        this.mission = mission;
403                }
404
405                public boolean conditionsMet() {
406                        if (mission != null) {
407                                return mission.elapsed >= days;
408                        }
409                        return stage.elapsed >= days;
410                }
411        }
412        
413        public static class InCommRelayRangeChecker implements ConditionChecker {
414                public boolean conditionsMet() {
415                        return Global.getSector().getIntelManager().isPlayerInRangeOfCommRelay();
416                }
417        }
418        
419        public static class InRangeOfEntityChecker implements ConditionChecker {
420                public SectorEntityToken entity;
421                public float range;
422                public InRangeOfEntityChecker(SectorEntityToken entity, float range) {
423                        this.entity = entity;
424                        this.range = range;
425                }
426                public boolean conditionsMet() {
427                        return Global.getSector().getCurrentLocation() == entity.getContainingLocation() &&
428                                                        Misc.getDistance(Global.getSector().getPlayerFleet(), entity) < range;
429                }
430        }
431        
432        public static class InHyperRangeOfEntityChecker implements ConditionChecker {
433                public SectorEntityToken entity;
434                public float rangeLY;
435                public boolean requirePlayerInHyperspace;
436                public InHyperRangeOfEntityChecker(SectorEntityToken entity, float rangeLY, boolean requirePlayerInHyperspace) {
437                        this.entity = entity;
438                        this.rangeLY = rangeLY;
439                        this.requirePlayerInHyperspace = requirePlayerInHyperspace;
440                }
441                public boolean conditionsMet() {
442                        if (requirePlayerInHyperspace && !Global.getSector().getPlayerFleet().isInHyperspace()) return false;
443                        return Misc.getDistanceLY(Global.getSector().getPlayerFleet(), entity) < rangeLY;
444                }
445        }
446        
447        public static class EnteredLocationChecker implements ConditionChecker {
448                public LocationAPI location;
449                public EnteredLocationChecker(LocationAPI location) {
450                        this.location = location;
451                }
452                public boolean conditionsMet() {
453                        return Global.getSector().getCurrentLocation() == location;
454                }
455        }
456        
457        public static class AlwaysTrueChecker implements ConditionChecker {
458                public boolean conditionsMet() {
459                        return true;
460                }
461        }
462        
463        public static class StageConnection {
464                public Object from;
465                public Object to;
466                public ConditionChecker checker;
467                public StageConnection(Object from, Object to, ConditionChecker checker) {
468                        this.from = from;
469                        this.to = to;
470                        this.checker = checker;
471                }
472        }
473        
474        public static class TimeLimitData {
475                public float days;
476                public Object failStage;
477                public LinkedHashSet<Object> endLimitStages = new LinkedHashSet<Object>();
478                public StarSystemAPI noLimitWhileInSystem;
479        }
480        
481        public static interface Abortable {
482                void abort(HubMission mission, boolean missionOver);
483        }
484        
485        public static class VariableSet implements Abortable {
486                public MemoryAPI memory;
487                public String key;
488                public boolean removeOnMissionOver;
489                public VariableSet(MemoryAPI memory, String key, boolean removeOnMissionOver) {
490                        this.memory = memory;
491                        this.key = key;
492                        this.removeOnMissionOver = removeOnMissionOver;
493                }
494
495                public void abort(HubMission mission, boolean missionOver) {
496                        if (!removeOnMissionOver && missionOver) return;
497                        memory.unset(key);
498                }
499        }
500        
501        public static class MadeImportant implements Abortable {
502                public MemoryAPI memory;
503                public String reason;
504                public MadeImportant(MemoryAPI memory, String reason) {
505                        this.memory = memory;
506                        this.reason = reason;
507                }
508                public void abort(HubMission mission, boolean missionOver) {
509                        Misc.makeUnimportant(memory, reason);
510                }
511        }
512        
513        public static class DefeatTriggerAdded implements Abortable {
514                protected CampaignFleetAPI fleet;
515                protected String trigger;
516                protected boolean permanent;
517                public DefeatTriggerAdded(CampaignFleetAPI fleet, String trigger, boolean permanent) {
518                        this.fleet = fleet;
519                        this.trigger = trigger;
520                        this.permanent = permanent;
521                }
522                public void abort(HubMission mission, boolean missionOver) {
523                        if (!(permanent && missionOver)) {
524                                Misc.removeDefeatTrigger(fleet, trigger);
525                        }
526                }
527        }
528        
529        public static String REMOVE_ON_MISSION_OVER = "remove_on_mission_over";
530        public static class EntityAdded implements Abortable {
531                public SectorEntityToken entity;
532                public EntityAdded(SectorEntityToken entity) {
533                        this.entity = entity;
534                }
535
536                public void abort(HubMission mission, boolean missionOver) {
537                        if (missionOver) {
538                                if (!entity.hasTag(REMOVE_ON_MISSION_OVER)) return;
539                                if (entity.hasTag(Tags.FADING_OUT_AND_EXPIRING)) return;
540                        }
541                        if (entity.getContainingLocation() != null) {
542                                entity.getContainingLocation().removeEntity(entity);
543                        }
544                }
545        }
546        
547        public static class PersonAdded implements Abortable {
548                public MarketAPI market;
549                public PersonAPI person;
550                public boolean wasOnlyAddedToCommDirectory;
551
552                public PersonAdded(MarketAPI market, PersonAPI person, boolean wasOnlyAddedToCommDirectory) {
553                        this.market = market;
554                        this.person = person;
555                        this.wasOnlyAddedToCommDirectory = wasOnlyAddedToCommDirectory;
556                }
557
558                public void abort(HubMission mission, boolean missionOver) {
559                        if (missionOver && !person.hasTag(REMOVE_ON_MISSION_OVER)) return;
560                        
561                        if (mission instanceof BaseHubMission) {
562                                BaseHubMission bhm = (BaseHubMission) mission;
563                                if (Misc.setFlagWithReason(person.getMemoryWithoutUpdate(), 
564                                                "$requiredForMissions", bhm.getReason(), false, -1f)) {
565                                        return;
566                                }
567                        }
568                        
569                        if (!wasOnlyAddedToCommDirectory) {
570                                if (market != null) market.removePerson(person);
571                                Global.getSector().getImportantPeople().removePerson(person);
572                        }
573                        if (market != null) market.getCommDirectory().removePerson(person);
574                }
575        }
576        
577        public static class PersonMadeRequired implements Abortable {
578                public PersonAPI person;
579                
580                public PersonMadeRequired(PersonAPI person) {
581                        this.person = person;
582                }
583                
584                public void abort(HubMission mission, boolean missionOver) {
585                        if (mission instanceof BaseHubMission) {
586                                BaseHubMission bhm = (BaseHubMission) mission;
587                                if (Misc.setFlagWithReason(person.getMemoryWithoutUpdate(), 
588                                                "$requiredForMissions", bhm.getReason(), false, -1f)) {
589                                        return;
590                                }
591                        }
592                }
593        }
594        
595        protected Object currentStage = null;
596        protected LinkedHashMap<Object, StageData> stages = new LinkedHashMap<Object, StageData>();
597        protected Boolean stageTransitionsRepeatable = null;
598        protected List<Object> successStages = new ArrayList<Object>();
599        protected List<Object> failStages = new ArrayList<Object>();
600        protected List<Object> noPenaltyFailStages = new ArrayList<Object>();
601        protected Object abandonStage = Abandon.ABANDON;
602        protected List<StageConnection> connections = new ArrayList<StageConnection>();
603        protected List<MissionTrigger> triggers = new ArrayList<MissionTrigger>();
604        protected List<Abortable> changes = new ArrayList<Abortable>();
605        protected List<FlagData> flags = new ArrayList<FlagData>();
606        
607        protected transient Object startingStage;
608        
609        protected transient CargoAPI cargoOnAccept = Global.getFactory().createCargo(true);     
610        protected CargoAPI cargoOnSuccess = null;       
611        
612        protected float elapsed = 0f;
613        protected TimeLimitData timeLimit;
614        protected HubMissionResult result;
615        
616        
617        protected MissionHub hub;
618        protected PersonAPI personOverride = null;
619        protected HubMissionCreator creator;
620        protected Random genRandom = null;
621        
622        protected IntervalUtil tracker = new IntervalUtil(0.09f, 0.11f);
623        
624        protected Float repRewardPerson = null;
625        protected Float repPenaltyPerson = null;
626        protected Float repRewardFaction = null;
627        protected Float repPenaltyFaction = null;
628        protected Integer creditReward = null;
629        protected Integer xpReward = null;
630        
631        protected String iconName;
632        
633        protected RepLevel rewardLimitPerson = null; 
634        protected RepLevel rewardLimitFaction = null; 
635        protected RepLevel penaltyLimitPerson = null; 
636        protected RepLevel penaltyLimitFaction = null; 
637        
638        protected String missionId;
639        
640        protected float rewardMult = 1f;
641        protected float quality = 0f;
642        
643        protected IntelSortTier sortTier = null;
644        
645        public static class PotentialContactData {
646                public PersonAPI contact;
647                public float probability = -1f;
648        }
649        
650        protected List<PotentialContactData> potentialContactsOnMissionSuccess = null;
651        protected Boolean doNotAutoAddPotentialContactsOnSuccess = null;
652        
653        public BaseHubMission() {
654        }
655        
656        public void setStageTransitionsRepeatable() {
657                stageTransitionsRepeatable = true;
658        }
659        
660        public void setDoNotAutoAddPotentialContactsOnSuccess() {
661                this.doNotAutoAddPotentialContactsOnSuccess = true;
662        }
663
664        public void setGiverIsPotentialContactOnSuccess() {
665                setPersonIsPotentialContactOnSuccess(getPerson());
666        }
667        
668        public void setGiverIsPotentialContactOnSuccess(float probability) {
669                setPersonIsPotentialContactOnSuccess(getPerson(), probability);
670        }
671        
672        public void setPersonIsPotentialContactOnSuccess(PersonAPI person) {
673                setPersonIsPotentialContactOnSuccess(person, -1f);
674        }
675        public void setPersonIsPotentialContactOnSuccess(PersonAPI person, float probability) {
676                if (person == null) return;
677                if (potentialContactsOnMissionSuccess == null) {
678                        potentialContactsOnMissionSuccess = new ArrayList<PotentialContactData>();
679                }
680                for (PotentialContactData data : potentialContactsOnMissionSuccess) {
681                        if (data.contact == person) return;
682                }
683                PotentialContactData data = new PotentialContactData();
684                data.contact = person;
685                data.probability = probability;
686                potentialContactsOnMissionSuccess.add(data);
687        }
688        
689        public void setGenRandom(Random random) {
690                genRandom = random;
691        }
692        
693        public void setMissionId(String missionId) {
694                this.missionId = missionId;
695        }
696        public String getMissionId() {
697                return missionId;
698        }
699        
700        public boolean isBarEvent() {
701                return isBarEvent;
702        }
703
704        protected boolean isBarEvent = false;
705        public void createAndAbortIfFailed(MarketAPI market, boolean barEvent) {
706                isBarEvent = barEvent;
707                if (getPerson() != null) {
708                        genMissionRewardMultAndQuality();
709                }
710                if (!create(market, barEvent)) {
711                        abort();
712                }
713        }
714        protected abstract boolean create(MarketAPI createdAt, boolean barEvent);
715        
716        protected String baseName = null;
717        
718        public String getBaseName() {
719                if (baseName != null) {
720                        return baseName;
721                }
722                return "Call setName(<name>) to set mission name";
723        }
724        public void setName(String name) {
725                baseName = name;
726        }
727        
728        public String getBlurbText() {
729                return null;
730        }
731        
732//      public abstract boolean addNextStepText(TooltipMakerAPI info, Color tc, float pad);
733//      public abstract void addDescriptionForCurrentStage(TooltipMakerAPI info, float width, float height);
734        
735        public boolean addNextStepText(TooltipMakerAPI info, Color tc, float pad) {
736                String text = getNextStepText();
737                if (text != null) {
738                        info.addPara(text, tc, pad);
739                        return true;
740                }
741                return false;
742        }
743        public void addDescriptionForNonEndStage(TooltipMakerAPI info, float width, float height) {
744                
745        }
746        public void addDescriptionForCurrentStage(TooltipMakerAPI info, float width, float height) {
747                float opad = 10f;
748                String text = getStageDescriptionText();
749                if (text != null) {
750                        info.addPara(text, opad);
751                } else {
752                        String noun = getMissionTypeNoun();
753                        String verb = getMissionCompletionVerb();
754                        if (isSucceeded()) {
755                                info.addPara("You have successfully " + verb + " this " + noun + ".", opad);
756                        } else if (isFailed()) {
757                                info.addPara("You have failed this " + noun + ".", opad);
758                        } else if (isAbandoned()) {
759                                info.addPara("You have abandoned this " + noun + ".", opad);
760                        } else {
761                                addDescriptionForNonEndStage(info, width, height);
762                        }
763                }
764        }
765        public String getStageDescriptionText() {
766                return null;
767        }
768        public String getNextStepText() {
769                return null;
770        }
771        
772        @Override
773        protected void advanceImpl(float amount) {
774                float days = Global.getSector().getClock().convertToDays(amount);
775                StageData stage = getData(currentStage);
776                if (stage != null) {
777                        stage.elapsed += days;
778                }
779                
780                //elapsed += days * 100f;
781                elapsed += days;
782
783                //if (timeLimit != null) timeLimit.days = 240;
784                if (timeLimit != null && timeLimit.days < elapsed &&
785                                (timeLimit.noLimitWhileInSystem == null || 
786                                                timeLimit.noLimitWhileInSystem != Global.getSector().getCurrentLocation())) {
787                        setCurrentStage(timeLimit.failStage, null, null);
788                        timeLimit = null;
789                        runTriggers();
790                }
791                
792                tracker.advance(days);
793                if (tracker.intervalElapsed()) {
794                        checkStageChangesAndTriggers(null, null);
795                }
796        }
797        
798        public float getElapsedInCurrentStage() {
799                StageData stage = getData(currentStage);
800                if (stage != null) {
801                        return stage.elapsed;
802                }
803                return 0f;
804        }
805        
806        @Override
807        protected void notifyEnded() {
808                super.notifyEnded();
809                Global.getSector().removeScript(this);
810        }
811
812        public void accept(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
813                setImportant(true);
814                
815                if (startingStage == null) {
816                        throw new RuntimeException("startingStage can not be null. Use setStartingStage()");
817                }
818                
819                // don't pass in dialog/memoryMap so it doesn't add an update item to the textPanel
820                // might not anyway, but that depends on some future decisions...
821                setCurrentStage(startingStage, null, null); //dialog, memoryMap);
822                
823                acceptImpl(dialog, memoryMap);
824                
825                TextPanelAPI text = dialog != null ? dialog.getTextPanel() : null;
826                
827                if (cargoOnAccept != null) {
828                        cargoOnAccept.sort();
829                        for (CargoStackAPI stack : cargoOnAccept.getStacksCopy()) {
830                                Global.getSector().getPlayerFleet().getCargo().addItems(stack.getType(), stack.getData(), stack.getSize());
831                                if (text != null) {
832                                        AddRemoveCommodity.addStackGainText(stack, text);
833                                }
834                        }
835                        cargoOnAccept.clear();
836                }
837                
838                Global.getSector().getIntelManager().addIntel(this, false, text);
839                Global.getSector().addScript(this);
840                
841                startingStage = null;
842                
843                runTriggers();
844        }
845        
846        public void acceptImpl(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
847                
848        }
849
850        protected transient boolean aborted = false;
851        public void abort() {
852                boolean missionWasAccepted = currentStage != null;
853                for (Abortable curr : changes) {
854                        curr.abort(this, missionWasAccepted);
855                }
856                changes.clear();
857                
858                //if (genRandom != null) {
859                if (!missionWasAccepted) {
860                        aborted = true;
861                }
862                
863                // is this needed? not if missions are reset every time in BaseMissionHub.updateOfferedMissions()
864                // and not saved
865//              currentStage = null;
866//              stages.clear();
867//              successStages.clear();
868//              failStages.clear();
869//              connections.clear();
870//              
871//              genRandom = null;
872//              startingStage = null;
873        }
874        
875        public boolean isMissionCreationAborted() {
876                return aborted;
877        }
878        
879        
880        public String getTriggerPrefix() {
881                //return getClass().getSimpleName();
882                return getMissionId();
883        }
884        
885        protected boolean callAction(String action, String ruleId, InteractionDialogAPI dialog, List<Token> params, Map<String, MemoryAPI> memoryMap) {
886                return false;
887        }
888        
889        protected void addPotentialContacts(InteractionDialogAPI dialog) {
890                if (potentialContactsOnMissionSuccess != null) {
891                        for (PotentialContactData curr : potentialContactsOnMissionSuccess) {
892                                PersonAPI p = curr.contact;
893                                MarketAPI m = curr.contact.getMarket();
894                                if (m == null) m = getPerson().getMarket();
895                                if (m != null) {
896                                        if (curr.probability < 0) {
897                                                ContactIntel.addPotentialContact(p, m, dialog != null ? dialog.getTextPanel() : null);
898                                        } else {
899                                                ContactIntel.addPotentialContact(curr.probability, p, m, dialog != null ? dialog.getTextPanel() : null);
900                                        }
901                                }
902                        }
903                        potentialContactsOnMissionSuccess = null;
904                }
905        }
906        
907        @SuppressWarnings("rawtypes")
908        @Override
909        public boolean callEvent(String ruleId, InteractionDialogAPI dialog, List<Token> params, Map<String, MemoryAPI> memoryMap) {
910                String action = params.get(0).getString(memoryMap);
911                
912                if ("endFailure".equals(action)) {
913                        Object stage = null;
914                        if (!failStages.isEmpty()) {
915                                stage = failStages.get(0);
916                        }
917                        if (stage == null && !noPenaltyFailStages.isEmpty()) {
918                                stage = noPenaltyFailStages.get(0);
919                        }
920                        if (stage != null) {
921                                setCurrentStage(stage, dialog, memoryMap);
922                                runTriggers();
923                        }
924                        return true;
925                }
926                
927                if ("makeUnimportant".equals(action)) {
928                        SectorEntityToken target = dialog.getInteractionTarget();
929                        if (target != null) {
930                                makeUnimportant(target, (Enum) currentStage);
931                                if (target.getMarket() != null) {
932                                        makeUnimportant(target.getMarket(), (Enum) currentStage);
933                                }
934                                if (target.getActivePerson() != null) {
935                                        makeUnimportant(target.getActivePerson(), (Enum) currentStage);
936                                }
937                        }
938                        return true;
939                }
940                
941                if ("showMap".equals(action)) {
942                        SectorEntityToken mapLoc = getMapLocation(null, startingStage);
943                        if (mapLoc != null) {
944                                String title = params.get(1).getStringWithTokenReplacement(ruleId, dialog, memoryMap);
945                                String text = "";
946                                Set<String> tags = getIntelTags(null);
947                                tags.remove(Tags.INTEL_ACCEPTED);
948                                String icon = getIcon();
949                                
950                                Color color = getFactionForUIColors().getBaseUIColor();
951                                //String factionId = getFactionForUIColors().getId();
952                                if (mapLoc != null && mapLoc.getFaction() != null && !mapLoc.getFaction().isNeutralFaction()) {
953                                        color = mapLoc.getFaction().getBaseUIColor();
954                                        //factionId = mapLoc.getFaction().getId();
955                                } else if (mapLoc instanceof PlanetAPI) {
956                                        PlanetAPI planet = (PlanetAPI) mapLoc;
957                                        if (planet.getStarSystem() != null && planet.getFaction().isNeutralFaction()) {
958                                                StarSystemAPI system = planet.getStarSystem();
959                                                if (system.getStar() == planet || system.getCenter() == planet) {
960                                                        if (planet.getMarket() != null) {
961                                                                color = planet.getMarket().getTextColorForFactionOrPlanet();
962                                                        } else {
963                                                                color = Misc.setAlpha(planet.getSpec().getIconColor(), 255);
964                                                                color = Misc.setBrightness(color, 235);
965                                                        }
966                                                } else {
967                                                        color = Misc.setAlpha(planet.getSpec().getIconColor(), 255);
968                                                        color = Misc.setBrightness(color, 235);
969                                                }
970                                        }
971                                }
972                                if (mapMarkerNameColor != null) {
973                                        color = mapMarkerNameColor;
974                                }
975                                
976                                dialog.getVisualPanel().showMapMarker(mapLoc, 
977                                                title, color, 
978                                                true, icon, text, tags);
979                        }
980                        return true;
981                }
982                
983                if ("hideMap".equals(action)) {
984                        dialog.getVisualPanel().removeMapMarkerFromPersonInfo();
985                        return true;
986                }
987                
988//              if (action.equals("endSuccess")) {
989//                      doNotEndMission = true;
990//                      checkStageChangesAndTriggers();
991//                      doNotEndMission = false;
992//                      endSuccess(dialog, memoryMap);
993//              } else if (action.equals("endFailure")) {
994//                      doNotEndMission = true;
995//                      checkStageChangesAndTriggers();
996//                      doNotEndMission = false;
997//                      endFailure(dialog, memoryMap);
998//              } else
999                if (action.equals("updateStage")) {
1000                        checkStageChangesAndTriggers(dialog, memoryMap);
1001                } else if (action.equals("updateData")) {
1002                        checkStageChangesAndTriggers(dialog, memoryMap);
1003                        updateInteractionData(dialog, memoryMap);
1004                } else if (action.equals("addContacts")) {
1005                        addPotentialContacts(dialog);
1006                } else if (action.equals("repSuccess")) {
1007                        adjustRep(dialog.getTextPanel(), null, RepActions.MISSION_SUCCESS);
1008                } else if (action.equals("repFailure")) {
1009                        adjustRep(dialog.getTextPanel(), null, RepActions.MISSION_FAILURE);
1010                } else {
1011                        if (!callAction(action, ruleId, dialog, params, memoryMap)) {
1012                                throw new RuntimeException("Unhandled action [" + action + "] in " + getClass().getSimpleName() + 
1013                                                                                   " for rule [" + ruleId + "], params:[" + params + "]");
1014                        }
1015                }
1016                return true;
1017        }
1018        
1019        protected void showPersonInfo(PersonAPI person, InteractionDialogAPI dialog, boolean withFaction, boolean withRelBar) {
1020                dialog.getInteractionTarget().setActivePerson(person);
1021                dialog.getVisualPanel().showPersonInfo(person, !withFaction, withRelBar);
1022        }
1023        
1024        protected transient MemoryAPI interactionMemory = null;
1025        public void updateInteractionData(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
1026                interactionMemory = memoryMap.get(MemKeys.LOCAL);
1027                if (interactionMemory == null) {
1028                        if (dialog.getInteractionTarget().getActivePerson() != null) {
1029                                interactionMemory = dialog.getInteractionTarget().getActivePerson().getMemoryWithoutUpdate();   
1030                        } else {
1031                                interactionMemory = dialog.getInteractionTarget().getMemoryWithoutUpdate();
1032                        }
1033                }
1034
1035                // unless we're already talking to a person
1036                // actually, always do it; this happens in a bar event
1037                // don't always do it - if updateData is called when talking to *another* person
1038                // (i.e. not the mission giver) this would override their $HeOrShe etc tokens
1039                if (isBarEvent() || !memoryMap.containsKey(MemKeys.ENTITY)) {
1040                        setPersonTokens(interactionMemory);
1041                }
1042
1043                if (getCurrentStage() != null) {
1044                        set("$" + getMissionId() + "_stage", ((Enum)getCurrentStage()).name());
1045                }
1046                
1047                updateInteractionDataImpl();
1048        }
1049        
1050        
1051        protected void updateInteractionDataImpl() {
1052                
1053        }
1054        
1055        protected void setPersonTokens(MemoryAPI mem) {
1056                PersonAPI person = getPerson();
1057                if (person == null) return;
1058                
1059                if (person.isMale()) {
1060                        mem.set("$hisOrHer", "his", 0);
1061                        mem.set("$HisOrHer", "His", 0);
1062                        mem.set("$himOrHer", "him", 0);
1063                        mem.set("$HimOrHer", "Him", 0);
1064                        mem.set("$heOrShe", "he", 0);
1065                        mem.set("$HeOrShe", "He", 0);
1066                        mem.set("$himOrHerself", "himself", 0);
1067                        mem.set("$HimOrHerself", "Himself", 0);
1068                        mem.set("$manOrWoman", "man", 0);
1069                        mem.set("$ManOrWoman", "Man", 0);
1070                } else {
1071                        mem.set("$hisOrHer", "her", 0);
1072                        mem.set("$HisOrHer", "Her", 0);
1073                        mem.set("$himOrHer", "her", 0);
1074                        mem.set("$HimOrHer", "Her", 0);
1075                        mem.set("$heOrShe", "she", 0);
1076                        mem.set("$HeOrShe", "She", 0);
1077                        mem.set("$himOrHerself", "herself", 0);
1078                        mem.set("$HimOrHerself", "Herself", 0);
1079                        mem.set("$manOrWoman", "woman", 0);
1080                        mem.set("$ManOrWoman", "Woman", 0);
1081                }
1082                
1083                if (person.getRank() != null) {
1084                        mem.set("$personRankAOrAn", person.getRankArticle(), 0);
1085                        mem.set("$personRank", person.getRank().toLowerCase(), 0);
1086                        mem.set("$PersonRank", Misc.ucFirst(person.getRank()), 0);
1087                }
1088                
1089                if (person.getPost() != null) {
1090                        mem.set("$personPostAOrAn", person.getPostArticle().toLowerCase(), 0);
1091                        mem.set("$personPost", person.getPost().toLowerCase(), 0);
1092                        mem.set("$PersonPost", Misc.ucFirst(person.getPost()), 0);
1093                }
1094                
1095                mem.set("$PersonName", person.getName().getFullName(), 0);
1096                mem.set("$personName", person.getName().getFullName(), 0);
1097                mem.set("$personFirstName", person.getName().getFirst(), 0);
1098                mem.set("$personLastName", person.getName().getLast(), 0);
1099                
1100                if (person.getVoice() != null) {
1101                        mem.set("$voice", person.getVoice());
1102                }
1103                if (person.getImportance() != null) {
1104                        mem.set("$importance", person.getImportance().name());
1105                }
1106                
1107        }
1108        
1109        public void set(String key, Object value) {
1110                if (value instanceof Enum) value = ((Enum)value).name(); 
1111                interactionMemory.set(key, value, 0f);
1112        }
1113        
1114        public void unset(String key) {
1115                interactionMemory.unset(key);
1116        }
1117        
1118        
1119        public StageData getData(Object id) {
1120                if (id == null) return null;
1121                StageData data = stages.get(id);
1122                if (data == null) {
1123                        data = new StageData();
1124                        data.id = id;
1125                        stages.put(id, data);
1126                }
1127                return data;
1128        }
1129        
1130        protected boolean shouldSendUpdateForStage(Object id) {
1131                return true;
1132        }
1133        
1134        public void checkStageChangesAndTriggers(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
1135                if (isEnding()) return;
1136                boolean changed = false;
1137                do {
1138                        changed = false;
1139                        for (StageConnection conn : connections) {
1140                                if (conn.from != currentStage && conn.from != null) continue;
1141                                if (conn.to == currentStage) continue;
1142                                
1143                                if (conn.checker.conditionsMet()) {
1144                                        setCurrentStage(conn.to, dialog, memoryMap);
1145                                        changed = true;
1146                                        
1147                                        if (stageTransitionsRepeatable == null || !stageTransitionsRepeatable) {
1148                                                connections.remove(conn);
1149                                        }
1150                                        
1151                                        break;
1152                                }
1153                        }
1154                        //runTriggers();
1155                } while (changed);
1156                
1157                runTriggers();
1158        }
1159        
1160        protected void runTriggers() {
1161                Iterator<MissionTrigger> iter = triggers.iterator();
1162                while (iter.hasNext()) {
1163                        MissionTrigger trigger = iter.next();
1164                        if (!trigger.getStages().contains(currentStage)) continue;
1165                        if (trigger.getCondition().conditionsMet()) {
1166                                TriggerActionContext context = new TriggerActionContext(this);
1167                                for (TriggerAction curr : trigger.getActions()) {
1168                                        curr.doAction(context);
1169                                }
1170                                iter.remove();
1171                        }
1172                }
1173        }
1174        
1175        public List<CampaignFleetAPI> runStageTriggersReturnFleets(Object stage) {
1176                List<CampaignFleetAPI> result = new ArrayList<CampaignFleetAPI>();
1177                Iterator<MissionTrigger> iter = triggers.iterator();
1178                while (iter.hasNext()) {
1179                        MissionTrigger trigger = iter.next();
1180                        if (!trigger.getStages().contains(stage)) continue;
1181                        if (trigger.getCondition().conditionsMet()) {
1182                                TriggerActionContext context = new TriggerActionContext(this);
1183                                for (TriggerAction curr : trigger.getActions()) {
1184                                        curr.doAction(context);
1185                                }
1186                                iter.remove();
1187                                result.addAll(context.allFleets);
1188                        }
1189                }
1190                return result;
1191        }
1192        
1193        protected transient boolean doNotEndMission = false;
1194        public void setCurrentStage(Object next, InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
1195                if (currentStage == next) {
1196                        return;
1197                }
1198                
1199                if (currentStage != null) {
1200                        StageData data = getData(currentStage);
1201                        for (ImportanceData curr : data.important) {
1202                                Misc.makeUnimportant(curr.memory, getReason());
1203                                removeImportanceChanges(curr.memory);
1204                                
1205                                if (curr.flag != null) {
1206                                        curr.memory.unset(curr.flag);
1207                                        removeMemoryFlagChanges(curr.memory, curr.flag);
1208                                }
1209                        }
1210                }
1211                
1212                currentStage = next;
1213                if (timeLimit != null && timeLimit.endLimitStages != null && 
1214                                timeLimit.endLimitStages.contains(currentStage)) {
1215                        timeLimit = null;
1216                }
1217                
1218                StageData data = getData(currentStage);
1219                data.elapsed = 0;
1220                for (ImportanceData curr : data.important) {
1221                        //if (curr.market != null) curr.memory = curr.market.getMemoryWithoutUpdate();
1222                        
1223                        Misc.makeImportant(curr.memory, getReason());
1224                        changes.add(new MadeImportant(curr.memory, getReason()));
1225                        if (curr.flag != null) {
1226                                curr.memory.set(curr.flag, true);
1227                                changes.add(new VariableSet(curr.memory, curr.flag, true));
1228                        }
1229                }
1230                
1231                for (FlagData fd : flags) {
1232                        if (fd.stages.contains(currentStage)) {
1233                                removeMemoryFlagChanges(fd.memory, fd.flag);
1234                                if (fd.value == null) {
1235                                        fd.memory.set(fd.flag, true);
1236                                } else {
1237                                        fd.memory.set(fd.flag, fd.value);
1238                                }
1239                                changes.add(new VariableSet(fd.memory, fd.flag, true));
1240                        } else {
1241                                fd.memory.unset(fd.flag);
1242                                removeMemoryFlagChanges(fd.memory, fd.flag);
1243                        }
1244                                
1245                }
1246
1247                
1248                if (!doNotEndMission) {
1249                        if (successStages.contains(currentStage)) {
1250                                endSuccess(dialog, memoryMap);
1251                        } else if (failStages.contains(currentStage)) {
1252                                endFailure(dialog, memoryMap);
1253                        } else if (currentStage != null && currentStage == abandonStage) {
1254                                endAbandon();
1255                        } else if (shouldSendUpdateForStage(currentStage)) {
1256                                sendUpdateForNextStep(NEXT_STEP_UPDATE, dialog == null ? null : dialog.getTextPanel());
1257                        }
1258                }
1259                
1260                runTriggers();
1261        }
1262        
1263        protected void endSuccess(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
1264                setImportant(false);
1265                
1266                result = new HubMissionResult();
1267                result.success = true;
1268                
1269                TextPanelAPI textPanel = null;
1270                if (dialog != null) textPanel = dialog.getTextPanel();
1271                
1272                int xp = getXPReward();
1273                if (xp > 0) { 
1274                        Global.getSector().getPlayerStats().addXP(xp, textPanel);
1275                }
1276                
1277                int reward = getCreditsReward();
1278                if (reward > 0) {
1279                        Global.getSector().getPlayerFleet().getCargo().getCredits().add(reward);
1280                }
1281                
1282                if (textPanel != null && reward > 0) {
1283                        AddRemoveCommodity.addCreditsGainText(reward, dialog.getTextPanel());
1284                }
1285                
1286                
1287                result.reward = reward;
1288                result.xp = xp;
1289                
1290                adjustRep(textPanel, result, RepActions.MISSION_SUCCESS);
1291
1292                
1293                endSuccessImpl(dialog, memoryMap);
1294                
1295                if (cargoOnSuccess != null) {
1296                        cargoOnSuccess.sort();
1297                        for (CargoStackAPI stack : cargoOnSuccess.getStacksCopy()) {
1298                                Global.getSector().getPlayerFleet().getCargo().addItems(stack.getType(), stack.getData(), stack.getSize());
1299                                if (textPanel != null) { 
1300                                        AddRemoveCommodity.addStackGainText(stack, textPanel);
1301                                }
1302                        }
1303                        cargoOnSuccess.clear();
1304                }
1305                
1306                endAfterDelay();
1307                
1308//              if (textPanel == null) { // if in dialog, already printed stuff to the text panel
1309//                      sendUpdateIfPlayerHasIntel(result, false);
1310//              }
1311                sendUpdateForNextStep(END_MISSION_UPDATE, dialog == null ? null : dialog.getTextPanel());
1312                
1313                if (creator != null) creator.incrCompleted();
1314                
1315                
1316                if (potentialContactsOnMissionSuccess != null) {
1317                        if (doNotAutoAddPotentialContactsOnSuccess == null || !doNotAutoAddPotentialContactsOnSuccess) {
1318                                for (PotentialContactData curr : potentialContactsOnMissionSuccess) {
1319                                        PersonAPI p = curr.contact;
1320                                        MarketAPI m = curr.contact.getMarket();
1321                                        if (m == null) m = getPerson().getMarket();
1322                                        if (curr.probability < 0) {
1323                                                ContactIntel.addPotentialContact(p, m, dialog != null ? dialog.getTextPanel() : null);
1324                                        } else {
1325                                                ContactIntel.addPotentialContact(curr.probability, p, m, dialog != null ? dialog.getTextPanel() : null);
1326                                        }
1327                                }
1328                                potentialContactsOnMissionSuccess = null;
1329                                doNotAutoAddPotentialContactsOnSuccess = null;
1330                        }
1331                }
1332                
1333                abort();
1334                
1335                if (completedKey != null) {
1336                        Global.getSector().getMemoryWithoutUpdate().set(completedKey, true);
1337                }
1338        }
1339        
1340        protected void endFailure(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
1341                setImportant(false);
1342                
1343                result = new HubMissionResult();
1344                result.success = false;
1345                
1346                TextPanelAPI textPanel = null;
1347                if (dialog != null) textPanel = dialog.getTextPanel();
1348                
1349                if (!noPenaltyFailStages.contains(currentStage)) {
1350                        adjustRep(textPanel, result, RepActions.MISSION_FAILURE);
1351                }
1352                
1353                endFailureImpl(dialog, memoryMap);
1354                endAfterDelay();
1355                
1356//              if (textPanel == null) {
1357//                      sendUpdateIfPlayerHasIntel(result, false);
1358//              }
1359                sendUpdateForNextStep(END_MISSION_UPDATE, dialog == null ? null : dialog.getTextPanel());
1360                
1361                if (creator != null) creator.incrFailed();
1362                abort();
1363        }
1364        
1365        protected void endAbandon() {
1366                result = new HubMissionResult();
1367                result.success = false;
1368                
1369                if (!canAbandonWithoutPenalty()) {
1370                        adjustRep(null, result, RepActions.MISSION_FAILURE);
1371                        if (creator != null) creator.incrFailed();
1372                }
1373                
1374                endAbandonImpl();
1375
1376                endAfterDelay();
1377                abort();
1378        }
1379        
1380        
1381        
1382        protected void endSuccessImpl(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
1383        }
1384        protected void endFailureImpl(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) { 
1385        }
1386        protected void endAbandonImpl() { 
1387        }
1388        
1389        
1390        protected Boolean adjustedRep;
1391        protected void adjustRep(TextPanelAPI textPanel, HubMissionResult result, RepActions action) {
1392                if (adjustedRep != null) return;
1393                adjustedRep = true;
1394                
1395                MissionCompletionRep completionRepPerson = new MissionCompletionRep(
1396                                getRepRewardSuccessPerson(), getRewardLimitPerson(),
1397                                -getRepPenaltyFailurePerson(), getPenaltyLimitPerson());
1398                MissionCompletionRep completionRepFaction = new MissionCompletionRep(
1399                                getRepRewardSuccessFaction(), getRewardLimitFaction(),
1400                                -getRepPenaltyFailureFaction(), getPenaltyLimitFaction());
1401
1402                boolean withMessage = textPanel != null;
1403                
1404                boolean adjustPersonRep = (action == RepActions.MISSION_SUCCESS && completionRepPerson.successDelta != 0) ||
1405                                (action == RepActions.MISSION_FAILURE && completionRepPerson.failureDelta != 0);
1406                if (adjustPersonRep && getPerson() != null) {
1407                        ReputationAdjustmentResult rep = Global.getSector().adjustPlayerReputation(
1408                                        new RepActionEnvelope(action, completionRepPerson,
1409                                                        textPanel, true, withMessage), 
1410                                                        getPerson());
1411                        if (result != null) result.repPerson = rep;
1412                        completionRepPerson.successDelta = 0;
1413                }
1414
1415                boolean adjustFactionRep = (action == RepActions.MISSION_SUCCESS && completionRepFaction.successDelta != 0) ||
1416                                (action == RepActions.MISSION_FAILURE && completionRepFaction.failureDelta != 0);
1417                if (adjustFactionRep && getPerson() != null) {
1418                        ReputationAdjustmentResult rep = Global.getSector().adjustPlayerReputation(
1419                                        new RepActionEnvelope(action, completionRepFaction,
1420                                                        textPanel, true, withMessage), 
1421                                                        getPerson().getFaction().getId());
1422                        if (result != null) result.repFaction = rep;
1423                        completionRepFaction.successDelta = 0f;
1424                }
1425        }
1426        
1427        
1428        public void setSuccessStage(Object id) {
1429                addSuccessStages(id);
1430        }
1431        public void addSuccessStages(Object ... ids) {
1432                for (Object id : ids) {
1433                        successStages.add(id);
1434                }
1435        }
1436        
1437        public void setFailureStage(Object id) {
1438                addFailureStages(id);
1439        }
1440        public void addFailureStages(Object ... ids) {
1441                for (Object id : ids) {
1442                        failStages.add(id);
1443                }
1444        }
1445        
1446        public void setNoPenaltyFailureStage(Object id) {
1447                addNoPenaltyFailureStages(id);
1448        }
1449        public void addNoPenaltyFailureStages(Object ... ids) {
1450                addFailureStages(ids);
1451                for (Object id : ids) {
1452                        noPenaltyFailStages.add(id);
1453                }
1454        }
1455        
1456        protected void removeImportanceChanges(MemoryAPI memory) {
1457                Iterator<Abortable> iter = changes.iterator();
1458                while (iter.hasNext()) {
1459                        Abortable curr = iter.next();
1460                        if (curr instanceof MadeImportant) {
1461                                MadeImportant mi = (MadeImportant) curr;
1462                                if (mi.memory == memory) {
1463                                        iter.remove();
1464                                }
1465                        }
1466                }
1467        }
1468        
1469        protected void removeMemoryFlagChanges(MemoryAPI memory, String flag) {
1470                Iterator<Abortable> iter = changes.iterator();
1471                while (iter.hasNext()) {
1472                        Abortable curr = iter.next();
1473                        if (curr instanceof VariableSet) {
1474                                VariableSet vs = (VariableSet) curr;
1475                                if (vs.memory == memory && flag.equals(vs.key)) {
1476                                        iter.remove();
1477                                }
1478                        }
1479                }
1480        }
1481        
1482        public String getReason() {
1483                return getMissionId(); 
1484        }
1485
1486        public int getCreditsReward() {
1487                if (creditReward != null) return creditReward;
1488                return 0;
1489        }
1490        
1491        public int getXPReward() {
1492                if (xpReward != null) return xpReward;
1493                return 0;
1494        }
1495        
1496        public float getRepRewardSuccessPerson() {
1497                if (repRewardPerson != null) return repRewardPerson;
1498                return RepRewards.HIGH;
1499        }
1500        
1501        public float getRepPenaltyFailurePerson() {
1502                if (repPenaltyPerson != null) return repPenaltyPerson;
1503                return RepRewards.SMALL;
1504        }
1505        
1506        public float getRepRewardSuccessFaction() {
1507                if (repRewardFaction != null) return repRewardFaction;
1508                return RepRewards.MEDIUM;
1509        }
1510        
1511        public float getRepPenaltyFailureFaction() {
1512                if (repPenaltyFaction != null) return repPenaltyFaction;
1513                return RepRewards.TINY;
1514        }
1515        
1516        public RepLevel getRewardLimitPerson() {
1517                return rewardLimitPerson != null ? rewardLimitPerson : RepLevel.COOPERATIVE;
1518        }
1519
1520        public RepLevel getRewardLimitFaction() {
1521                return rewardLimitFaction != null ? rewardLimitFaction : RepLevel.COOPERATIVE;
1522        }
1523
1524        public RepLevel getPenaltyLimitPerson() {
1525                return penaltyLimitPerson != null ? penaltyLimitPerson : RepLevel.VENGEFUL;
1526        }
1527
1528        public RepLevel getPenaltyLimitFaction() {
1529                return penaltyLimitFaction != null ? penaltyLimitFaction : RepLevel.VENGEFUL;
1530        }
1531
1532        public MissionHub getHub() {
1533                return hub;
1534        }
1535        
1536        public void setHub(MissionHub hub) {
1537                this.hub = hub;
1538        }
1539        
1540        public PersonAPI getPerson() {
1541                if (personOverride != null) return personOverride;
1542                if (hub == null) return null;
1543                return hub.getPerson();
1544        }
1545
1546        public HubMissionCreator getCreator() {
1547                return creator;
1548        }
1549
1550        public void setCreator(HubMissionCreator creator) {
1551                this.creator = creator;
1552        }
1553
1554        public void setStartingStage(Object startingStage) {
1555                this.startingStage = startingStage;
1556        }
1557        
1558        public void setPersonDoGenericPortAuthorityCheck(PersonAPI person) {
1559                if (person.getMemoryWithoutUpdate().getBoolean("$doGenericPortAuthorityCheck")) return;
1560                setFlag(person, "$doGenericPortAuthorityCheck", false);
1561        }
1562
1563//      public void setMarketExtraSmugglingSuspicionLevel(MarketAPI market, float extra) {
1564//              setFlag(market.getMemoryWithoutUpdate(), MemFlags.MARKET_EXTRA_SUSPICION, extra, false);
1565//      }
1566        
1567        public void setFlagWithReason(SectorEntityToken entity, String flag, boolean permanent) {
1568                setFlag(entity.getMemoryWithoutUpdate(), flag, null, permanent, (Object[])null);
1569                
1570                String reason = getReason();
1571                Misc.setFlagWithReason(entity.getMemoryWithoutUpdate(), flag, reason, true, -1f);
1572                
1573                if (!permanent) {
1574                        String requiredKey = flag + "_" + reason;
1575                        changes.add(new VariableSet(entity.getMemoryWithoutUpdate(), requiredKey, true));
1576                }
1577        }
1578        
1579        public void setFlag(SectorEntityToken entity, String flag, boolean permanent) {
1580                setFlag(entity.getMemoryWithoutUpdate(), flag, null, permanent, (Object[])null);
1581        }
1582        public void setFlag(PersonAPI person, String flag, boolean permanent) {
1583                setFlag(person.getMemoryWithoutUpdate(), flag, null, permanent, (Object[])null);
1584        }
1585        
1586        public void setFlag(SectorEntityToken entity, String flag, boolean permanent, Object ... stages) {
1587                setFlag(entity.getMemoryWithoutUpdate(), flag, null, permanent, stages);
1588        }
1589        public void setFlag(PersonAPI person, String flag, boolean permanent, Object ... stages) {
1590                setFlag(person.getMemoryWithoutUpdate(), flag, null, permanent, stages);
1591        }
1592        
1593        public void setGlobalFlag(String flag, Object value, Object ... stages) {
1594                setFlag(getGlobalMemory(), flag, value, false, stages);
1595        }
1596        
1597        public void setFlag(MemoryAPI memory, String flag, Object value, boolean permanent) {
1598                setFlag(memory, flag, value, permanent, (Object []) null);
1599        }
1600        
1601        
1602        public void setFlag(MemoryAPI memory, String flag, Object value, boolean permanent, Object ... stages) {
1603                if (stages != null && stages.length > 0) {
1604                        FlagData fd = new FlagData();
1605                        fd.memory = memory;
1606                        fd.flag = flag;
1607                        fd.value = value;
1608                        fd.stages.addAll(Arrays.asList(stages));
1609                        flags.add(fd);
1610                        if (fd.stages.contains(currentStage)) {
1611                                removeMemoryFlagChanges(fd.memory, fd.flag);
1612                                if (fd.value == null) {
1613                                        fd.memory.set(fd.flag, true);
1614                                } else {
1615                                        fd.memory.set(fd.flag, fd.value);
1616                                }
1617                                changes.add(new VariableSet(fd.memory, fd.flag, true));
1618                        }
1619                } else {
1620                        if (value == null) {
1621                                memory.set(flag, true);
1622                        } else {
1623                                memory.set(flag, value);
1624                        }
1625                        changes.add(new VariableSet(memory, flag, !permanent));
1626                }
1627        }
1628        
1629        public void setMusic(MarketAPI market, String musicSetId, Object ... stages) {
1630                setMusic(market.getMemoryWithoutUpdate(), musicSetId, stages);
1631        }
1632        public void setMusic(StarSystemAPI system, String musicSetId, Object ... stages) {
1633                setMusic(system.getMemoryWithoutUpdate(), musicSetId, stages);
1634        }
1635        public void setMusic(SectorEntityToken entity, String musicSetId, Object ... stages) {
1636                setMusic(entity.getMemoryWithoutUpdate(), musicSetId, stages);
1637        }
1638        public void setMusic(MemoryAPI memory, String musicSetId, Object ... stages) {
1639                setFlag(memory, MusicPlayerPluginImpl.MUSIC_SET_MEM_KEY_MISSION, musicSetId, false, stages);
1640        }
1641        
1642        public boolean setGlobalReference(String key) {
1643                if (getGlobalMemory().contains(key)) {
1644                        return false;
1645                }
1646                getGlobalMemory().set(key, this);
1647                changes.add(new VariableSet(getGlobalMemory(), key, true));
1648                return true;
1649        }
1650        
1651        public boolean setGlobalReference(String refKey, String inProgressFlag) {
1652                if (getGlobalMemory().contains(refKey)) {
1653                        return false;
1654                }
1655                getGlobalMemory().set(refKey, this);
1656                changes.add(new VariableSet(getGlobalMemory(), refKey, true));
1657                
1658                if (inProgressFlag != null) {
1659                        getGlobalMemory().set(inProgressFlag, true);
1660                        changes.add(new VariableSet(getGlobalMemory(), inProgressFlag, true));
1661                }
1662                return true;
1663        }
1664        
1665        public boolean setPersonMissionRef(PersonAPI person, String key) {
1666                if (person == null) return false;
1667                if (person.getMemoryWithoutUpdate().contains(key)) {
1668                        return false;
1669                }
1670                person.getMemoryWithoutUpdate().set(key, this);
1671                changes.add(new VariableSet(person.getMemoryWithoutUpdate(), key, true));
1672                return true;
1673        }
1674        
1675        public boolean setFactionMissionRef(FactionAPI faction, String key) {
1676                if (faction == null) return false;
1677                if (faction.getMemoryWithoutUpdate().contains(key)) {
1678                        return false;
1679                }
1680                faction.getMemoryWithoutUpdate().set(key, this);
1681                changes.add(new VariableSet(faction.getMemoryWithoutUpdate(), key, true));
1682                return true;
1683        }
1684        
1685        public boolean setMarketMissionRef(MarketAPI market, String key) {
1686                if (market == null) return false;
1687                if (market.getMemoryWithoutUpdate().contains(key)) {
1688                        return false;
1689                }
1690                market.getMemoryWithoutUpdate().set(key, this);
1691                changes.add(new VariableSet(market.getMemoryWithoutUpdate(), key, true));
1692                return true;
1693        }
1694        
1695        public boolean setEntityMissionRef(SectorEntityToken entity, String key) {
1696                if (entity == null) return false;
1697                if (entity.getMemoryWithoutUpdate().contains(key)) {
1698                        return false;
1699                }
1700                entity.getMemoryWithoutUpdate().set(key, this);
1701                changes.add(new VariableSet(entity.getMemoryWithoutUpdate(), key, true));
1702                return true;
1703        }
1704        
1705        public MemoryAPI getGlobalMemory() {
1706                return Global.getSector().getMemoryWithoutUpdate();
1707        }
1708        
1709        public void makeImportantDoNotShowAsIntelMapLocation(PersonAPI person, String flag, Enum ... stages) {
1710                makeImportant(person.getMemoryWithoutUpdate(), flag, null, person, stages);
1711        }
1712        public void makeImportantDoNotShowAsIntelMapLocation(SectorEntityToken entity, String flag, Enum ... stages) {
1713                makeImportant(entity.getMemoryWithoutUpdate(), flag, null, entity, stages);
1714        }
1715        public void makeImportantDoNotShowAsIntelMapLocation(MarketAPI market, String flag, Enum ... stages) {
1716                makeImportant(market.getMemoryWithoutUpdate(), flag, null, market, stages);
1717        }
1718        public void makeImportant(PersonAPI person, String flag, Enum ... stages) {
1719                makeImportant(person.getMemoryWithoutUpdate(), flag, MapLocationType.NORMAL, person, stages);
1720        }
1721        public void makeImportant(SectorEntityToken entity, String flag, Enum ... stages) {
1722                makeImportant(entity.getMemoryWithoutUpdate(), flag, MapLocationType.NORMAL, entity, stages);
1723        }
1724        public void makeImportant(MarketAPI market, String flag, Enum ... stages) {
1725                makeImportant(market.getMemoryWithoutUpdate(), flag, MapLocationType.NORMAL, market, stages);
1726        }
1727        public void makeImportant(MemoryAPI memory, String flag, MapLocationType type, Object personOrEntityOrMarket, Enum ... stages) {
1728                boolean inCurrentStage = false;
1729                if (stages != null) {
1730                        for (Object id : stages) {
1731                                if (currentStage != null && id == currentStage) inCurrentStage = true;
1732                                ImportanceData data = new ImportanceData();
1733                                data.memory = memory;
1734                                data.flag = flag;
1735                                data.locType = type;
1736                                if (personOrEntityOrMarket instanceof PersonAPI) {
1737                                        data.person = (PersonAPI) personOrEntityOrMarket;
1738                                } else if (personOrEntityOrMarket instanceof SectorEntityToken) {
1739                                        data.entity = (SectorEntityToken) personOrEntityOrMarket;
1740                                } else if (personOrEntityOrMarket instanceof MarketAPI) {
1741                                        data.market = (MarketAPI) personOrEntityOrMarket;
1742                                }
1743                                getData(id).important.add(data);
1744                        }
1745                } else {
1746                        inCurrentStage = true;
1747                }
1748                
1749                if (inCurrentStage) {
1750                        Misc.makeImportant(memory, getReason());
1751                        if (stages != null) {
1752                                changes.add(new MadeImportant(memory, getReason()));
1753                        }
1754                        if (flag != null) {
1755                                memory.set(flag, true);
1756                                if (stages != null) {
1757                                        changes.add(new VariableSet(memory, flag, true));
1758                                }
1759                        }
1760                }
1761        }
1762        
1763        public void makeUnimportant(PersonAPI person, Enum ... stages) {
1764                if (person == null) return;
1765                makeUnimportant(person.getMemoryWithoutUpdate(), person, stages);
1766        }
1767        public void makeUnimportant(SectorEntityToken entity, Enum ... stages) {
1768                if (entity == null) return;
1769                makeUnimportant(entity.getMemoryWithoutUpdate(), entity, stages);
1770        }
1771        public void makeUnimportant(MarketAPI market, Enum ... stages) {
1772                if (market == null) return;
1773                makeUnimportant(market.getMemoryWithoutUpdate(), market, stages);
1774        }
1775        public void makeUnimportant(PersonAPI person) {
1776                if (person == null) return;
1777                makeUnimportant(person.getMemoryWithoutUpdate(), person, (Enum []) null);
1778        }
1779        public void makeUnimportant(SectorEntityToken entity) {
1780                if (entity == null) return;
1781                makeUnimportant(entity.getMemoryWithoutUpdate(), entity, (Enum []) null);
1782        }
1783        public void makeUnimportant(MarketAPI market) {
1784                if (market == null) return;
1785                makeUnimportant(market.getMemoryWithoutUpdate(), market, (Enum []) null);
1786        }
1787        public void makeUnimportant(MemoryAPI memory, Object personOrEntityOrMarket) {
1788                makeUnimportant(memory, personOrEntityOrMarket, (Enum []) null);
1789        }
1790        public void makeUnimportant(MemoryAPI memory, Object personOrEntityOrMarket, Enum ... stages) {
1791                List<StageData> list = new ArrayList<BaseHubMission.StageData>();
1792                if (stages != null) {
1793                        for (Object id : stages) {
1794                                StageData stageData = getData(id);
1795                                list.add(stageData);
1796                        }
1797                } else {
1798                        list.addAll(this.stages.values());
1799                }
1800                
1801                for (StageData stageData : list) {
1802                        Iterator<ImportanceData> iter = stageData.important.iterator();
1803                        while (iter.hasNext()) {
1804                                ImportanceData data = iter.next();
1805                                if (data.memory == memory || 
1806                                                data.person == personOrEntityOrMarket ||
1807                                                data.entity == personOrEntityOrMarket ||
1808                                                data.market == personOrEntityOrMarket) {
1809                                        iter.remove();
1810                                }
1811                        }
1812                }
1813                Misc.makeUnimportant(memory, getReason());
1814        }
1815        
1816
1817// too easy to mix up with the other method
1818//      public void setTimeLimit(Object failStage, float days, Object ... noLimitAfterStages) {
1819//              setTimeLimit(failStage, days, null, noLimitAfterStages);
1820//      }
1821        public void setTimeLimit(Object failStage, float days, StarSystemAPI noLimitWhileInSystem, Object ... noLimitAfterStages) {
1822                timeLimit = new TimeLimitData();
1823                timeLimit.days = days;
1824                timeLimit.failStage = failStage;
1825                timeLimit.noLimitWhileInSystem = noLimitWhileInSystem;
1826                if (noLimitAfterStages != null) {
1827                        for (Object stage : noLimitAfterStages) {
1828                                timeLimit.endLimitStages.add(stage);
1829                        }
1830                }
1831        }
1832        
1833        public HubMissionResult getResult() {
1834                return result;
1835        }
1836
1837
1838        public int genRoundNumber(int min, int max) {
1839                int result = min + genRandom.nextInt(max - min + 1);
1840                return getRoundNumber(result);
1841        }
1842                
1843        public static int getRoundNumber(float num) {
1844                int num2 = (int) num;
1845                for (int i = 1; i < 10; i++) {
1846                        int threshold = (int) Math.pow(10, i);
1847                        int base = threshold / 10;
1848                        if (num2 > threshold) {
1849                                num2 = num2 / base * base;
1850                        }
1851                }
1852                return (int) num2;
1853        }
1854        public void setCreditReward(int min, int max) {
1855                setCreditReward(min, max, true);
1856        }
1857        public void setCreditReward(int min, int max, boolean withMult) {
1858                int reward = min + genRandom.nextInt(max - min + 1);
1859                if (withMult) {
1860                        reward = getRoundNumber(reward * rewardMult);
1861                }
1862                reward = reward / 1000 * 1000;
1863                if (reward > 100000) {
1864                        reward = reward / 10000 * 10000;
1865                }
1866                setCreditReward(reward);
1867        }
1868        
1869        public void setCreditReward(Integer creditReward) {
1870                this.creditReward = creditReward;
1871        }
1872        
1873        public void setCreditRewardApplyRelMult(Integer creditReward) {
1874                creditReward = getRoundNumber(creditReward * rewardMult);
1875                this.creditReward = creditReward;
1876        }
1877        
1878        public void setCreditReward(CreditReward reward) {
1879                setCreditReward(reward.min, reward.max);
1880        }
1881        public void setCreditReward(CreditReward reward, int marketSize) {
1882                setCreditReward(reward.min / 2 + reward.perMarketSize * Math.max(0, marketSize - 3), 
1883                                            reward.max / 2 + reward.perMarketSize * Math.max(0, marketSize - 3));
1884        }
1885        
1886        public void setCreditRewardWithBonus(CreditReward reward, int bonus) {
1887                setCreditReward(reward.min + bonus, reward.max + bonus);
1888        }
1889        
1890        public int getRewardBonusForMarines(int marines) {
1891                return marines * EXTRA_REWARD_PER_MARINE;
1892        }
1893        
1894//      public void setXPReward(XPReward reward) {
1895//              float f = reward.getFractionOfLevel();
1896//              LevelupPlugin plugin = Global.getSettings().getLevelupPlugin();
1897//              
1898//              int level = Global.getSector().getPlayerStats().getLevel();
1899//              if (level < MIN_LEVEL_TO_SCALE_XP_GAIN_AT) level = MIN_LEVEL_TO_SCALE_XP_GAIN_AT;
1900//              if (level > MAX_LEVEL_TO_SCALE_XP_GAIN_AT) level = MAX_LEVEL_TO_SCALE_XP_GAIN_AT;
1901//              
1902//              int xp = (int) plugin.getXPForNextLevel(level);
1903//              xp *= f;
1904//              
1905//              setXPReward(xp);
1906//      }
1907        
1908        public void setXPReward(int xpReward) {
1909                this.xpReward = xpReward;
1910                if (this.xpReward <= 0) this.xpReward = null;
1911        }
1912        
1913
1914        public void setRepPersonChangesNone() {
1915                setRepRewardPerson(0f);
1916                setRepPenaltyPerson(0f);
1917        }
1918        
1919        public void setRepFactionChangesNone() {
1920                setRepRewardFaction(0f);
1921                setRepPenaltyFaction(0f);
1922        }
1923        
1924        public void setRepPersonChangesTiny() {
1925                setRepRewardPerson(RepRewards.TINY);
1926                setRepPenaltyPerson(0f);
1927        }
1928        public void setRepFactionChangesTiny() {
1929                setRepRewardFaction(RepRewards.TINY);
1930                setRepPenaltyFaction(0f);
1931        }
1932        public void setRepPersonChangesVeryLow() {
1933                setRepRewardPerson(RepRewards.SMALL);
1934                setRepPenaltyPerson(RepRewards.TINY);
1935        }
1936        public void setRepFactionChangesVeryLow() {
1937                setRepRewardFaction(RepRewards.SMALL);
1938                setRepPenaltyFaction(RepRewards.TINY);
1939        }
1940        public void setRepPersonChangesLow() {
1941                setRepRewardPerson(RepRewards.MEDIUM);
1942                setRepPenaltyPerson(RepRewards.TINY);
1943        }
1944        public void setRepFactionChangesLow() {
1945                setRepRewardFaction(RepRewards.MEDIUM);
1946                setRepPenaltyFaction(RepRewards.TINY);
1947        }
1948        public void setRepPersonChangesMedium() {
1949                setRepRewardPerson(RepRewards.HIGH);
1950                setRepPenaltyPerson(RepRewards.SMALL);
1951        }
1952        public void setRepFactionChangesMedium() {
1953                setRepRewardFaction(RepRewards.HIGH);
1954                setRepPenaltyFaction(RepRewards.SMALL);
1955        }
1956        public void setRepPersonChangesHigh() {
1957                setRepRewardPerson(RepRewards.VERY_HIGH);
1958                setRepPenaltyPerson(RepRewards.MEDIUM);
1959        }
1960        public void setRepFactionChangesHigh() {
1961                setRepRewardFaction(RepRewards.VERY_HIGH);
1962                setRepPenaltyFaction(RepRewards.MEDIUM);
1963        }
1964        public void setRepPersonChangesVeryHigh() {
1965                setRepRewardPerson(RepRewards.EXTREME);
1966                setRepPenaltyPerson(RepRewards.MEDIUM);
1967        }
1968        public void setRepFactionChangesVeryHigh() {
1969                setRepRewardFaction(RepRewards.EXTREME);
1970                setRepPenaltyFaction(RepRewards.MEDIUM);
1971        }
1972        
1973        public void setRepChanges(float repRewardPerson, float repPenaltyPerson, 
1974                                                          float repRewardFaction, float repPenaltyFaction) {
1975                setRepRewardPerson(repRewardPerson);
1976                setRepPenaltyPerson(repPenaltyPerson);
1977                setRepRewardFaction(repRewardFaction);
1978                setRepPenaltyFaction(repPenaltyFaction);
1979        }
1980        public void setNoRepChanges() {
1981                setRepChanges(0, 0, 0, 0);
1982        }
1983        
1984        public void setRepRewardPerson(Float repRewardPerson) {
1985                this.repRewardPerson = repRewardPerson;
1986        }
1987
1988        public void setRepPenaltyPerson(Float repPenaltyPerson) {
1989                this.repPenaltyPerson = repPenaltyPerson;
1990        }
1991
1992        public void setRepRewardFaction(Float repRewardFaction) {
1993                this.repRewardFaction = repRewardFaction;
1994        }
1995
1996        public void setRepPenaltyFaction(Float repPenaltyFaction) {
1997                this.repPenaltyFaction = repPenaltyFaction;
1998        }
1999
2000        public void setPenaltyLimitPerson(RepLevel penaltyLimitPerson) {
2001                this.penaltyLimitPerson = penaltyLimitPerson;
2002        }
2003
2004        public void setPenaltyLimitFaction(RepLevel penaltyLimitFaction) {
2005                this.penaltyLimitFaction = penaltyLimitFaction;
2006        }
2007        
2008        public static boolean playerLevelIsAtLeast(int level) {
2009                return Global.getSector().getPlayerStats().getLevel() >= level;
2010        }
2011        
2012        public static boolean playerLevelIsMaxed() {
2013                int max = Global.getSettings().getLevelupPlugin().getMaxLevel();
2014                return Global.getSector().getPlayerStats().getLevel() >= max;
2015        }
2016        
2017        public static int getMaxPlayerLevel() {
2018                return Global.getSettings().getLevelupPlugin().getMaxLevel();
2019        }
2020        
2021        public static boolean isDevMode() {
2022                return Global.getSettings().isDevMode();
2023        }
2024        
2025        protected Set<String> addedTags = null;
2026        public void addTag(String tag) {
2027                if (addedTags == null) addedTags = new HashSet<String>();
2028                addedTags.add(tag);
2029        }
2030        
2031        @Override
2032        public Set<String> getIntelTags(SectorMapAPI map) {
2033                Set<String> tags = super.getIntelTags(map);
2034                tags.add(Tags.INTEL_MISSIONS);
2035                tags.add(Tags.INTEL_ACCEPTED);
2036                if (getFactionForUIColors() != null && !getFactionForUIColors().isPlayerFaction()) {
2037                        tags.add(getFactionForUIColors().getId());
2038                }
2039                if (addedTags != null) {
2040                        tags.addAll(addedTags);
2041                }
2042                return tags;
2043        }
2044        
2045        public SectorEntityToken getMapLocationFor(SectorEntityToken entity) {
2046                if ((entity.isDiscoverable() || 
2047                                (entity instanceof CampaignFleetAPI && !((CampaignFleetAPI)entity).isVisibleToPlayerFleet())) && entity.getStarSystem() != null) {
2048                        return entity.getStarSystem().getCenter();
2049                }
2050                return entity;
2051        }
2052        
2053        @Override
2054        public SectorEntityToken getMapLocation(SectorMapAPI map) {
2055                if (currentStage == null) return null;
2056
2057                return getMapLocation(map, currentStage);
2058        }
2059        
2060        public void makePrimaryObjective(Object personOrMarketOrEntity) {
2061                StageData stage = getData(currentStage);
2062                ImportanceData data = null;
2063                for (ImportanceData curr : stage.important) {
2064                        if (curr.locType == null) continue;
2065                        if (curr.entity == personOrMarketOrEntity || 
2066                                        curr.market == personOrMarketOrEntity ||
2067                                        curr.person == personOrMarketOrEntity) {
2068                                data = curr;
2069                                break;
2070                        }
2071                }
2072                if (data != null) {
2073                        stage.important.remove(data);
2074                        stage.important.add(0, data);
2075                }
2076        }
2077        
2078        public SectorEntityToken getMapLocation(SectorMapAPI map, Object currentStage) {
2079                if (currentStage == null) {
2080                        currentStage = startingStage;
2081                }
2082                StageData stage = getData(currentStage);
2083                ImportanceData data = null;
2084                for (ImportanceData curr : stage.important) {
2085                        if (curr.locType == null) continue;
2086                        if (curr.entity != null && !curr.entity.isAlive()) continue;
2087                        data = curr;
2088                        break;
2089                }
2090                if (data == null || data.locType == null) return null;
2091                
2092                SectorEntityToken entity = data.entity;
2093                if (entity == null && data.person != null && data.person.getMarket() != null) {
2094                        entity = data.person.getMarket().getPrimaryEntity();
2095                }
2096                if (entity == null && data.market!= null) {
2097                        entity = data.market.getPrimaryEntity();
2098                }
2099                if (entity == null) return null;
2100                
2101                if (data.locType == MapLocationType.NORMAL) {
2102                        if ((entity.isDiscoverable() || 
2103                                        (entity instanceof CampaignFleetAPI && !((CampaignFleetAPI)entity).isVisibleToPlayerFleet())) && entity.getStarSystem() != null) {
2104                                return entity.getStarSystem().getCenter();
2105                        }
2106                        return entity;
2107                } else if (data.locType == MapLocationType.CONSTELLATION) {
2108                        Constellation c = entity.getConstellation();
2109                        SectorEntityToken result = null;
2110                        if (c != null && map != null) {
2111                                result = map.getConstellationLabelEntity(c);
2112                        }
2113                        if (result == null) result = entity;
2114                        return result;
2115                }
2116                
2117                return entity;
2118        }
2119
2120        public String getSortString() {
2121                if (getTagsForSort().contains(Tags.INTEL_EXPLORATION) && getIntelTags(null).contains(Tags.INTEL_EXPLORATION)) {
2122                        return getSortStringNewestFirst();
2123                }
2124                return getBaseName();
2125        }
2126        
2127        public void setIconName(String iconName) {
2128                this.iconName = iconName;
2129        }
2130        public void setIconName(String category, String id) {
2131                this.iconName = Global.getSettings().getSpriteName(category, id);
2132        }
2133        
2134        public String getPostfixForState() {
2135                if (isEnding()) {
2136                        if (isSucceeded()) {
2137                                return " - Completed";  
2138                        } else if (isFailed()) {
2139                                return " - Failed";
2140                        } else if (isAbandoned()) {
2141                                return " - Abandoned";
2142                        }
2143                        return " - Ended";
2144                }
2145                if (startingStage != null) {
2146                        return " - Accepted";
2147                }
2148                return "";
2149        }
2150        
2151        public String getName() {
2152                return getBaseName() + (getPostfixForState() == null ? "" : getPostfixForState());
2153        }
2154        
2155        @Override
2156        public FactionAPI getFactionForUIColors() {
2157                if (getPerson() == null) return Global.getSector().getPlayerFaction();
2158                return getPerson().getFaction();
2159        }
2160
2161        public String getSmallDescriptionTitle() {
2162                return getName();
2163        }
2164        
2165        protected boolean isSucceeded() {
2166                return successStages.contains(currentStage);
2167        }
2168        protected boolean isFailed() {
2169                return failStages.contains(currentStage);
2170        }
2171        protected boolean isAbandoned() {
2172                return abandonStage == currentStage;
2173        }
2174        
2175        public String getIcon() {
2176                if (iconName != null) return iconName;
2177                return getPerson().getPortraitSprite();
2178        }
2179        
2180        @Override
2181        public String getImportantIcon() {
2182                if (!isEnding() && !isEnded()) {
2183                        return Global.getSettings().getSpriteName("intel", "important_accepted_mission");
2184                }
2185                return super.getImportantIcon();
2186        }
2187        
2188        protected void addResultBulletsAssumingAlreadyIndented(TooltipMakerAPI info, ListInfoMode mode) {
2189                if (result == null) return;
2190                if (mode == ListInfoMode.INTEL) return; // don't show result stuff when it's in the intel list or in a textPanel
2191                
2192                Color h = Misc.getHighlightColor();
2193                Color tc = getBulletColorForMode(mode);
2194                PersonAPI person = getPerson();
2195                FactionAPI faction = getFactionForUIColors();
2196                boolean isUpdate = getListInfoParam() != null;
2197                float initPad = 3f;
2198                if (mode == ListInfoMode.IN_DESC) initPad = 10f;
2199                
2200                if (result.reward > 0) {
2201                        info.addPara("%s received", initPad, tc, h, Misc.getDGSCredits(result.reward));
2202                        initPad = 0f;
2203                }
2204                
2205                if (result.repPerson != null) {
2206                        CoreReputationPlugin.addAdjustmentMessage(result.repPerson.delta, null, person, 
2207                                                                                                  null, info, tc, isUpdate, initPad);
2208                        initPad = 0f;
2209                }
2210                if (result.repFaction != null) {
2211                        CoreReputationPlugin.addAdjustmentMessage(result.repFaction.delta, faction, null, 
2212                                                                                                  null, info, tc, isUpdate, initPad);
2213                        initPad = 0f;
2214                }
2215        }
2216        
2217        public static String NEXT_STEP_UPDATE = "next_step_update";
2218        public static String END_MISSION_UPDATE = "end_mission_update";
2219        public void sendUpdateForNextStep(String listInfoParam, TextPanelAPI textPanel) {
2220                if (textPanel == null) {
2221                        sendUpdateIfPlayerHasIntel(listInfoParam, false);
2222                } else {
2223                        this.listInfoParam = listInfoParam;
2224                        Global.getSector().getIntelManager().addIntelToTextPanel(this, textPanel);
2225                        this.listInfoParam = null;
2226                }
2227        }
2228        
2229        public void sendUpdateToTextPanel(String listInfoParam, TextPanelAPI textPanel) {
2230                this.listInfoParam = listInfoParam;
2231                Global.getSector().getIntelManager().addIntelToTextPanel(this, textPanel);
2232                this.listInfoParam = null;
2233        }
2234        
2235        protected void addBulletPointsPre(TooltipMakerAPI info, Color tc, float initPad, ListInfoMode mode) {
2236                
2237        }
2238        protected void addBulletPointsPost(TooltipMakerAPI info, Color tc, float initPad, ListInfoMode mode) {
2239                
2240        }
2241        
2242        protected String getToCompleteText() {
2243                return "to complete";
2244        }
2245        
2246        protected void addBulletPoints(TooltipMakerAPI info, ListInfoMode mode) {
2247                
2248                Color h = Misc.getHighlightColor();
2249                Color g = Misc.getGrayColor();
2250                float pad = 3f;
2251                float opad = 10f;
2252                
2253                float initPad = pad;
2254                if (mode == ListInfoMode.IN_DESC) initPad = opad;
2255                
2256                Color tc = getBulletColorForMode(mode);
2257                
2258                bullet(info);
2259                
2260                addBulletPointsPre(info, tc, initPad, mode);
2261                
2262                boolean isUpdate = getListInfoParam() != null;
2263                
2264                PersonAPI person = getPerson();
2265                FactionAPI faction = getFactionForUIColors();
2266                
2267                if (isUpdate) {
2268                        // Possible updates: failed, completed, next step
2269                        if (getListInfoParam() == NEXT_STEP_UPDATE) {
2270                                if (addNextStepText(info, tc, initPad)) {
2271                                        initPad = 0f;
2272                                }
2273                        } else if (isFailed()) {
2274                                addResultBulletsAssumingAlreadyIndented(info, mode);
2275                                addNextStepText(info, tc, initPad);
2276                        } else if (isSucceeded()) {
2277                                addResultBulletsAssumingAlreadyIndented(info, mode);
2278                                addNextStepText(info, tc, initPad);
2279                        } else {
2280                                addNextStepText(info, tc, initPad);
2281                        }
2282                } else {
2283                        // either in small description, or in tooltip/intel list
2284                        if (result != null) {
2285                                if (mode == ListInfoMode.IN_DESC) {
2286                                        addResultBulletsAssumingAlreadyIndented(info, mode);
2287                                }
2288                        } else {
2289                                if (mode == ListInfoMode.IN_DESC) {
2290//                                      if (addNextStepText(info, tc, initPad)) {
2291//                                              initPad = 0f;
2292//                                      }
2293                                        
2294                                        int reward = getCreditsReward();
2295                                        if (reward > 0) {
2296                                                info.addPara("%s reward", initPad, tc, h, Misc.getDGSCredits(reward));
2297                                                initPad = 0f;
2298                                        }
2299                                        if (timeLimit != null) {
2300                                                addDays(info, getToCompleteText(), timeLimit.days - elapsed, tc, initPad);
2301                                                initPad = 0f;
2302                                        }       
2303                                } else {
2304                                        if (addNextStepText(info, tc, initPad)) {
2305                                                initPad = 0f;
2306                                        }
2307//                                      int reward = getCreditsReward();
2308//                                      if (reward > 0) {
2309//                                              info.addPara("%s reward", initPad, tc, h, Misc.getDGSCredits(reward));
2310//                                              initPad = 0f;
2311//                                      }
2312                                        if (timeLimit != null) {
2313                                                addDays(info, getToCompleteText(), timeLimit.days - elapsed, tc, initPad);
2314                                                initPad = 0f;
2315                                        }
2316                                }
2317                        }
2318                }
2319                
2320                addBulletPointsPost(info, tc, initPad, mode);
2321                
2322                unindent(info);
2323                
2324        }
2325        
2326        @Override
2327        public IntelSortTier getSortTier() {
2328                if (sortTier == null || isEnding() || isEnded()) return super.getSortTier();
2329                return sortTier;
2330        }
2331        
2332        protected Boolean largeTitleFont;
2333        public void setUseLargeFontInMissionList() {
2334                largeTitleFont = true;
2335                sortTier = IntelSortTier.TIER_2;
2336        }
2337        
2338        @Override
2339        public void createIntelInfo(TooltipMakerAPI info, ListInfoMode mode) {
2340                Color c = getTitleColor(mode);
2341                boolean large = largeTitleFont != null && largeTitleFont;
2342                if (large) info.setParaSmallInsignia();
2343                info.addPara(getName(), c, 0f);
2344                if (large) info.setParaFontDefault();
2345                
2346                addBulletPoints(info, mode);
2347        }
2348        
2349        @Override
2350        public void createSmallDescription(TooltipMakerAPI info, float width, float height) {
2351                Color h = Misc.getHighlightColor();
2352                Color g = Misc.getGrayColor();
2353                float pad = 3f;
2354                float opad = 10f;
2355                
2356                FactionAPI faction = getFactionForUIColors();
2357                PersonAPI person = getPerson();
2358                
2359                if (person != null) {
2360                        info.addImages(width, 128, opad, opad, person.getPortraitSprite(), faction.getCrest());
2361                        
2362                        String post = "one";
2363                        if (person.getPost() != null) post = person.getPost().toLowerCase(); 
2364                        if (post == null && person.getRank() != null) post = person.getRank().toLowerCase(); 
2365                        info.addPara(Misc.ucFirst(getMissionTypeNoun()) + " given by " + post + " " + person.getNameString() + ", affiliated with " + 
2366                                        faction.getDisplayNameWithArticle() + ".",
2367                                        opad, faction.getBaseUIColor(), faction.getDisplayNameWithArticleWithoutArticle());
2368                }
2369//              info.addPara("Given by: " + person.getNameString() + ", " + 
2370//                              faction.getDisplayName() + "-affiliated.",
2371//                              opad, faction.getBaseUIColor(), faction.getDisplayName());
2372                
2373                addDescriptionForCurrentStage(info, width, height);
2374                
2375                addBulletPoints(info, ListInfoMode.IN_DESC);
2376                
2377                if (abandonStage != null && !isAbandoned() && !isSucceeded() && !isFailed()) {
2378                        addAbandonButton(info, width);
2379                }
2380        }
2381        
2382        
2383
2384        
2385        
2386        
2387        public void setAbandonStage(Object abandonStage) {
2388                this.abandonStage = abandonStage;
2389        }
2390        public void setNoAbandon() {
2391                this.abandonStage = null;
2392        }
2393
2394        @Override
2395        public boolean doesButtonHaveConfirmDialog(Object buttonId) {
2396                if (buttonId == BUTTON_ABANDON) {
2397                        return true;
2398                }
2399                return super.doesButtonHaveConfirmDialog(buttonId);
2400        }
2401        
2402        protected void addAbandonButton(TooltipMakerAPI info, float width) {
2403                addAbandonButton(info, width, "Abandon");
2404        }
2405        protected void addAbandonButton(TooltipMakerAPI info, float width, String abandon) {
2406                float opad = 10f;
2407                ButtonAPI button = info.addButton(abandon, BUTTON_ABANDON, 
2408                                getFactionForUIColors().getBaseUIColor(), getFactionForUIColors().getDarkUIColor(),
2409                                (int)(width), 20f, opad * 2f);
2410                button.setShortcut(Keyboard.KEY_U, true);
2411        }
2412        
2413        public boolean canAbandonWithoutPenalty() {
2414                return elapsed < getNoPenaltyAbandonDays();
2415        }
2416        
2417        protected float getNoPenaltyAbandonDays() {
2418                return 1f;
2419        }
2420        
2421        @Override
2422        public void buttonPressConfirmed(Object buttonId, IntelUIAPI ui) {
2423                if (buttonId == BUTTON_ABANDON) {
2424                        setImportant(false);
2425                        
2426                        setCurrentStage(abandonStage, null, null);
2427                        //endImmediately();
2428                        runTriggers();
2429                }
2430                super.buttonPressConfirmed(buttonId, ui);
2431        }
2432
2433
2434        @Override
2435        public void createConfirmationPrompt(Object buttonId, TooltipMakerAPI prompt) {
2436                FactionAPI faction = getFactionForUIColors();
2437                
2438                if (buttonId == BUTTON_ABANDON) {
2439                        boolean loseRepFaction = getRepPenaltyFailureFaction() > 0; 
2440                        boolean loseRepPerson = getRepPenaltyFailurePerson() > 0;
2441                        if (!loseRepFaction && !loseRepPerson) {
2442                                prompt.addPara("You can abandon this " + getMissionTypeNoun() + " without a penalty.", 0f);
2443                        } else if (canAbandonWithoutPenalty()) {
2444                                prompt.addPara("It's been less than a day, and you can still abandon this " + getMissionTypeNoun() + " without a penalty.", 0f);
2445                        } else {
2446                                if (loseRepFaction && !loseRepPerson) {
2447                                        prompt.addPara("You can abandon this " + getMissionTypeNoun() + ", but will suffer " +
2448                                                        "a reputation penalty with " + faction.getDisplayNameWithArticle() + ".", 0f,
2449                                                        Misc.getTextColor(), faction.getBaseUIColor(), faction.getDisplayNameWithArticleWithoutArticle());
2450                                } else if (!loseRepFaction && loseRepPerson) {
2451                                        prompt.addPara("You can abandon this " + getMissionTypeNoun() + ", but will suffer " +
2452                                                        "a reputation penalty with " + getPerson().getNameString() + ".", 
2453                                                        Misc.getTextColor(), 0f);
2454                                } else {
2455                                        prompt.addPara("You can abandon this " + getMissionTypeNoun() + ", but will suffer " +
2456                                                        "a reputation penalty with both " + getPerson().getNameString() + " and " + 
2457                                                        faction.getDisplayNameWithArticle() + ".", 0f,
2458                                                        Misc.getTextColor(), faction.getBaseUIColor(), faction.getDisplayNameWithArticleWithoutArticle());
2459                                }
2460                        }
2461                } else {
2462                        super.createConfirmationPrompt(buttonId, prompt);
2463                }
2464        }
2465        
2466        protected String getMissionTypeNoun() {
2467                return "mission";
2468        }
2469        
2470        protected String getMissionCompletionVerb() {
2471                return "completed";
2472        }
2473        
2474        public int getDistanceLY(MarketAPI market) {
2475                return getDistanceLY(market.getPrimaryEntity());
2476        }
2477        
2478        public int getDistanceLY(SectorEntityToken entity) {
2479                int dist = 0;
2480                if (getPerson() != null && getPerson().getMarket() != null) {
2481                        dist = (int) Math.round(Misc.getDistanceLY(getPerson().getMarket().getLocationInHyperspace(), entity.getLocationInHyperspace()));
2482                }
2483                return dist;
2484        }
2485        
2486        public int getDistanceLY(StarSystemAPI system) {
2487                int dist = 0;
2488                if (getPerson() != null && getPerson().getMarket() != null) {
2489                        dist = (int) Math.round(Misc.getDistanceLY(getPerson().getMarket().getLocationInHyperspace(), system.getLocation()));
2490                }
2491                return dist;
2492        }
2493        
2494        public int getFuel(SectorEntityToken entity, boolean bothWays) {
2495                int dist = getDistanceLY(entity);
2496                
2497                float fuel = Global.getSector().getPlayerFleet().getLogistics().getFuelCostPerLightYear();
2498                fuel *= dist;
2499                if (bothWays) fuel *= 2f;
2500                return (int) Math.round(fuel);
2501        }
2502        
2503        public Object pickOneObject(List options) {
2504                WeightedRandomPicker<Object> picker = new WeightedRandomPicker<Object>(genRandom);
2505                for (Object option : options) {
2506                        picker.add(option);
2507                }
2508                return picker.pick();
2509        }
2510        
2511        public String pickOne(List<String> options) {
2512                return pickOne(options.toArray(new String[0]));
2513        }
2514        public String pickOne(String ... options) {
2515                WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(genRandom);
2516                for (String option : options) {
2517                        picker.add(option);
2518                }
2519                return picker.pick();
2520        }
2521        
2522        protected String getWithoutArticle(String item) {
2523                if (item.startsWith("a ")) {
2524                        return item.replaceFirst("a ", "");
2525                }
2526                if (item.startsWith("an ")) {
2527                        return item.replaceFirst("an ", "");
2528                }
2529                if (item.startsWith("the ")) {
2530                        return item.replaceFirst("the ", "");
2531                }
2532                return item;
2533        }
2534        
2535        public void setStageOnGlobalFlag(Object to, String flag) {
2536                connectWithGlobalFlag(null, to, flag);
2537        }
2538        public void setStageOnEntityNotAlive(Object to, SectorEntityToken entity) {
2539                connectWithEntityNotAlive(null, to, entity);
2540        }
2541        public void setStageOnDaysElapsed(Object to, float days) {
2542                connectWithDaysElapsed(null, to, days);
2543        }
2544        public void setStageOnInRangeOfCommRelay(Object to) {
2545                connectWithInRangeOfCommRelay(null, to);
2546        }
2547        public void setStageOnEnteredLocation(Object to, LocationAPI location) {
2548                connectWithEnteredLocation(null, to, location);
2549        }
2550        public void setStageInRangeOfEntity(Object to, SectorEntityToken entity, float range) {
2551                connectWithInRangeOfEntity(null, to, entity, range);
2552        }
2553        public void setStageOnWithinHyperspaceRange(Object to, SectorEntityToken entity, float rangeLY) {
2554                connectWithWithinHyperspaceRange(null, to, entity, rangeLY);
2555        }
2556        public void setStageOnCustomCondition(Object to, ConditionChecker custom) {
2557                connections.add(new StageConnection(null, to, custom));
2558        }
2559        
2560        public void connectWithGlobalFlag(Object from, Object to, String flag) {
2561                connections.add(new StageConnection(from, to, new GlobalBooleanChecker(flag)));
2562                // so it gets auto-unset if it's ever set
2563                changes.add(new VariableSet(getGlobalMemory(), flag, true));
2564        }
2565        
2566        
2567        public void setStageOnMemoryFlag(Object to, HasMemory withMemory, String flag) {
2568                setStageOnMemoryFlag(to, withMemory.getMemoryWithoutUpdate(), flag);
2569        }
2570        public void connectWithMemoryFlag(Object from, Object to, HasMemory withMemory, String flag) {
2571                connectWithMemoryFlag(from, to, withMemory.getMemoryWithoutUpdate(), flag);
2572        }
2573        
2574        public void setStageOnMemoryFlag(Object to, MemoryAPI memory, String flag) {
2575                connectWithMemoryFlag(null, to, memory, flag);
2576        }
2577        public void connectWithMemoryFlag(Object from, Object to, MemoryAPI memory, String flag) {
2578                connections.add(new StageConnection(from, to, new MemoryBooleanChecker(memory, flag)));
2579                // so it gets auto-unset if it's ever set
2580                changes.add(new VariableSet(memory, flag, true));
2581        }
2582        
2583        public void connectWithEntityNotAlive(Object from, Object to, SectorEntityToken entity) {
2584                connections.add(new StageConnection(from, to, new EntityNotAliveChecker(entity)));
2585        }
2586        
2587        public void connectWithMarketDecivilized(Object from, Object to, MarketAPI market) {
2588                connections.add(new StageConnection(from, to, new MarketDecivChecker(market)));
2589        }
2590        public void setStageOnMarketDecivilized(Object to, MarketAPI market) {
2591                connections.add(new StageConnection(null, to, new MarketDecivChecker(market)));
2592        }
2593        
2594        public void connectWithHostilitiesEnded(Object from, Object to, PersonAPI person, MarketAPI market) {
2595                connections.add(new StageConnection(from, to, new HostilitiesEndedChecker(person, market)));
2596        }
2597        public void setStageOnHostilitiesEnded(Object to, PersonAPI person, MarketAPI market) {
2598                connections.add(new StageConnection(null, to, new HostilitiesEndedChecker(person, market)));
2599        }
2600        public void connectWithHostilitiesStarted(Object from, Object to, PersonAPI person, MarketAPI market) {
2601                connections.add(new StageConnection(from, to, new HostilitiesStartedChecker(person, market)));
2602        }
2603        public void setStageOnHostilitiesStarted(Object to, PersonAPI person, MarketAPI market) {
2604                connections.add(new StageConnection(null, to, new HostilitiesStartedChecker(person, market)));
2605        }
2606        
2607        public void connectWithDaysElapsed(Object from, Object to, float days) {
2608                connections.add(new StageConnection(from, to, new DaysElapsedChecker(days, getData(from))));
2609        }
2610        
2611        public void connectWithInRangeOfCommRelay(Object from, Object to) {
2612                connections.add(new StageConnection(from, to, new InCommRelayRangeChecker()));
2613        }
2614        
2615        public void connectWithEnteredLocation(Object from, Object to, LocationAPI location) {
2616                connections.add(new StageConnection(from, to, new EnteredLocationChecker(location)));
2617        }
2618        public void connectWithInRangeOfEntity(Object from, Object to, SectorEntityToken entity, float range) {
2619                connections.add(new StageConnection(from, to, new InRangeOfEntityChecker(entity, range)));
2620        }
2621        public void connectWithWithinHyperspaceRange(Object from, Object to, SectorEntityToken entity, float rangeLY) {
2622                connectWithWithinHyperspaceRange(from, to, entity, rangeLY, false);
2623        }
2624        public void connectWithWithinHyperspaceRange(Object from, Object to, SectorEntityToken entity, float rangeLY, 
2625                                                                                        boolean requirePlayerInHyperspace) {
2626                connections.add(new StageConnection(from, to, new InHyperRangeOfEntityChecker(entity, rangeLY, requirePlayerInHyperspace)));
2627        }
2628        
2629        public void connectWithCustomCondition(Object from, Object to, ConditionChecker custom) {
2630                connections.add(new StageConnection(from, to, custom));
2631        }
2632        
2633        public boolean rollProbability(float p) {
2634                return genRandom.nextFloat() < p;
2635        }
2636        
2637        
2638        public SectorEntityToken spawnDebrisField(float radius, float density, LocData data) {
2639                DebrisFieldParams params = new DebrisFieldParams(
2640                                radius, // field radius - should not go above 1000 for performance reasons
2641                                density, // density, visual - affects number of debris pieces
2642                                10000000f, // duration in days 
2643                                0f); // days the field will keep generating glowing pieces
2644                params.source = DebrisFieldSource.MIXED;
2645                params.baseSalvageXP = (long) radius; // base XP for scavenging in field
2646                
2647                if (!data.updateLocIfNeeded(this, null)) return null;
2648                
2649                SectorEntityToken debris = Misc.addDebrisField(data.system, params, genRandom);
2650                data.placeEntity(debris);
2651                changes.add(new EntityAdded(debris));
2652                
2653                return debris;
2654        }
2655        
2656        public SectorEntityToken spawnMissionNode(LocData data) {
2657                return spawnEntity(Entities.MISSION_LOCATION, data);
2658        }
2659        
2660        public void makeMissionNodeDiscoverable(SectorEntityToken node) {
2661                makeDiscoverable(node, 1000f, 200f);
2662        }
2663        public void makeDiscoverable(SectorEntityToken entity, float range, float xp) {
2664                entity.setDiscoveryXP(xp);
2665                entity.setSensorProfile(1f);
2666                entity.setDiscoverable(true);
2667                entity.getDetectedRangeMod().modifyFlat("gen", range);
2668        }
2669        
2670        public EntityLocation generateLocation(String entityId, EntityLocationType locType, SectorEntityToken param, LocationAPI system) {
2671                EntityLocation loc = null;
2672                float gap = 100f;
2673                if (system instanceof StarSystemAPI) {
2674                        if (locType == EntityLocationType.HIDDEN) {
2675                                loc = BaseThemeGenerator.pickHiddenLocation(genRandom, (StarSystemAPI)system, gap, null);
2676                        } else if (locType == EntityLocationType.HIDDEN_NOT_NEAR_STAR) {
2677                                loc = BaseThemeGenerator.pickHiddenLocationNotNearStar(genRandom, (StarSystemAPI)system, gap, null);
2678                        } else if (locType == EntityLocationType.ORBITING_PLANET) {
2679                                loc = BaseThemeGenerator.pickCommonLocation(genRandom, (StarSystemAPI)system, gap, false, null);
2680                        } else if (locType == EntityLocationType.ORBITING_PLANET_OR_STAR) {
2681                                loc = BaseThemeGenerator.pickCommonLocation(genRandom, (StarSystemAPI)system, gap, true, null);
2682                        } else if (locType == EntityLocationType.UNCOMMON) {
2683                                loc = BaseThemeGenerator.pickUncommonLocation(genRandom, (StarSystemAPI)system, gap, null);
2684                        } else if (locType == EntityLocationType.ANY) {
2685                                loc = BaseThemeGenerator.pickAnyLocation(genRandom, (StarSystemAPI)system, gap, null);
2686                        }
2687                }
2688                
2689                if (locType == EntityLocationType.ORBITING_PARAM) {
2690                        loc = BaseThemeGenerator.createLocationAtRandomGap(genRandom, param, gap);
2691                        if (loc == null) {
2692                                float radius = 75f;
2693                                if (entityId != null) {
2694                                        CustomEntitySpecAPI spec = Global.getSettings().getCustomEntitySpec(entityId);
2695                                        radius = spec.getDefaultRadius();
2696                                }
2697                                loc = new EntityLocation();
2698                                loc.type = LocationType.PLANET_ORBIT;
2699                                loc.orbit = Global.getFactory().createCircularOrbitWithSpin(param, 
2700                                                                        genRandom.nextFloat() * 360f, param.getRadius() + radius + 100f, 
2701                                                                        20f + 20f * genRandom.nextFloat(), genRandom.nextFloat() * 10f + 1f);
2702                        }
2703                }
2704                
2705                if (loc == null) {
2706                        if (system instanceof StarSystemAPI) {
2707                                loc = new EntityLocation();
2708                                loc.type = LocationType.STAR_ORBIT;
2709                                loc.orbit = Global.getFactory().createCircularOrbitWithSpin(((StarSystemAPI)system).getCenter(), 
2710                                                                        genRandom.nextFloat() * 360f, 5000f, 
2711                                                                        20f + 20f * genRandom.nextFloat(), genRandom.nextFloat() * 10f + 1f);
2712                        } else {
2713                                loc = new EntityLocation();
2714                                loc.type = LocationType.OUTER_SYSTEM;
2715                                loc.location = new Vector2f();
2716                        }
2717                }
2718                return loc;
2719        }
2720        
2721        public SectorEntityToken spawnEntity(String entityId, LocData data) {
2722                
2723                if (!data.updateLocIfNeeded(this, entityId)) return null;
2724                
2725                AddedEntity added = BaseThemeGenerator.addEntityAutoDetermineType(genRandom, data.system, data.loc, entityId, Factions.NEUTRAL);
2726                if (added == null) return null;
2727                
2728                if (data.removeOnMissionOver) {
2729                        added.entity.addTag(REMOVE_ON_MISSION_OVER);
2730                }
2731                
2732                added.entity.addTag(Tags.NOT_RANDOM_MISSION_TARGET);
2733                
2734                changes.add(new EntityAdded(added.entity));
2735                return added.entity;
2736        }
2737        
2738        public SectorEntityToken spawnEntityToken(LocData data) {
2739                if (!data.updateLocIfNeeded(this, null)) return null;
2740                
2741                SectorEntityToken token = data.system.createToken(0, 0);
2742                data.system.addEntity(token);
2743                data.placeEntity(token);
2744                changes.add(new EntityAdded(token));
2745                
2746                return token;
2747        }
2748        
2749        
2750        public SectorEntityToken spawnDerelictHull(String hullId, LocData data) {
2751                if (hullId == null) {
2752                        return spawnDerelictOfType((DerelictType) null, data);
2753                }
2754                DerelictShipData shipData = DerelictShipEntityPlugin.createHull(hullId, genRandom, DerelictShipEntityPlugin.getDefaultSModProb());
2755                return spawnDerelict(shipData, data);
2756        }
2757        
2758        public SectorEntityToken spawnDerelict(String factionId, DerelictType type, LocData data) {
2759                if (factionId == null) {
2760                        return spawnDerelictOfType(type, data);
2761                }
2762                DerelictShipData shipData = DerelictShipEntityPlugin.createRandom(factionId, type, genRandom, DerelictShipEntityPlugin.getDefaultSModProb());
2763                return spawnDerelict(shipData, data);
2764        }
2765        public SectorEntityToken spawnDerelictOfType(DerelictType type, LocData data) {
2766                WeightedRandomPicker<String> factions = SalvageSpecialAssigner.getNearbyFactions(genRandom, data.system.getLocation(),
2767                                                                                15f, 10f, 10f);
2768                DerelictShipData shipData = DerelictShipEntityPlugin.createRandom(factions.pick(), type, genRandom, DerelictShipEntityPlugin.getDefaultSModProb());
2769                return spawnDerelict(shipData, data);
2770        }
2771        
2772        public SectorEntityToken spawnDerelict(DerelictShipData shipData, LocData data) {
2773                if (shipData == null) return null;
2774                
2775                if (!data.updateLocIfNeeded(this, Entities.WRECK)) return null;
2776                
2777                SectorEntityToken entity = BaseThemeGenerator.addSalvageEntity(genRandom, data.system, Entities.WRECK, Factions.NEUTRAL, shipData);
2778                entity.setDiscoverable(true);
2779                data.placeEntity(entity);
2780                
2781                changes.add(new EntityAdded(entity));
2782                return entity;
2783        }
2784        
2785        public void spawnShipGraveyard(String factionId, int minShips, int maxShips, LocData data) {
2786                SectorEntityToken focus = spawnEntityToken(data);
2787                
2788                int numShips = minShips + genRandom.nextInt(maxShips - minShips + 1);
2789                
2790                WeightedRandomPicker<Float> bands = new WeightedRandomPicker<Float>(genRandom);
2791                for (int i = 0; i < numShips + 5; i++) {
2792                        bands.add(new Float(120f + i * 20f), (i + 1f) * (i + 1f));
2793                }
2794                
2795                for (int i = 0; i < numShips; i++) {
2796                        float r = bands.pickAndRemove();
2797                        
2798                        EntityLocation loc = new EntityLocation();
2799                        loc.type = LocationType.OUTER_SYSTEM;
2800                        float orbitDays = r / (5f + genRandom.nextFloat() * 10f);
2801                        loc.orbit = Global.getFactory().createCircularOrbit(focus, genRandom.nextFloat() * 360f, r, orbitDays);
2802                        
2803                        LocData curr = new LocData(loc, data.system, data.removeOnMissionOver);
2804                        
2805                        spawnDerelict(factionId, null, curr);
2806                }
2807        }
2808        
2809        
2810        protected PersonAPI findOrCreateTrader(String factionId, MarketAPI market, boolean cleanUpOnMissionOverIfWasNewPerson) {
2811                if (CheapCommodityMission.SAME_CONTACT_DEBUG) {
2812                        return findOrCreatePerson(factionId, market, cleanUpOnMissionOverIfWasNewPerson,
2813                                        Ranks.CITIZEN, 
2814                                        Ranks.POST_MERCHANT);   
2815                }
2816                return findOrCreatePerson(factionId, market, cleanUpOnMissionOverIfWasNewPerson,
2817                                        Ranks.CITIZEN, 
2818                                        Ranks.POST_MERCHANT,
2819                                        Ranks.POST_COMMODITIES_AGENT,
2820                                        Ranks.POST_INVESTOR,
2821                                        Ranks.POST_TRADER);
2822                                
2823        }
2824        
2825        protected PersonAPI findOrCreateCriminal(MarketAPI market, boolean cleanUpOnMissionOverIfWasNewPerson) {
2826                return findOrCreatePerson(Factions.PIRATES, market, cleanUpOnMissionOverIfWasNewPerson,
2827                                Ranks.CITIZEN, 
2828                                Ranks.POST_GANGSTER,
2829                                Ranks.POST_SMUGGLER,
2830                                Ranks.POST_FENCE);
2831                
2832        }
2833        
2834        protected PersonAPI findOrCreateCriminalTrader(MarketAPI market, boolean cleanUpOnMissionOverIfWasNewPerson) {
2835                return findOrCreatePerson(Factions.PIRATES, market, cleanUpOnMissionOverIfWasNewPerson,
2836                                Ranks.CITIZEN, 
2837                                Ranks.POST_SMUGGLER,
2838                                Ranks.POST_FENCE);
2839                
2840        }
2841        
2842        protected PersonAPI findOrCreatePerson(String factionId, MarketAPI market, boolean cleanUpOnMissionOverIfWasNewPerson, String defaultRank, String ... posts) {
2843                String reason = getReason();
2844                PersonAPI person = null;
2845                ImportantPeopleAPI ip = Global.getSector().getImportantPeople();
2846                
2847                
2848                FactionAPI faction = null;
2849                if (market != null) faction = market.getFaction();
2850                if (factionId != null) {
2851                        faction = Global.getSector().getFaction(factionId);
2852                }
2853                
2854                person = ip.getPerson(genRandom, faction, market, reason, defaultRank, posts).getPerson();
2855                
2856                boolean createdNewPerson = !ip.isLastGetPersonResultWasExistingPerson();
2857                
2858                if (person != null && !createdNewPerson &&
2859                                Misc.flagHasReason(person.getMemoryWithoutUpdate(), "$requiredForMissions", getReason())) {
2860                        // this can happen if the person was already created *for this exact type of mission*
2861                        // so, don't use them - they're already the target of the same mission and mission
2862                        // creation would fail later anyway.
2863                        // this also causes - when this mission is aborted - for that person to be removed from
2864                        // their market in this failed mission's abort(), because it's requiredForMissions with the
2865                        // same id as this.
2866                        person = null;
2867                }
2868                
2869                if (person == null) {
2870                        person = faction.createRandomPerson(genRandom);
2871                        WeightedRandomPicker<String> postPicker = new WeightedRandomPicker<String>(genRandom);
2872                        for (String post : posts) {
2873                                postPicker.add(post);
2874                        }
2875                        person.setPostId(postPicker.pick());
2876                        person.setRankId(defaultRank);
2877                        person.setMarket(market);
2878                        if (market != null) market.addPerson(person);
2879                        ip.addPerson(person);
2880                        ip.getData(person).getLocation().setMarket(market);
2881                }
2882                
2883                if (isBarEvent() || createdNewPerson) {
2884                        ip.excludeFromGetPerson(person);
2885                }
2886                
2887                boolean addedToComms = false;
2888                if (market != null && market.getCommDirectory().getEntryForPerson(person) == null) {
2889                        market.getCommDirectory().addPerson(person);
2890                        addedToComms = true;
2891                }
2892                
2893                boolean willBeRemoved = false;
2894                if (createdNewPerson || addedToComms) {
2895                        if (cleanUpOnMissionOverIfWasNewPerson) {
2896                                person.addTag(REMOVE_ON_MISSION_OVER);
2897                        }
2898                        PersonAdded added = new PersonAdded(market, person, !createdNewPerson); 
2899                        changes.add(added);
2900                        willBeRemoved = true;
2901                }
2902                
2903                makePersonRequired(person);
2904                if (!willBeRemoved && person.hasTag(REMOVE_ON_MISSION_OVER)) {
2905                        PersonAdded added = new PersonAdded(market, person, false);
2906                        changes.add(added);
2907                }
2908                
2909                person.setMarket(market);
2910                
2911                return person;
2912        }
2913        
2914        public void makePersonRequired(PersonAPI person) {
2915                PersonMadeRequired req = new PersonMadeRequired(person);
2916                // always add at the start so the flag is unset and the person can be deleted by the PersonAdded change
2917                changes.add(0, req);
2918                Misc.setFlagWithReason(person.getMemoryWithoutUpdate(), "$requiredForMissions", getReason(), true, -1f);
2919        }
2920        
2921        protected void ensurePersonIsInCommDirectory(MarketAPI market, PersonAPI person) {
2922                boolean addedToComms = false;
2923                if (market != null && market.getCommDirectory().getEntryForPerson(person) == null) {
2924                        market.getCommDirectory().addPerson(person);
2925                        addedToComms = true;
2926                }
2927                
2928                if (addedToComms) {
2929                        PersonAdded added = new PersonAdded(market, person, true); 
2930                        changes.add(added);
2931                }
2932        }
2933        
2934        
2935        protected transient String giverFactionId;
2936        protected transient String giverRank = Ranks.CITIZEN;
2937        protected transient String giverPost = Ranks.POST_CITIZEN;
2938        protected transient String giverVoice = null;
2939        protected transient String giverPortrait;
2940        protected transient PersonImportance giverImportance = PersonImportance.MEDIUM;
2941        protected transient String [] giverTags;
2942        protected transient Gender giverGender = Gender.ANY;
2943        
2944        public void setGiverVoice(String giverVoice) {
2945                this.giverVoice = giverVoice;
2946        }
2947
2948        public void setGiverFaction(String factionId) {
2949                giverFactionId = factionId;
2950        }
2951        
2952        public Gender getGiverGender() {
2953                return giverGender;
2954        }
2955
2956        public void setGiverRank(String giverRank) {
2957                this.giverRank = giverRank;
2958        }
2959
2960        public void setGiverPost(String giverPost) {
2961                this.giverPost = giverPost;
2962        }
2963        
2964        public void setGiverPortrait(String giverPortrait) {
2965                this.giverPortrait = giverPortrait;
2966        }
2967        
2968        public void setGiverImportance(PersonImportance giverImportance) {
2969                this.giverImportance = giverImportance;
2970        }
2971        
2972        public void setGiverTags(String ... giverTags) {
2973                this.giverTags = giverTags;
2974        }
2975
2976
2977        public void findOrCreateGiver(MarketAPI market, boolean addToCommDirectory, boolean cleanUpOnMissionOverIfWasNewPerson) {
2978                String factionId = giverFactionId;
2979                if (factionId == null) factionId = market.getFactionId();
2980                PersonAPI person = findOrCreatePerson(factionId, market, cleanUpOnMissionOverIfWasNewPerson, giverRank, giverPost);
2981                
2982                ImportantPeopleAPI ip = Global.getSector().getImportantPeople();
2983                boolean createdNewPerson = !ip.isLastGetPersonResultWasExistingPerson();
2984                
2985                if (person != null) {
2986                        if (createdNewPerson) {
2987                                person.setRankId(giverRank);
2988                                person.setPostId(giverPost);
2989                                person.setImportanceAndVoice(giverImportance, genRandom);
2990                                if (giverVoice != null) {
2991                                        person.setVoice(giverVoice);
2992                                }
2993                                if (giverPortrait != null) {
2994                                        person.setPortraitSprite(giverPortrait);
2995                                }
2996                        }
2997                        // add giver tags regardless of whether person was created or already existed
2998                        if (giverTags != null) {
2999                                for (String tag : giverTags){ 
3000                                        person.addTag(tag);
3001                                }
3002                        }
3003                        if (createdNewPerson && !addToCommDirectory) {
3004                                market.getCommDirectory().removePerson(person);
3005                        }
3006                        person.setMarket(market);
3007                        personOverride = person;
3008                }
3009        }
3010        
3011        public PersonAPI getPersonOverride() {
3012                return personOverride;
3013        }
3014
3015        public void setPersonOverride(PersonAPI personOverride) {
3016                this.personOverride = personOverride;
3017        }
3018
3019        public PersonImportance pickImportance() {
3020                WeightedRandomPicker<PersonImportance> picker = new WeightedRandomPicker<PersonImportance>(genRandom);
3021                picker.add(PersonImportance.VERY_LOW, 1f);
3022                picker.add(PersonImportance.LOW, 5f);
3023                picker.add(PersonImportance.MEDIUM, 10f);
3024                picker.add(PersonImportance.HIGH, 5f);
3025                picker.add(PersonImportance.VERY_HIGH, 1f);
3026                return picker.pick();
3027        }
3028        public PersonImportance pickMediumImportance() {
3029                WeightedRandomPicker<PersonImportance> picker = new WeightedRandomPicker<PersonImportance>(genRandom);
3030                picker.add(PersonImportance.LOW, 5f);
3031                picker.add(PersonImportance.MEDIUM, 10f);
3032                picker.add(PersonImportance.HIGH, 5f);
3033                return picker.pick();
3034        }
3035        public PersonImportance pickHighImportance() {
3036                WeightedRandomPicker<PersonImportance> picker = new WeightedRandomPicker<PersonImportance>(genRandom);
3037                picker.add(PersonImportance.MEDIUM, 10f);
3038                picker.add(PersonImportance.HIGH, 5f);
3039                picker.add(PersonImportance.VERY_HIGH, 1f);
3040                return picker.pick();
3041        }
3042        public PersonImportance pickLowImportance() {
3043                WeightedRandomPicker<PersonImportance> picker = new WeightedRandomPicker<PersonImportance>(genRandom);
3044                picker.add(PersonImportance.VERY_LOW, 10f);
3045                picker.add(PersonImportance.LOW, 5f);
3046                picker.add(PersonImportance.MEDIUM, 1f);
3047                return picker.pick();
3048        }
3049
3050        public void createGiver(MarketAPI market, boolean addToCommDirectory, boolean removeOnMissionOver) {
3051                String factionId = giverFactionId;
3052                if (factionId == null) factionId = market.getFactionId();
3053                
3054                PersonAPI person = Global.getSector().getFaction(factionId).createRandomPerson(giverGender, genRandom);
3055                person.setRankId(giverRank);
3056                person.setPostId(giverPost);
3057                person.setImportanceAndVoice(giverImportance, genRandom);
3058                if (giverVoice != null) {
3059                        person.setVoice(giverVoice);
3060                }
3061                if (giverPortrait != null) {
3062                        person.setPortraitSprite(giverPortrait);
3063                }
3064                if (giverTags != null) {
3065                        for (String tag : giverTags){ 
3066                                person.addTag(tag);
3067                        }
3068                }
3069                
3070                ImportantPeopleAPI ip = Global.getSector().getImportantPeople();
3071                
3072                market.addPerson(person);
3073                ip.addPerson(person);
3074                ip.getData(person).getLocation().setMarket(market);
3075        
3076                if (addToCommDirectory) {
3077                        market.getCommDirectory().addPerson(person);
3078                }
3079                if (removeOnMissionOver) {
3080                        person.addTag(REMOVE_ON_MISSION_OVER);
3081                }
3082                PersonAdded added = new PersonAdded(market, person, false); 
3083                changes.add(added);
3084                
3085                makePersonRequired(person);
3086                
3087                personOverride = person;
3088                
3089                genMissionRewardMultAndQuality();
3090        }
3091        
3092        
3093        public void genMissionRewardMultAndQuality() {
3094                PersonAPI person = getPerson();
3095                if (person == null) return;
3096                float rel = person.getRelToPlayer().getRel();
3097                
3098                if (rel > 0) {
3099                        rewardMult = 1f + rel * (Global.getSettings().getFloat("missionMaxRewardMultFromRel") - 1f);
3100                } else if (rel < 0) {
3101                        rewardMult = 1f + rel * (1f - Global.getSettings().getFloat("missionMinRewardMultFromRel"));
3102                }
3103                
3104                float importance = person.getImportance().getValue();
3105
3106                float min = getMinQuality(); 
3107                float maxRelBonus = Global.getSettings().getFloat("missionMaxPossibleQualityAboveImportance"); 
3108                
3109                quality = Math.min(Math.max(0, rel), importance);
3110                if (rel > importance && importance < 1f) {
3111                        quality += (rel - importance) / (1f - importance) * maxRelBonus;
3112                }
3113                
3114                if (person.getMemoryWithoutUpdate().contains(BaseMissionHub.MISSION_QUALITY_BONUS)) {
3115                        quality += person.getMemoryWithoutUpdate().getFloat(BaseMissionHub.MISSION_QUALITY_BONUS);
3116                }
3117                
3118                if (quality < min) quality = min;
3119                if (quality > 1f) quality = 1f;
3120        }
3121        
3122        public float getBaseQuality() {
3123                PersonAPI person = getPerson();
3124                if (person == null) return 0.5f;
3125                float importance = person.getImportance().getValue();
3126                return importance;
3127        }
3128        public float getMaxQuality() {
3129                PersonAPI person = getPerson();
3130                if (person == null) return 0f;
3131                float maxRelBonus = Global.getSettings().getFloat("missionMaxPossibleQualityAboveImportance");
3132                float importance = person.getImportance().getValue();
3133                return Math.min(1f, importance + maxRelBonus);
3134        }
3135        public float getMinQuality() {
3136                PersonAPI person = getPerson();
3137                if (person == null) return 0f;
3138                float importance = person.getImportance().getValue();
3139                float min = importance - Global.getSettings().getFloat("missionMinPossibleQualityBelowImportance");
3140                if (min < 0) min = 0;
3141                return min;
3142        }
3143
3144        public float getQuality() {
3145                return quality;
3146        }
3147
3148        public void setQuality(float quality) {
3149                this.quality = quality;
3150        }
3151
3152        public float getRewardMult() {
3153                return rewardMult;
3154        }
3155        
3156        public float getRewardMultFraction() {
3157                float max = Global.getSettings().getFloat("missionMaxRewardMultFromRel");
3158                return Math.min(1f, Math.max(0f, (rewardMult - 1f) / (max - 1f)));
3159        }
3160
3161        public void setRewardMult(float rewardMult) {
3162                this.rewardMult = rewardMult;
3163        }
3164
3165        public Object getCurrentStage() {
3166                return currentStage;
3167        }
3168        
3169        public void addFleetDefeatTrigger(CampaignFleetAPI fleet, String trigger, boolean permanent) {
3170                Misc.addDefeatTrigger(fleet, trigger);
3171                changes.add(new DefeatTriggerAdded(fleet, trigger, permanent));
3172        }
3173
3174        public String getLocated(SectorEntityToken entity) {
3175                return BreadcrumbSpecial.getLocatedString(entity, true);
3176        }
3177        public String getLocatedUnclear(SectorEntityToken entity) {
3178                return BreadcrumbSpecial.getLocatedString(entity, false);
3179        }
3180        
3181        
3182        public String getGetWithinCommsRangeText() {
3183                return "Get within range of a functional comm relay to complete the mission and receive " +
3184                                "your reward.";
3185        }
3186        
3187        public String getGetWithinCommsRangeTextShort() {
3188                return "Get within comms range to complete the mission";
3189        }
3190        
3191        public String getGoToSystemTextShort(StarSystemAPI system) {
3192                return "Go to the " + system.getNameWithLowercaseTypeShort();
3193        }
3194        
3195        public String getGoToPlanetTextShort(PlanetAPI planet) {
3196                if (planet.getStarSystem() != null) {
3197                        return "Go to " + planet.getName() + " in the " + planet.getStarSystem().getNameWithLowercaseTypeShort();
3198                } else {
3199                        return "Go to " + planet.getName();
3200                }
3201        }
3202        
3203        public String getGoToPlanetTextPre(PlanetAPI planet) {
3204                String a = planet.getSpec().getAOrAn();
3205                String world = planet.getTypeNameWithWorld().toLowerCase();
3206                if (planet.getStarSystem() != null) {
3207                        return "Go to " + planet.getName() + ", " + a + " " + world + " in the " + planet.getStarSystem().getNameWithLowercaseType();
3208                } else {
3209                        return "Go to " + planet.getName() + ", " + a + " " + world + " in hyperspace";
3210                }
3211        }
3212        
3213        public String getGoToMarketText(MarketAPI market) {
3214                if (market.getStarSystem() != null) {
3215                        return "Go to " + market.getName() + " in the " + market.getStarSystem().getNameWithLowercaseTypeShort();
3216                } else {
3217                        return "Go to " + market.getName();
3218                }
3219        }
3220        
3221        public String getGoTalkToPersonText(PersonAPI person) {
3222                MarketAPI market = person.getMarket();
3223                if (market != null) {
3224                        return getGoToMarketText(market) + " and talk to " + person.getNameString();
3225                } else {
3226                        return "Talk to " + person.getNameString();
3227                }
3228        }
3229        
3230        
3231        public String getReturnText(MarketAPI market) {
3232                return getReturnText(market.getName());
3233        }
3234        public String getReturnText(String locationName) {
3235                return "Return to " + locationName + " and talk to " + 
3236                                 getPerson().getNameString() + " to receive your reward";
3237        }
3238        public String getReturnTextShort(MarketAPI market) {
3239                return getReturnTextShort(market.getName());
3240        }
3241        public String getReturnTextShort(String locationName) {
3242                return "Return to " + locationName + " and talk to " + 
3243                                getPerson().getNameString() + "";
3244        }
3245        
3246
3247        public EntityLocation generateLocationInsideTerrain(CampaignTerrainAPI terrain) {
3248                //LocationAPI location = terrain.getContainingLocation();
3249                
3250                CampaignTerrainPlugin plugin = terrain.getPlugin();
3251                boolean found = false;
3252                float orbitAngle = 0f;
3253                float orbitRadius = 0f;
3254                float orbitPeriod = 0f;
3255                SectorEntityToken orbitFocus = terrain;
3256                Vector2f forceLoc = null;
3257                
3258                if (plugin instanceof BaseTiledTerrain) {
3259                        BaseTiledTerrain tiles = (BaseTiledTerrain) plugin;
3260                        
3261                        float maxRadius = plugin.getRenderRange();
3262                        if (maxRadius < 100f) maxRadius = 100f;
3263                        WeightedRandomPicker<Pair<Integer, Integer>> picker = new WeightedRandomPicker<Pair<Integer,Integer>>(genRandom);
3264                        WeightedRandomPicker<Pair<Integer, Integer>> pickerPref = new WeightedRandomPicker<Pair<Integer,Integer>>(genRandom);
3265                        
3266                        for (int i = 0; i < tiles.getTiles().length; i++) {
3267                                for (int j = 0; j < tiles.getTiles()[0].length; j++) {
3268                                        if (tiles.getTiles()[i][j] >= 0) {
3269                                                float [] f = tiles.getTileCenter(i, j);
3270                                                Vector2f loc = new Vector2f(f[0], f[1]);
3271                                                float dist = Misc.getDistance(terrain.getLocation(), loc);
3272                                                float weight = (float) Math.pow(dist / maxRadius, 3);
3273                                                if (dist < 16000) {
3274                                                        pickerPref.add(new Pair<Integer, Integer>(i, j), weight);
3275                                                } else if (pickerPref.isEmpty()) {
3276                                                        picker.add(new Pair<Integer, Integer>(i, j), weight);
3277                                                }
3278                                        }
3279                                }
3280                        }
3281                        
3282                        Pair<Integer, Integer> pick = pickerPref.pick();
3283                        if (pick == null) pick = picker.pick();
3284                        
3285                        if (pick != null) {
3286                                float [] f = tiles.getTileCenter(pick.one, pick.two);
3287                                Vector2f loc = new Vector2f(f[0], f[1]);
3288                                
3289                                if (terrain.getOrbit() == null || terrain.getCircularOrbitRadius() <= 0 || terrain.getOrbitFocus() == null) {
3290                                        forceLoc = loc;
3291                                } else {
3292                                        orbitFocus = terrain.getOrbitFocus();
3293                                        orbitAngle = Misc.getAngleInDegrees(orbitFocus.getLocation(), loc);
3294                                        orbitRadius = Misc.getDistance(orbitFocus.getLocation(), loc);
3295                                        orbitPeriod = terrain.getCircularOrbitPeriod();
3296                                }
3297                                found = true;
3298                        }
3299
3300                } else if (plugin instanceof BaseRingTerrain) {
3301                        BaseRingTerrain ring = (BaseRingTerrain) plugin;
3302                        SectorEntityToken atCenter = ring.getRingParams().relatedEntity;
3303                        
3304                        float centerRadius = 0f;
3305                        if (atCenter != null) centerRadius = atCenter.getRadius();
3306                        float ringMiddle = ring.getRingParams().middleRadius;
3307                        float ringMin = ring.getRingParams().middleRadius - ring.getRingParams().bandWidthInEngine / 2f;
3308                        float ringMax = ring.getRingParams().middleRadius + ring.getRingParams().bandWidthInEngine / 2f;
3309                        
3310                        float min = Math.max(centerRadius, ringMin);
3311                        orbitRadius = min + (ringMax - min) * (0.1f + 0.8f * genRandom.nextFloat());
3312                        orbitAngle = genRandom.nextFloat() * 360f;
3313                        found = true;
3314                }
3315                
3316                if (!found) {
3317                        orbitRadius = 100f + 100f * genRandom.nextFloat();
3318                        orbitAngle = 360f * genRandom.nextFloat();
3319                }
3320                
3321                EntityLocation eLoc = new EntityLocation();
3322                eLoc.type = LocationType.OUTER_SYSTEM;
3323                if (forceLoc != null) {
3324                        eLoc.location = forceLoc;
3325                } else {
3326                        if (orbitPeriod <= 0f) {
3327                                orbitPeriod = orbitRadius / (5f + 5f * genRandom.nextFloat());
3328                        }
3329                        eLoc.orbit = Global.getFactory().createCircularOrbit(orbitFocus, 
3330                                        orbitAngle, orbitRadius, orbitPeriod);
3331                }
3332
3333                return eLoc;
3334        }
3335        
3336        public static String getTerrainName(CampaignTerrainAPI terrain) {
3337                String name = terrain.getPlugin().getTerrainName();
3338                if (name == null) name = "";
3339                if (name.contains(" L4") || name.contains(" L5")) {
3340                        name = getTerrainType(terrain);
3341                }
3342                return name;
3343        }
3344        public static boolean hasSpecialName(CampaignTerrainAPI terrain) {
3345                return !getTerrainName(terrain).toLowerCase().equals(getTerrainType(terrain).toLowerCase());
3346        }
3347        public static String getTerrainNameAOrAn(CampaignTerrainAPI terrain) {
3348                String name = getTerrainName(terrain);
3349                if (name != null) {
3350                        return Misc.getAOrAnFor(name);
3351                } else {
3352                        return terrain.getPlugin().getNameAOrAn();
3353                }
3354        }
3355        public static String getTerrainTypeAOrAn(CampaignTerrainAPI terrain) {
3356                String type = getTerrainType(terrain);
3357                if (type != null) {
3358                        return Misc.getAOrAnFor(type);
3359                } else {
3360                        return terrain.getPlugin().getNameAOrAn();
3361                }
3362        }
3363        public static String getTerrainType(CampaignTerrainAPI terrain) {
3364                return terrain.getPlugin().getNameForTooltip().toLowerCase();
3365        }
3366        
3367        
3368//      public void addGetWithinCommsRangeText(TooltipMakerAPI info, float pad) {
3369//              info.addPara("Get within range of a functional comm relay to complete the mission and receive " +
3370//                                      "your reward.", pad);
3371//      }
3372//      public void addGetWithinCommsRangeTextShort(TooltipMakerAPI info, Color tc, float pad) {
3373//              info.addPara("Get within comms range to complete the mission", tc, pad);
3374//      }
3375//      
3376//      public void addGoToSystemTextShort(StarSystemAPI system, TooltipMakerAPI info, Color tc, float pad) {
3377//              info.addPara("Go to the " + system.getNameWithLowercaseTypeShort() + "", tc, pad);
3378//      }
3379//      
3380//      public void addGoToPlanetTextShort(PlanetAPI planet, TooltipMakerAPI info, Color tc, float pad) {
3381//              if (planet.getStarSystem() != null) {
3382//                      info.addPara("Go to " + planet.getName() + " in the " + planet.getStarSystem().getNameWithLowercaseTypeShort(), tc, pad);
3383//              } else {
3384//                      info.addPara("Go to " + planet.getName() + " in hyperspace", tc, pad);
3385//              }
3386//      }
3387        
3388        public static float getUnits(float lightYears) {
3389                return lightYears * Misc.getUnitsPerLightYear();
3390        }
3391        
3392        public static boolean playerHasEnough(String comId, int quantity) {
3393                return Global.getSector().getPlayerFleet().getCargo().getCommodityQuantity(comId) >= quantity;
3394        }
3395        
3396        public void assignShipName(FleetMemberAPI member, String factionId) {
3397                CampaignFleetAPI fleet = Global.getFactory().createEmptyFleet(factionId, null, true);
3398                fleet.getFleetData().setShipNameRandom(genRandom);
3399                fleet.getFleetData().addFleetMember(member);
3400                fleet.getFleetData().removeFleetMember(member);
3401        }
3402        
3403        public String getDayOrDays(float days) {
3404                int d = (int) Math.round(days);
3405                String daysStr = "days";
3406                if (d == 1) {
3407                        daysStr = "day";
3408                }
3409                return daysStr;
3410        }
3411        
3412//      public List<String> getHighRankingMilitaryPosts(MarketAPI market) {
3413//              List<String> posts = new ArrayList<String>();
3414//              if (Misc.isMilitary(market)) {
3415//                      posts.add(Ranks.POST_BASE_COMMANDER);
3416//              }
3417//              if (Misc.hasOrbitalStation(market)) {
3418//                      posts.add(Ranks.POST_STATION_COMMANDER);
3419//              }
3420//              if (posts.isEmpty()) {
3421//                      posts.add(Ranks.POST_GENERIC_MILITARY);
3422//              }
3423//              return posts;
3424//      }
3425
3426        public List<Abortable> getChanges() {
3427                return changes;
3428        }
3429
3430        public Random getGenRandom() {
3431                return genRandom;
3432        }
3433        
3434        public void addOnAcceptCommodity(String commodityId, int quantity) {
3435                cargoOnAccept.addCommodity(commodityId, quantity);
3436        }
3437        public void addOnAcceptWeaponDrop(final String weaponId, final int quantity) {
3438                cargoOnAccept.addWeapons(weaponId, quantity);
3439        }
3440        public void addOnAcceptFighterLPCDrop(final String wingId, final int quantity) {
3441                cargoOnAccept.addFighters(wingId, quantity);
3442        }
3443        public void addOnAcceptHullmodDrop(final String hullmodId) {
3444                cargoOnAccept.addHullmods(hullmodId, 1);
3445        }
3446        public void addOnAcceptSpecialItemDrop(final String itemId, final String data) {
3447                cargoOnAccept.addSpecial(new SpecialItemData(itemId, data), 1);
3448        }
3449        
3450        public void addOnSuccessCommodity(String commodityId, int quantity) {
3451                if (cargoOnSuccess == null) cargoOnSuccess = Global.getFactory().createCargo(true);
3452                cargoOnSuccess.addCommodity(commodityId, quantity);
3453        }
3454        public void addOnSuccessWeaponDrop(final String weaponId, final int quantity) {
3455                if (cargoOnSuccess == null) cargoOnSuccess = Global.getFactory().createCargo(true);
3456                cargoOnSuccess.addWeapons(weaponId, quantity);
3457        }
3458        public void addOnSuccessFighterLPCDrop(final String wingId, final int quantity) {
3459                if (cargoOnSuccess == null) cargoOnSuccess = Global.getFactory().createCargo(true);
3460                cargoOnSuccess.addFighters(wingId, quantity);
3461        }
3462        public void addOnSuccessHullmodDrop(final String hullmodId) {
3463                if (cargoOnSuccess == null) cargoOnSuccess = Global.getFactory().createCargo(true);
3464                cargoOnSuccess.addHullmods(hullmodId, 1);
3465        }
3466        public void addOnSuccessSpecialItemDrop(final String itemId, final String data) {
3467                if (cargoOnSuccess == null) cargoOnSuccess = Global.getFactory().createCargo(true);
3468                cargoOnSuccess.addSpecial(new SpecialItemData(itemId, data), 1);
3469        }
3470        
3471        public int getMarinesRequiredToDisrupt(MarketAPI market, Industry industry, int daysRequired) {
3472                int daysPerToken = MarketCMD.getDisruptDaysPerToken(market, industry);
3473                daysRequired -= (int) industry.getDisruptedDays();
3474                int tokens = (int) Math.ceil((float) daysRequired / (float) daysPerToken);
3475                if (tokens < 1) tokens = 1;
3476                
3477                int marinesRequired = MarketCMD.getMarinesFor(market, tokens);
3478                marinesRequired = getAdjustedMarinesRequired(marinesRequired);
3479                return marinesRequired;
3480        }
3481        
3482        public void addDisruptRaidInfo(MarketAPI market, Industry industry, int daysRequired, TooltipMakerAPI info, float pad) {
3483                int marinesRequired = getMarinesRequiredToDisrupt(market, industry, daysRequired); 
3484                
3485                RaidDangerLevel danger = industry.getSpec().getDisruptDanger();
3486                
3487                Color h = Misc.getHighlightColor();
3488                LabelAPI label = info.addPara(industry.getCurrentName() + " must be disrupted for at least %s days. The operation " +
3489                                "will require around %s marines, and the " +
3490                                "danger level is %s.", 
3491                                pad, h,
3492                                "" + daysRequired,
3493                                Misc.getWithDGS(marinesRequired),
3494                                danger.name.toLowerCase());
3495                label.setHighlightColors(h, h, danger.color);
3496        }
3497        
3498        public int getMarinesRequiredForCustomObjective(MarketAPI market, RaidDangerLevel danger) {
3499                int marinesRequired = MarketCMD.getMarinesFor(market, Math.max(1, danger.marineTokens));
3500                marinesRequired = getAdjustedMarinesRequired(marinesRequired);
3501                return marinesRequired;
3502        }
3503        
3504        public int getMarinesRequiredForCustomDefenderStrength(int defenderStrength, RaidDangerLevel danger) {
3505                int marinesRequired = MarketCMD.getMarinesFor(defenderStrength, Math.max(1, danger.marineTokens));
3506                marinesRequired = getAdjustedMarinesRequired(marinesRequired);
3507                return marinesRequired;
3508        }
3509        
3510        public void addCustomRaidInfo(MarketAPI market, RaidDangerLevel danger, TooltipMakerAPI info, float pad) {
3511                int marinesRequired = getMarinesRequiredForCustomObjective(market, danger);
3512                
3513                Color h = Misc.getHighlightColor();
3514                LabelAPI label = info.addPara("The operation " +
3515                                "will require around %s marines, and the " +
3516                                "danger level is %s.", 
3517                                pad, h,
3518                                Misc.getWithDGS(marinesRequired),
3519                                danger.name.toLowerCase());
3520                label.setHighlightColors(h, danger.color);
3521        }
3522        
3523        public void addCustomRaidInfo(int defenderStrength, RaidDangerLevel danger, TooltipMakerAPI info, float pad) {
3524                int marinesRequired = MarketCMD.getMarinesFor(defenderStrength, Math.max(1, danger.marineTokens));
3525                marinesRequired = getAdjustedMarinesRequired(marinesRequired);
3526                
3527                Color h = Misc.getHighlightColor();
3528                LabelAPI label = info.addPara("The operation " +
3529                                "will require around %s marines, and the " +
3530                                "danger level is %s.", 
3531                                pad, h,
3532                                Misc.getWithDGS(marinesRequired),
3533                                danger.name.toLowerCase());
3534                label.setHighlightColors(h, danger.color);
3535        }
3536        
3537        public static int getAdjustedMarinesRequired(int marinesRequired) {
3538                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
3539                float support = Misc.getFleetwideTotalMod(playerFleet, Stats.FLEET_GROUND_SUPPORT, 0f);
3540                StatBonus stat = playerFleet.getStats().getDynamic().getMod(Stats.PLANETARY_OPERATIONS_MOD);
3541                
3542                
3543                int min = 0;
3544                int max = 0;
3545                for (int i = 1; i < marinesRequired * 2; i *= 2) {
3546                        float currSupport = (int) Math.round(Math.min(support, i));
3547                        float strength = i + currSupport;
3548                        strength = stat.computeEffective(strength);
3549                        if (strength >= marinesRequired) {
3550                                min = i / 2;
3551                                max = i;
3552                                break;
3553                        }
3554                }
3555                if (max > 0) {
3556                        int iter = Math.max(1, (max - min) / 100);
3557                        
3558                        for (int i = min; i <= max; i += iter) {
3559                                float currSupport = (int) Math.min(support, i);
3560                                float strength = i + currSupport;
3561                                strength = stat.computeEffective(strength);
3562                                if (strength >= marinesRequired) {
3563                                        marinesRequired = (int) Math.round(strength);
3564                                        break;
3565                                }
3566                        }
3567                }
3568                
3569                //if (true) return marinesRequired;
3570                
3571                int base = 10;
3572                if (marinesRequired > 100) base = 50;
3573                if (marinesRequired > 500) base = 100;
3574                if (marinesRequired > 1000) base = 250;
3575                if (marinesRequired > 2000) base = 500;
3576                if (marinesRequired > 5000) base = 1000;
3577                for (int i = 0; i < 10; i++) {
3578                        if (marinesRequired <= (i + 1) * base) {
3579                                marinesRequired = (i + 1) * base;
3580                                break;
3581                        }
3582                }
3583                marinesRequired = getRoundNumber(marinesRequired);
3584                return marinesRequired;
3585        }
3586        
3587        
3588        public static void addStandardMarketDesc(String prefix, MarketAPI market, TooltipMakerAPI info, float pad) {
3589                Color h = Misc.getHighlightColor();
3590                FactionAPI f = market.getFaction();
3591                
3592                if (Misc.isHiddenBase(market)) {
3593                        String a = f.getPersonNamePrefixAOrAn();
3594                        if (prefix == null || prefix.isEmpty()) {
3595                                if (market.isInHyperspace()) {
3596                                        LabelAPI label = info.addPara(market.getName() + "is " + a + " " + f.getPersonNamePrefix() + 
3597                                                        " base located in hyperspace.",
3598                                                        pad);
3599                                        label.setHighlight(market.getName(), f.getPersonNamePrefix());
3600                                        label.setHighlightColors(f.getBaseUIColor(), f.getBaseUIColor());
3601                                } else {
3602                                        LabelAPI label = info.addPara(market.getName() + "is " + a + " " + f.getPersonNamePrefix() + 
3603                                                        " base in the " + market.getStarSystem().getNameWithLowercaseTypeShort() + ".",
3604                                                        pad);
3605                                        label.setHighlight(market.getName(), f.getPersonNamePrefix());
3606                                        label.setHighlightColors(f.getBaseUIColor(), f.getBaseUIColor());
3607                                }
3608                        } else {
3609                                if (market.isInHyperspace()) {
3610                                        LabelAPI label = info.addPara(prefix + " " + market.getName() + ", " + a + " " + f.getPersonNamePrefix() + 
3611                                                        " base located in hyperspace.",
3612                                                        pad);
3613                                        label.setHighlight(market.getName(), f.getDisplayNameWithArticleWithoutArticle());
3614                                        label.setHighlightColors(f.getBaseUIColor(), f.getBaseUIColor());
3615                                } else {
3616                                        LabelAPI label = info.addPara(prefix + " " + market.getName() + ", " + a + " " + f.getPersonNamePrefix() + 
3617                                                        " base in the " + market.getStarSystem().getNameWithLowercaseTypeShort() + ".",
3618                                                        pad);
3619                                        label.setHighlight(market.getName(), f.getPersonNamePrefix());
3620                                        label.setHighlightColors(f.getBaseUIColor(), f.getBaseUIColor());
3621                                }
3622                        }
3623                        
3624                        
3625                        return;
3626                }
3627
3628                if (prefix == null || prefix.isEmpty()) {
3629                        if (market.isInHyperspace()) {
3630                                LabelAPI label = info.addPara(market.getName() + "is a size %s " +
3631                                                "colony in hyperspace controlled by " + f.getDisplayNameWithArticle() + ".",
3632                                                pad, f.getBaseUIColor(),
3633                                                "" + market.getSize(), f.getDisplayNameWithArticleWithoutArticle());
3634                                label.setHighlight(market.getName(), "" + market.getSize(), f.getDisplayNameWithArticleWithoutArticle());
3635                                label.setHighlightColors(f.getBaseUIColor(), h, f.getBaseUIColor());
3636                        } else {
3637                                LabelAPI label = info.addPara(market.getName() + "is a size %s " +
3638                                                "colony in the " + market.getStarSystem().getNameWithLowercaseTypeShort() + " controlled by " + f.getDisplayNameWithArticle() + ".",
3639                                                pad, f.getBaseUIColor(),
3640                                                "" + market.getSize(), f.getDisplayNameWithArticleWithoutArticle());
3641                                label.setHighlight(market.getName(), "" + market.getSize(), f.getDisplayNameWithArticleWithoutArticle());
3642                                label.setHighlightColors(f.getBaseUIColor(), h, f.getBaseUIColor());
3643                        }
3644                } else {
3645                        if (market.isInHyperspace()) {
3646                                LabelAPI label = info.addPara(prefix + " " + market.getName() + ", a size %s " +
3647                                                "colony in hyperspace controlled by " + f.getDisplayNameWithArticle() + ".",
3648                                                pad, f.getBaseUIColor(),
3649                                                "" + market.getSize(), f.getDisplayNameWithArticleWithoutArticle());
3650                                label.setHighlight(market.getName(), "" + market.getSize(), f.getDisplayNameWithArticleWithoutArticle());
3651                                label.setHighlightColors(f.getBaseUIColor(), h, f.getBaseUIColor());
3652                        } else {
3653                                LabelAPI label = info.addPara(prefix + " " + market.getName() + ", a size %s " +
3654                                                "colony in the " + market.getStarSystem().getNameWithLowercaseTypeShort() + " controlled by " + f.getDisplayNameWithArticle() + ".",
3655                                                pad, f.getBaseUIColor(),
3656                                                "" + market.getSize(), f.getDisplayNameWithArticleWithoutArticle());
3657                                label.setHighlight(market.getName(), "" + market.getSize(), f.getDisplayNameWithArticleWithoutArticle());
3658                                label.setHighlightColors(f.getBaseUIColor(), h, f.getBaseUIColor());
3659                        }
3660                }
3661//              label.setHighlight("" + market.getSize(), f.getDisplayNameWithArticleWithoutArticle());
3662//              label.setHighlightColors(h, f.getBaseUIColor());
3663        }
3664        
3665        public int getBombardmentFuel(MarketAPI market) {
3666                int fuel = MarketCMD.getBombardmentCost(market, Global.getSector().getPlayerFleet());
3667                fuel = getRoundNumber(fuel);
3668                return fuel;
3669        }
3670        
3671        public void addBombardmentInfo(MarketAPI market, TooltipMakerAPI info, float pad) {
3672                int fuel = getBombardmentFuel(market);
3673                
3674                Color h = Misc.getHighlightColor();
3675                info.addPara("Effectively bombarding the target will require approximately %s units of fuel.", 
3676                                pad, h,
3677                                Misc.getWithDGS(fuel));
3678        }
3679        
3680        public void addSpecialItemDropOnlyUseInAcceptImplNotUndoneOnAbort(SectorEntityToken entity, SpecialItemData data) {
3681                CargoAPI cargo = Global.getFactory().createCargo(true);
3682                cargo.addSpecial(data, 1);
3683                BaseSalvageSpecial.addExtraSalvage(entity, cargo);
3684        }
3685        
3686        
3687        public PersonAPI getImportantPerson(String id) {
3688                return Global.getSector().getImportantPeople().getData(id).getPerson();
3689        }
3690        public PersonDataAPI getImportantPersonData(String id) {
3691                return Global.getSector().getImportantPeople().getData(id);
3692        }
3693        
3694        public void setMemoryValuePermanent(HasMemory withMemory, String key, Object value) {
3695                withMemory.getMemoryWithoutUpdate().set(key, value);
3696        }
3697
3698        protected String completedKey = null;
3699        public void setStoryMission() {
3700                setNoAbandon();
3701                addTag(Tags.INTEL_STORY);
3702                setUseLargeFontInMissionList();
3703                //setCompletedKey();
3704        }
3705        
3706        public void setCompletedKey() {
3707                completedKey = "$" + getMissionId() + "_missionCompleted";
3708        }
3709        
3710        public boolean isOkToOfferMissionRequiringMarines(int marines) {
3711                PlaythroughLog log = PlaythroughLog.getInstance();
3712                long crew = log.getPrevValue("crew");
3713                long credits = log.getPrevValue("credits");
3714                return crew > marines * 2 || credits > marines * 400;
3715        }
3716
3717        public Object getStartingStage() {
3718                return startingStage;
3719        }
3720
3721        
3722        public PersonAPI getPersonAtMarketPost(MarketAPI market, String ... postIds) {
3723                for (String postId : postIds) {
3724                        for (PersonAPI person : market.getPeopleCopy()) {
3725                                if (postId.equals(person.getPostId())) {
3726                                        return person;
3727                                }
3728                        }
3729                }
3730                return null;
3731        }
3732        
3733        public MarketAPI getMarket(String id) {
3734                return Global.getSector().getEconomy().getMarket(id);
3735        }
3736        
3737        protected Color mapMarkerNameColor = null;
3738        public void setMapMarkerNameColor(Color mapMarkerColor) {
3739                this.mapMarkerNameColor = mapMarkerColor;
3740        }
3741        
3742        public void setMapMarkerNameColorBasedOnStar(StarSystemAPI system) {
3743                if (system.getCenter() instanceof PlanetAPI) {
3744                        Color color = Misc.setAlpha(((PlanetAPI)system.getCenter()).getSpec().getIconColor(), 255);
3745                        color = Misc.setBrightness(color, 235);
3746                        setMapMarkerNameColor(color);
3747                }
3748        }
3749
3750        public List<MissionTrigger> getTriggers() {
3751                return triggers;
3752        }
3753        
3754
3755}
3756
3757
3758
3759
3760
3761
3762