001package com.fs.starfarer.api.impl;
002
003import java.util.ArrayList;
004import java.util.Iterator;
005import java.util.List;
006import java.util.Map;
007
008import java.awt.Color;
009
010import com.fs.starfarer.api.Global;
011import com.fs.starfarer.api.campaign.CampaignFleetAPI;
012import com.fs.starfarer.api.campaign.CargoAPI;
013import com.fs.starfarer.api.campaign.CargoStackAPI;
014import com.fs.starfarer.api.campaign.GenericPluginManagerAPI;
015import com.fs.starfarer.api.campaign.InteractionDialogAPI;
016import com.fs.starfarer.api.campaign.PlayerMarketTransaction;
017import com.fs.starfarer.api.campaign.SectorEntityToken;
018import com.fs.starfarer.api.campaign.econ.MarketAPI;
019import com.fs.starfarer.api.campaign.econ.SubmarketAPI;
020import com.fs.starfarer.api.campaign.listeners.CargoScreenListener;
021import com.fs.starfarer.api.campaign.listeners.ColonyInteractionListener;
022import com.fs.starfarer.api.campaign.listeners.CommodityIconProvider;
023import com.fs.starfarer.api.campaign.listeners.CommodityTooltipModifier;
024import com.fs.starfarer.api.campaign.listeners.GroundRaidObjectivesListener;
025import com.fs.starfarer.api.campaign.rules.MemoryAPI;
026import com.fs.starfarer.api.combat.MutableStat;
027import com.fs.starfarer.api.fleet.MutableFleetStatsAPI;
028import com.fs.starfarer.api.impl.campaign.CargoPodsEntityPlugin;
029import com.fs.starfarer.api.impl.campaign.graid.GroundRaidObjectivePlugin;
030import com.fs.starfarer.api.impl.campaign.ids.Commodities;
031import com.fs.starfarer.api.impl.campaign.ids.Stats;
032import com.fs.starfarer.api.impl.campaign.ids.Submarkets;
033import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.MarketCMD.RaidType;
034import com.fs.starfarer.api.ui.Alignment;
035import com.fs.starfarer.api.ui.LabelAPI;
036import com.fs.starfarer.api.ui.PositionAPI;
037import com.fs.starfarer.api.ui.TooltipMakerAPI;
038import com.fs.starfarer.api.util.Misc;
039
040public class PlayerFleetPersonnelTracker implements ColonyInteractionListener,
041                                                                                                        GroundRaidObjectivesListener,
042                                                                                                        CommodityTooltipModifier,
043                                                                                                        CommodityIconProvider,
044                                                                                                        CargoScreenListener {
045
046        public static float XP_PER_RAID_MULT = 0.2f;
047        public static float MAX_EFFECTIVENESS_PERCENT = 100f;
048        public static float MAX_LOSS_REDUCTION_PERCENT = 50f;
049        
050        public static boolean KEEP_XP_DURING_TRANSFERS = true;
051        
052        public static enum PersonnelRank {
053                REGULAR("Regular", "icon_crew_green", 0.25f),
054                EXPERIENCED("Experienced", "icon_crew_regular", 0.5f),
055                VETERAN("Veteran", "icon_crew_veteran", 0.75f),
056                ELITE("Elite", "icon_crew_elite", 1f),
057                ;
058                public String name;
059                public String iconKey;
060                public float threshold;
061                private PersonnelRank(String name, String iconKey, float threshold) {
062                        this.name = name;
063                        this.iconKey = iconKey;
064                        this.threshold = threshold;
065                }
066                
067                public static PersonnelRank getRankForXP(float xp) {
068                        //float f = xp /MAX_XP_LEVEL;
069                        float f = xp;
070                        for (PersonnelRank rank : values()) {
071                                if (f < rank.threshold) {
072                                        return rank;
073                                }
074                        }
075                        return PersonnelRank.ELITE;
076                }
077        }
078        
079        public static class CommodityIconProviderWrapper {
080                public CargoStackAPI stack;
081                public CommodityIconProviderWrapper(CargoStackAPI stack) {
082                        this.stack = stack;
083                }
084        }
085        public static class CommodityDescriptionProviderWrapper {
086                public CargoStackAPI stack;
087                public CommodityDescriptionProviderWrapper(CargoStackAPI stack) {
088                        this.stack = stack;
089                }
090        }
091        
092        public static class PersonnelData implements Cloneable {
093                public String id;
094                public float xp;
095                public float num;
096                transient public float savedNum;
097                transient public float savedXP;
098                public PersonnelData(String id) {
099                        this.id = id;
100                }
101                @Override
102                protected PersonnelData clone() {
103                        try { 
104                                PersonnelData copy = (PersonnelData) super.clone();
105                                copy.savedNum = savedNum;
106                                copy.savedXP = savedXP;
107                                return copy;
108                        } catch (CloneNotSupportedException e) { 
109                                throw new RuntimeException(e);
110                        }
111                }
112                
113                public void add(int add) {
114                        num += add;
115                }
116                
117                public void remove(int remove, boolean removeXP) {
118                        if (!KEEP_XP_DURING_TRANSFERS) removeXP = true;
119                        
120                        if (remove > num) remove = (int) num;
121                        if (removeXP) xp *= (num - remove) / Math.max(1f, num);
122                        num -= remove;
123                        if (removeXP) {
124                                float maxXP = num;
125                                xp = Math.min(xp, maxXP);
126                        }
127                }
128                
129                public void addXP(float xp) {
130                        this.xp += xp;
131                        float maxXP = num;
132                        this.xp = Math.min(this.xp, maxXP);     
133                }
134                public void removeXP(float xp) {
135                        this.xp -= xp;
136                        if (xp < 0) xp = 0;     
137                }
138                
139                public float clampXP() {
140                        float maxXP = num;
141                        float prevXP = xp;
142                        this.xp = Math.min(this.xp, maxXP);
143                        return Math.max(0f, prevXP - maxXP);
144                }
145                
146                public void numMayHaveChanged(float newNum, boolean keepXP) {
147                        // if the number was reduced in some way (i.e. picking up a stack, or lost via code, w/e
148                        // then adjust XP downwards in same proportion
149                        if (num > newNum) {
150                                if (keepXP) {
151                                        clampXP();
152                                } else { 
153                                        xp *= newNum / Math.max(1f, num);
154                                }
155                        }
156                        num = newNum;
157                }
158                
159                public float getXPLevel() {
160                        float f = xp / Math.max(1f, num);
161                        if (f < 0) f = 0;
162                        if (f > 1f) f = 1f;
163                        return f;
164                }
165                
166                public PersonnelRank getRank() {
167                        PersonnelRank rank = PersonnelRank.getRankForXP(getXPLevel());
168                        return rank;
169                }
170                
171                public void integrateWithCurrentLocation(PersonnelAtEntity atLocation) {
172                        //int numTaken = (int) Math.max(0, num - savedNum);
173                        int numTaken = (int) Math.round(num - savedNum);
174                        if (atLocation != null) {// && numTaken > 0) {
175                                num = savedNum;
176                                xp = savedXP;
177                                //PersonnelData copy = atLocation.data.clone();
178                                PersonnelData copy = atLocation.data;
179                                if (numTaken > 0) {
180                                        transferPersonnel(copy, this, numTaken, this);
181                                } else if (numTaken < 0) {
182                                        transferPersonnel(this, copy, -numTaken, this);
183                                }
184                        }
185                }
186        }
187        
188        
189        public static class PersonnelAtEntity implements Cloneable {
190                public PersonnelData data;
191                public SectorEntityToken entity;
192                public String submarketId;
193                public PersonnelAtEntity(SectorEntityToken entity, String commodityId, String submarketId) {
194                        this.entity = entity;
195                        data = new PersonnelData(commodityId);
196                        this.submarketId = submarketId;
197                }
198                
199                @Override
200                protected PersonnelAtEntity clone() {
201                        try { 
202                                PersonnelAtEntity copy = (PersonnelAtEntity) super.clone();
203                                copy.data = data.clone();
204                                return copy;
205                        } catch (CloneNotSupportedException e) { 
206                                throw new RuntimeException(e);
207                        }
208                }
209        }
210        
211        
212        public static final String KEY = "$core_personnelTracker";
213        
214        public static PlayerFleetPersonnelTracker getInstance() {
215                Object test = Global.getSector().getMemoryWithoutUpdate().get(KEY);
216                if (test == null) {// || true) {
217                        test = new PlayerFleetPersonnelTracker();
218                        Global.getSector().getMemoryWithoutUpdate().set(KEY, test);
219                }
220                return (PlayerFleetPersonnelTracker) test; 
221        }
222        
223        protected PersonnelData marineData = new PersonnelData(Commodities.MARINES);
224        protected List<PersonnelAtEntity> droppedOff = new ArrayList<PersonnelAtEntity>();
225        
226        protected transient SectorEntityToken pods = null;
227        protected transient SubmarketAPI currSubmarket = null;
228        
229        public PlayerFleetPersonnelTracker() {
230                super();
231                
232                GenericPluginManagerAPI plugins = Global.getSector().getGenericPlugins();
233                //if (!plugins.hasPlugin(PlayerFleetPersonnelTracker.class)) {
234                        plugins.addPlugin(this, false);
235                //}
236                
237                //Global.getSector().getMemoryWithoutUpdate().set(KEY, this);
238                Global.getSector().getListenerManager().addListener(this);
239                
240                //marineData.xp = 2600 * 0.7f;
241                //marineData.num = 2600;
242                update();
243        }
244
245        public void reportCargoScreenOpened() {
246                doCleanup(true);
247                update();
248                currSubmarket = null;
249                
250                //marineData.xp = marineData.num * 0.7f;
251        }
252        
253        public void reportSubmarketOpened(SubmarketAPI submarket) {
254                doCleanup(false);
255                currSubmarket = submarket;
256        }
257
258        public void reportPlayerLeftCargoPods(SectorEntityToken entity) {
259                pods = entity;
260        }
261        
262        public void reportPlayerNonMarketTransaction(PlayerMarketTransaction transaction, InteractionDialogAPI dialog) {
263                if (pods == null && dialog != null) {
264                        SectorEntityToken target = dialog.getInteractionTarget();
265                        if (target != null && target.getCustomPlugin() instanceof CargoPodsEntityPlugin) {
266                                pods = target;
267                        }
268                }
269                processTransaction(transaction, pods);
270        }
271        
272        public void reportPlayerMarketTransaction(PlayerMarketTransaction transaction) {
273                if (transaction.getMarket() == null || 
274                                transaction.getMarket().getPrimaryEntity() == null ||
275                                transaction.getSubmarket() == null) return;
276                if (!transaction.getSubmarket().getSpecId().equals(Submarkets.SUBMARKET_STORAGE)) {
277                        doCleanup(true);
278                        update(false, true, null);
279                        return;
280                }
281                processTransaction(transaction, transaction.getMarket().getPrimaryEntity());
282        }
283        
284        public void processTransaction(PlayerMarketTransaction transaction, SectorEntityToken entity) {
285                if (entity == null) return;
286                
287                SubmarketAPI sub = transaction.getSubmarket();
288                
289//              // when ejecting cargo, there's a fake "storage" submarket, but when interacting with the pods, there's
290//              // no submarket - so for pods to display rank correctly, set the submarket when dropping off pods to null
291//              if (pods != null) {
292//                      sub = null;
293//              }
294                
295                for (CargoStackAPI stack : transaction.getSold().getStacksCopy()) {
296                        if (!stack.isPersonnelStack()) continue;
297                        if (stack.isMarineStack()) {
298                                PersonnelAtEntity at = getDroppedOffAt(stack.getCommodityId(), entity, sub, true);
299                                
300                                int num = (int) stack.getSize();
301                                transferPersonnel(marineData, at.data, num, marineData);
302                        }
303                }
304                
305                for (CargoStackAPI stack : transaction.getBought().getStacksCopy()) {
306                        if (!stack.isPersonnelStack()) continue;
307                        if (stack.isMarineStack()) {
308                                PersonnelAtEntity at = getDroppedOffAt(stack.getCommodityId(), entity, sub, true);
309                                
310                                int num = (int) stack.getSize();
311                                transferPersonnel(at.data, marineData, num, marineData);
312                        }
313                }
314                
315                doCleanup(true);
316                update();
317        }
318        
319        public static void transferPersonnel(PersonnelData from, PersonnelData to, int num, PersonnelData keepsXP) {
320                if (num > from.num) {
321                        num = (int) from.num;
322                }
323                if (num <= 0) return;
324                
325                if (KEEP_XP_DURING_TRANSFERS && keepsXP != null) {
326                        to.add(num);
327                        from.remove(num, false);
328                        
329                        float totalXP = to.xp + from.xp;
330                        if (keepsXP == from) {
331                                from.xp = Math.min(totalXP, from.num);
332                                to.xp = Math.max(0f, totalXP - from.num);
333                        } else if (keepsXP == to) {
334                                to.xp = Math.min(totalXP, to.num);
335                                from.xp = Math.max(0f, totalXP - to.num);
336                        }
337                } else {
338                        float xp = from.xp * num / from.num;
339                        
340                        to.add(num);
341                        to.addXP(xp);
342                        
343                        from.remove(num, true); // also removes XP
344                }
345        }
346        
347        
348        public void reportRaidObjectivesAchieved(RaidResultData data, InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
349                CampaignFleetAPI fleet = Global.getSector().getPlayerFleet();
350                CargoAPI cargo = fleet.getCargo();
351                float marines = cargo.getMarines();
352                
353                marineData.remove(data.marinesLost, true);
354                
355                float total = marines + data.marinesLost;
356                float xpGain = 1f - data.raidEffectiveness;
357                xpGain *= total;
358                xpGain *= XP_PER_RAID_MULT;
359                if (xpGain < 0) xpGain = 0;
360                marineData.addXP(xpGain);
361                
362                update();
363        }
364        
365        public void update() {
366                update(false, false, null);
367        }
368        public void update(boolean withIntegrationFromCurrentLocation, boolean keepXP, CargoStackAPI stack) {
369                CampaignFleetAPI fleet = Global.getSector().getPlayerFleet();
370                if (fleet == null) return;
371                CargoAPI cargo = fleet.getCargo();
372                
373                
374                float marines = cargo.getMarines();
375                marineData.numMayHaveChanged(marines, keepXP);
376                
377                if (withIntegrationFromCurrentLocation) {
378                        //getDroppedOffAt(Commodities.MARINES, getInteractionEntity(), currSubmarket, true);
379                        PersonnelAtEntity atLocation = getPersonnelAtLocation(Commodities.MARINES, currSubmarket);
380                        marineData.integrateWithCurrentLocation(atLocation);
381                }
382                
383                
384                MutableFleetStatsAPI stats = fleet.getStats();
385                
386                String id = "marineXP";
387                PersonnelRank rank = marineData.getRank();
388                float effectBonus = getMarineEffectBonus(marineData);
389                float casualtyReduction = getMarineLossesReductionPercent(marineData);
390                if (effectBonus > 0) {
391                        //stats.getDynamic().getMod(Stats.PLANETARY_OPERATIONS_MOD).modifyMult(id, 1f + effectBonus * 0.01f, rank.name + " marines");
392                        stats.getDynamic().getMod(Stats.PLANETARY_OPERATIONS_MOD).modifyPercent(id, effectBonus, rank.name + " marines");
393                } else {
394                        //stats.getDynamic().getMod(Stats.PLANETARY_OPERATIONS_MOD).unmodifyMult(id);
395                        stats.getDynamic().getMod(Stats.PLANETARY_OPERATIONS_MOD).unmodifyPercent(id);
396                }
397                if (casualtyReduction > 0) {
398                        stats.getDynamic().getStat(Stats.PLANETARY_OPERATIONS_CASUALTIES_MULT).modifyMult(id, 1f - casualtyReduction * 0.01f, rank.name + " marines");
399                } else {
400                        stats.getDynamic().getStat(Stats.PLANETARY_OPERATIONS_CASUALTIES_MULT).unmodifyMult(id);
401                }
402        }
403        
404        
405        
406        
407        public static float getMarineEffectBonus(PersonnelData data) {
408                float f =  data.getXPLevel();
409                //if (true) return 30f;
410                return Math.round(f * MAX_EFFECTIVENESS_PERCENT);
411        }
412        public static float getMarineLossesReductionPercent(PersonnelData data) {
413                float f =  data.getXPLevel();
414                //if (true) return 30f;
415                return Math.round(f * MAX_LOSS_REDUCTION_PERCENT);
416        }
417        
418        public void addSectionAfterPrice(TooltipMakerAPI info, float width, boolean expanded, CargoStackAPI stack) {
419                if (Commodities.MARINES.equals(stack.getCommodityId()) && !expanded) {
420                        saveData();
421                        update(true, true, stack);
422
423                        PersonnelData data = marineData;
424                        boolean nonPlayer = false;
425                        if (!stack.isInPlayerCargo()) {
426                                nonPlayer = true;
427                                PersonnelAtEntity atLoc = getPersonnelAtLocation(stack.getCommodityId(), getSubmarketFor(stack));
428                                if (atLoc != null) {
429                                        data = atLoc.data;
430                                } else {
431                                        data = null;
432                                }
433                        }
434                        //if (stack.isInPlayerCargo()) {
435                        if (data != null) {
436                                if (data.num <= 0) {
437                                        restoreData();
438                                        return;
439                                }
440                                
441                                float opad = 10f;
442                                float pad = 3f;
443                                Color h = Misc.getHighlightColor();
444        
445                                PersonnelRank rank = data.getRank();
446                                
447                                LabelAPI heading = info.addSectionHeading(rank.name + " marines", 
448                                                                        Misc.getBasePlayerColor(), Misc.getDarkPlayerColor(), Alignment.MID, opad);
449                                heading.autoSizeToWidth(info.getTextWidthOverride());
450                                PositionAPI p = heading.getPosition();
451                                p.setSize(p.getWidth(), p.getHeight() + 3f);
452                                
453                                
454                                switch (rank) {
455                                case REGULAR:
456                                        if (nonPlayer) {
457                                                info.addPara("Regular marines - tough, competent, and disciplined.", opad);
458                                        } else {
459                                                info.addPara("These marines are mostly regulars and have seen some combat, " +
460                                                                "but are not, overall, accustomed to your style of command.", opad); 
461                                        }
462                                        break;
463                                case EXPERIENCED:
464                                        if (nonPlayer) {
465                                                info.addPara("Experienced marines with substantial training and a number of " +
466                                                                         "operations under their belts.", opad);
467                                        } else {
468                                                info.addPara("You've led these marines on several operations, and " +
469                                                                "the experience gained by both parties is beginning to show concrete benefits.", opad);
470                                        }
471                                        break;
472                                case VETERAN:
473                                        if (nonPlayer) {
474                                                info.addPara("These marines are veterans of many ground operations. " +
475                                                                "Well-motivated and highly effective.", opad);
476                                        } else {
477                                                info.addPara("These marines are veterans of many ground operations under your leadership; " +
478                                                                "the command structure is well established and highly effective.", opad);
479                                        }
480                                        break;
481                                case ELITE:
482                                        if (nonPlayer) {
483                                                info.addPara("These marines are an elite force, equipped, led, and motivated well " +
484                                                            "above the standards of even the professional militaries in the Sector.", opad);
485                                        } else {
486                                                info.addPara("These marines are an elite force, equipped, led, and motivated well " +
487                                                                "above the standards of even the professional militaries in the Sector.", opad);
488                                        }
489                                        break;
490                                
491                                }
492                                
493                                float effectBonus = getMarineEffectBonus(data);
494                                float casualtyReduction = getMarineLossesReductionPercent(data);
495                                MutableStat fake = new MutableStat(1f);
496                                fake.modifyPercentAlways("1", effectBonus, "increased effectiveness of ground operations");
497                                fake.modifyPercentAlways("2", -casualtyReduction, "reduction to marine casualties suffered during ground operations");
498                                info.addStatModGrid(width, 50f, 10f, opad, fake, true, null);
499                                
500                        }
501                        restoreData();
502                }
503        }
504        
505        
506        public void reportPlayerClosedMarket(MarketAPI market) {
507                update();
508        }
509        public void reportPlayerOpenedMarket(MarketAPI market) {
510                update();
511        }
512        
513
514        public String getIconName() {
515                return null;
516        }
517
518
519        public int getHandlingPriority(Object params) {
520                if (params instanceof CommodityIconProviderWrapper) {
521                        CargoStackAPI stack = ((CommodityIconProviderWrapper) params).stack;
522                        if (Commodities.MARINES.equals(stack.getCommodityId())) {
523                                if (stack.isInPlayerCargo()) {
524                                        return GenericPluginManagerAPI.CORE_GENERAL;
525                                }
526                                
527                                SubmarketAPI sub = getSubmarketFor(stack);
528                                PersonnelAtEntity atLocation = getPersonnelAtLocation(stack.getCommodityId(), sub);
529                                if (atLocation != null) {
530                                        return GenericPluginManagerAPI.CORE_GENERAL;
531                                }
532                        }
533                }
534                return -1;
535        }
536        
537//      public PersonnelRank getFleetMarineRank() {
538//              PersonnelAtEntity atLocation = getPersonnelAtLocation(Commodities.MARINES);
539//              PersonnelRank rank = marineData.getRank(atLocation);
540//              return rank;
541//      }
542        
543
544        public String getRankIconName(CargoStackAPI stack) {
545                if (stack.isPickedUp()) return null;
546                saveData();
547                update(true, true, stack);
548                PersonnelData data = null;
549                
550                if (stack.isMarineStack()) {
551                        data = marineData;
552                        if (!stack.isInPlayerCargo()) {
553                                SubmarketAPI sub = getSubmarketFor(stack);
554                                PersonnelAtEntity atLocation = getPersonnelAtLocation(stack.getCommodityId(), sub);
555                                if (atLocation != null) {
556                                        data = atLocation.data;
557                                } else {
558                                        restoreData();
559                                        return null;
560                                }
561                        }
562                }
563                
564                
565                if (data == null || data.num <= 0) {
566                        restoreData();
567                        return null;
568                }
569                
570                PersonnelRank rank = data.getRank();
571                restoreData();
572                return Global.getSettings().getSpriteName("ui", rank.iconKey);
573        }
574        
575        public String getIconName(CargoStackAPI stack) {
576                return null;
577        }
578        
579        
580        protected transient PersonnelData savedMarineData;
581        protected transient List<PersonnelAtEntity> savedPersonnelData = new ArrayList<PersonnelAtEntity>();
582        
583        public void saveData() {
584                savedMarineData = marineData;
585                marineData = marineData.clone();
586                
587                savedPersonnelData = new ArrayList<PersonnelAtEntity>();
588                for (PersonnelAtEntity curr : droppedOff) {
589                        savedPersonnelData.add(curr.clone());
590                }
591        }
592        
593        public void restoreData() {
594                marineData = savedMarineData;
595                savedMarineData = null;
596                
597                droppedOff.clear();
598                droppedOff.addAll(savedPersonnelData);
599                savedPersonnelData.clear();
600        }
601
602        
603        public void reportPlayerOpenedMarketAndCargoUpdated(MarketAPI market) {
604        }
605
606        public void modifyRaidObjectives(MarketAPI market, SectorEntityToken entity, List<GroundRaidObjectivePlugin> objectives, RaidType type, int marineTokens, int priority) {
607                
608        }
609        
610        protected void doCleanup(boolean withDroppedOff) {
611                marineData.savedNum = marineData.num;
612                marineData.savedXP = marineData.xp;
613                pods = null;
614                
615                if (withDroppedOff) {
616                        Iterator<PersonnelAtEntity> iter = droppedOff.iterator();
617                        while (iter.hasNext()) {
618                                PersonnelAtEntity pae = iter.next();
619                                if (!pae.entity.isAlive() || pae.data.num <= 0 || pae.data.xp <= 0) {
620                                        iter.remove();
621                                }
622                        }
623                }
624        }
625        
626        public SectorEntityToken getInteractionEntity() {
627                InteractionDialogAPI dialog = Global.getSector().getCampaignUI().getCurrentInteractionDialog();
628                SectorEntityToken entity = null;
629                if (dialog != null) {
630                        entity = dialog.getInteractionTarget();
631                        if (entity != null && entity.getMarket() != null && entity.getMarket().getPrimaryEntity() != null) {
632                                entity = entity.getMarket().getPrimaryEntity();
633                        }
634                }
635                return entity;
636        }
637        
638        /**
639         * Assumes stack is not in player cargo.
640         * @param stack
641         * @return
642         */
643        public SubmarketAPI getSubmarketFor(CargoStackAPI stack) {
644                if (stack.getCargo() == null) return null;
645                SectorEntityToken entity = getInteractionEntity();
646                if (entity == null || entity.getMarket() == null || entity.getMarket().getSubmarketsCopy() == null) return currSubmarket;
647                
648                for (SubmarketAPI sub : entity.getMarket().getSubmarketsCopy()) {
649                        if (sub.getCargo() == stack.getCargo()) {
650                                return sub;
651                        }
652                }
653                return currSubmarket;
654        }
655
656        public PersonnelAtEntity getDroppedOffAt(String commodityId, SectorEntityToken entity, SubmarketAPI sub, boolean createIfNull) {
657                String submarketId = sub == null ? "" : sub.getSpecId();
658                for (PersonnelAtEntity pae : droppedOff) {
659                        String otherSubmarketId = pae.submarketId == null ? "" : pae.submarketId;
660                        if (entity == pae.entity && commodityId.equals(pae.data.id) && submarketId.equals(otherSubmarketId)) {
661                                return pae;
662                        }
663                }
664                if (createIfNull) {
665                        if (submarketId.isEmpty()) submarketId = null;
666                        PersonnelAtEntity pae = new PersonnelAtEntity(entity, commodityId, submarketId);
667                        droppedOff.add(pae);
668                        return pae;
669                }
670                return null;
671        }
672        
673        public PersonnelAtEntity getPersonnelAtLocation(String commodityId, SubmarketAPI sub) {
674                SectorEntityToken entity = getInteractionEntity();
675                PersonnelAtEntity atLocation = entity == null ? null : getDroppedOffAt(commodityId, entity, sub, false);
676                return atLocation;
677        }
678
679        public PersonnelData getMarineData() {
680                return marineData;
681        }
682
683        public List<PersonnelAtEntity> getDroppedOff() {
684                return droppedOff;
685        }
686        
687        
688}
689
690
691
692
693
694
695