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