package games.strategy.triplea; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.ButtonModel; import javax.swing.SwingUtilities; import games.strategy.debug.ClientLogger; import games.strategy.engine.data.GameData; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.ProductionRule; import games.strategy.engine.data.RepairRule; import games.strategy.engine.data.Resource; import games.strategy.engine.data.Territory; import games.strategy.engine.data.Unit; import games.strategy.net.GUID; import games.strategy.sound.ClipPlayer; import games.strategy.sound.SoundPath; import games.strategy.triplea.attachments.PlayerAttachment; import games.strategy.triplea.attachments.PoliticalActionAttachment; import games.strategy.triplea.attachments.TerritoryAttachment; import games.strategy.triplea.attachments.UserActionAttachment; import games.strategy.triplea.delegate.DiceRoll; import games.strategy.triplea.delegate.GameStepPropertiesHelper; import games.strategy.triplea.delegate.IBattle; import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.delegate.dataObjects.BattleListing; import games.strategy.triplea.delegate.dataObjects.CasualtyDetails; import games.strategy.triplea.delegate.dataObjects.CasualtyList; import games.strategy.triplea.delegate.dataObjects.FightBattleDetails; import games.strategy.triplea.delegate.dataObjects.MoveDescription; import games.strategy.triplea.delegate.dataObjects.TechResults; import games.strategy.triplea.delegate.dataObjects.TechRoll; import games.strategy.triplea.delegate.remote.IAbstractForumPosterDelegate; import games.strategy.triplea.delegate.remote.IAbstractPlaceDelegate; import games.strategy.triplea.delegate.remote.IBattleDelegate; import games.strategy.triplea.delegate.remote.IEditDelegate; import games.strategy.triplea.delegate.remote.IMoveDelegate; import games.strategy.triplea.delegate.remote.IPoliticsDelegate; import games.strategy.triplea.delegate.remote.IPurchaseDelegate; import games.strategy.triplea.delegate.remote.ITechDelegate; import games.strategy.triplea.delegate.remote.IUserActionDelegate; import games.strategy.triplea.formatter.MyFormatter; import games.strategy.triplea.player.AbstractHumanPlayer; import games.strategy.triplea.player.ITripleAPlayer; import games.strategy.triplea.ui.BattleDisplay; import games.strategy.triplea.ui.PlaceData; import games.strategy.triplea.ui.TripleAFrame; import games.strategy.ui.SwingAction; import games.strategy.util.CompositeMatchAnd; import games.strategy.util.IntegerMap; import games.strategy.util.Match; import games.strategy.util.Tuple; /** * As a rule, nothing that changes GameData should be in here. * It should be using a Change done in a delegate, and done through an IDelegate, which we get through * getPlayerBridge().getRemote() */ public class TripleAPlayer extends AbstractHumanPlayer<TripleAFrame> implements ITripleAPlayer { private boolean m_soundPlayedAlreadyCombatMove = false; private boolean m_soundPlayedAlreadyNonCombatMove = false; private boolean m_soundPlayedAlreadyPurchase = false; private boolean m_soundPlayedAlreadyTechnology = false; private boolean m_soundPlayedAlreadyBattle = false; private boolean m_soundPlayedAlreadyEndTurn = false; private boolean m_soundPlayedAlreadyPlacement = false; /** Creates new TripleAPlayer. */ public TripleAPlayer(final String name, final String type) { super(name, type); } @Override public void reportError(final String error) { ui.notifyError(error); } @Override public void reportMessage(final String message, final String title) { if (ui != null) { ui.notifyMessage(message, title); } } @Override public void start(final String name) { // must call super.start super.start(name); if (getPlayerBridge().isGameOver()) { return; } if (ui == null) { // We will get here if we are loading a save game of a map that we do not have. Caller code should be doing // the error handling, so just return.. return; } // TODO: parsing which UI thing we should run based on the string name of a possibly extended delegate // class seems like a bad way of doing this whole method. however i can't think of anything better right now. // This is how we find out our game step: getGameData().getSequence().getStep() // The game step contains information, like the exact delegate and the delegate's class, // that we can further use if needed. This is how we get our communication bridge for affecting the gamedata: // (ISomeDelegate) getPlayerBridge().getRemote() // We should never touch the game data directly. All changes to game data are done through the remote, // which then changes the game using the DelegateBridge -> change factory ui.requiredTurnSeries(getPlayerID()); boolean badStep = false; enableEditModeMenu(); if (name.endsWith("Tech")) { tech(); } else if (name.endsWith("TechActivation")) { // do nothing } else if (name.endsWith("Bid") || name.endsWith("Purchase")) { // the delegate handles everything purchase(GameStepPropertiesHelper.isBid(getGameData())); } else if (name.endsWith("Move")) { final boolean nonCombat = GameStepPropertiesHelper.isNonCombatMove(getGameData(), false); move(nonCombat, name); if (!nonCombat) { ui.waitForMoveForumPoster(getPlayerID(), getPlayerBridge()); // TODO only do forum post if there is a combat } } else if (name.endsWith("Battle")) { battle(); } else if (name.endsWith("Place")) { place(); } else if (name.endsWith("Politics")) { politics(true); } else if (name.endsWith("UserActions")) { userActions(true); } else if (name.endsWith("EndTurn")) { endTurn(); // reset our sounds m_soundPlayedAlreadyCombatMove = false; m_soundPlayedAlreadyNonCombatMove = false; m_soundPlayedAlreadyPurchase = false; m_soundPlayedAlreadyTechnology = false; m_soundPlayedAlreadyBattle = false; m_soundPlayedAlreadyEndTurn = false; m_soundPlayedAlreadyPlacement = false; } else { badStep = true; } disableEditModeMenu(); if (badStep) { throw new IllegalArgumentException("Unrecognized step name:" + name); } } private void enableEditModeMenu() { try { ui.setEditDelegate((IEditDelegate) getPlayerBridge().getRemotePersistentDelegate("edit")); } catch (final Exception e) { ClientLogger.logQuietly(e); } SwingUtilities.invokeLater(() -> { ui.getEditModeButtonModel().addActionListener(m_editModeAction); ui.getEditModeButtonModel().setEnabled(true); }); } private void disableEditModeMenu() { ui.setEditDelegate(null); SwingUtilities.invokeLater(() -> { ui.getEditModeButtonModel().setEnabled(false); ui.getEditModeButtonModel().removeActionListener(m_editModeAction); }); } private final AbstractAction m_editModeAction = SwingAction.of(e -> { final boolean editMode = ((ButtonModel) e.getSource()).isSelected(); try { // Set edit mode // All GameDataChangeListeners will be notified upon success final IEditDelegate editDelegate = (IEditDelegate) getPlayerBridge().getRemotePersistentDelegate("edit"); editDelegate.setEditMode(editMode); } catch (final Exception exception) { exception.printStackTrace(); // toggle back to previous state since setEditMode failed ui.getEditModeButtonModel().setSelected(!ui.getEditModeButtonModel().isSelected()); } }); private void politics(final boolean firstRun) { if (getPlayerBridge().isGameOver()) { return; } final IPoliticsDelegate iPoliticsDelegate; try { iPoliticsDelegate = (IPoliticsDelegate) getPlayerBridge().getRemoteDelegate(); } catch (final ClassCastException e) { final String errorContext = "PlayerBridge step name: " + getPlayerBridge().getStepName() + ", Remote class name: " + getPlayerBridge().getRemoteDelegate().getClass(); ClientLogger.logQuietly(errorContext, e); throw new IllegalStateException(errorContext, e); } final PoliticalActionAttachment actionChoice = ui.getPoliticalActionChoice(getPlayerID(), firstRun, iPoliticsDelegate); if (actionChoice != null) { iPoliticsDelegate.attemptAction(actionChoice); politics(false); } } private void userActions(final boolean firstRun) { if (getPlayerBridge().isGameOver()) { return; } final IUserActionDelegate iUserActionDelegate; try { iUserActionDelegate = (IUserActionDelegate) getPlayerBridge().getRemoteDelegate(); } catch (final ClassCastException e) { final String errorContext = "PlayerBridge step name: " + getPlayerBridge().getStepName() + ", Remote class name: " + getPlayerBridge().getRemoteDelegate().getClass(); // for some reason the client is not seeing or getting these errors, so print to err too System.err.println(errorContext); ClientLogger.logQuietly(e); throw new IllegalStateException(errorContext, e); } final UserActionAttachment actionChoice = ui.getUserActionChoice(getPlayerID(), firstRun, iUserActionDelegate); if (actionChoice != null) { iUserActionDelegate.attemptAction(actionChoice); userActions(false); } } @Override public boolean acceptAction(final PlayerID playerSendingProposal, final String acceptanceQuestion, final boolean politics) { final GameData data = getGameData(); if (!getPlayerID().amNotDeadYet(data) || getPlayerBridge().isGameOver()) { return true; } return ui.acceptAction(playerSendingProposal, "To " + getPlayerID().getName() + ": " + acceptanceQuestion, politics); } private void tech() { if (getPlayerBridge().isGameOver()) { return; } final ITechDelegate techDelegate; try { techDelegate = (ITechDelegate) getPlayerBridge().getRemoteDelegate(); } catch (final ClassCastException e) { final String errorContext = "PlayerBridge step name: " + getPlayerBridge().getStepName() + ", Remote class name: " + getPlayerBridge().getRemoteDelegate().getClass(); // for some reason the client is not seeing or getting these errors, so print to err too System.err.println(errorContext); ClientLogger.logQuietly(e); throw new IllegalStateException(errorContext, e); } final PlayerID id = getPlayerID(); if (!m_soundPlayedAlreadyTechnology) { ClipPlayer.play(SoundPath.CLIP_PHASE_TECHNOLOGY, id); m_soundPlayedAlreadyTechnology = true; } final TechRoll techRoll = ui.getTechRolls(id); if (techRoll != null) { final TechResults techResults = techDelegate.rollTech(techRoll.getRolls(), techRoll.getTech(), techRoll.getNewTokens(), techRoll.getWhoPaysHowMuch()); if (techResults.isError()) { ui.notifyError(techResults.getErrorString()); tech(); } else { ui.notifyTechResults(techResults); } } } private void move(final boolean nonCombat, final String stepName) { if (getPlayerBridge().isGameOver()) { return; } final IMoveDelegate moveDel; try { moveDel = (IMoveDelegate) getPlayerBridge().getRemoteDelegate(); } catch (final ClassCastException e) { final String errorContext = "PlayerBridge step name: " + getPlayerBridge().getStepName() + ", Remote class name: " + getPlayerBridge().getRemoteDelegate().getClass(); // for some reason the client is not seeing or getting these errors, so print to err too System.err.println(errorContext); ClientLogger.logQuietly(e); throw new IllegalStateException(errorContext, e); } final PlayerID id = getPlayerID(); if (nonCombat && !m_soundPlayedAlreadyNonCombatMove) { ClipPlayer.play(SoundPath.CLIP_PHASE_MOVE_NONCOMBAT, id); m_soundPlayedAlreadyNonCombatMove = true; } if (!nonCombat && !m_soundPlayedAlreadyCombatMove) { ClipPlayer.play(SoundPath.CLIP_PHASE_MOVE_COMBAT, id); m_soundPlayedAlreadyCombatMove = true; } // getMove will block until all moves are done. We recursively call this same method // until getMove stops blocking. final MoveDescription moveDescription = ui.getMove(id, getPlayerBridge(), nonCombat, stepName); if (moveDescription == null) { if (GameStepPropertiesHelper.isRemoveAirThatCanNotLand(getGameData())) { if (!canAirLand(true, id)) { // continue with the move loop move(nonCombat, stepName); } } if (!nonCombat) { if (canUnitsFight()) { move(nonCombat, stepName); } } return; } final String error = moveDel.move(moveDescription.getUnits(), moveDescription.getRoute(), moveDescription.getTransportsThatCanBeLoaded(), moveDescription.getDependentUnits()); if (error != null) { ui.notifyError(error); } move(nonCombat, stepName); } private boolean canAirLand(final boolean movePhase, final PlayerID player) { Collection<Territory> airCantLand; try { if (movePhase) { airCantLand = ((IMoveDelegate) getPlayerBridge().getRemoteDelegate()).getTerritoriesWhereAirCantLand(player); } else { airCantLand = ((IAbstractPlaceDelegate) getPlayerBridge().getRemoteDelegate()).getTerritoriesWhereAirCantLand(); } } catch (final ClassCastException e) { final String errorContext = "PlayerBridge step name: " + getPlayerBridge().getStepName() + ", Remote class name: " + getPlayerBridge().getRemoteDelegate().getClass(); // for some reason the client is not seeing or getting these errors, so print to err too System.err.println(errorContext); ClientLogger.logQuietly(e); throw new IllegalStateException(errorContext, e); } if (airCantLand.isEmpty()) { return true; } else { return ui.getOKToLetAirDie(getPlayerID(), airCantLand, movePhase); } } private boolean canUnitsFight() { Collection<Territory> unitsCantFight; try { unitsCantFight = ((IMoveDelegate) getPlayerBridge().getRemoteDelegate()).getTerritoriesWhereUnitsCantFight(); } catch (final ClassCastException e) { final String errorContext = "PlayerBridge step name: " + getPlayerBridge().getStepName() + ", Remote class name: " + getPlayerBridge().getRemoteDelegate().getClass(); ClientLogger.logQuietly(errorContext, e); throw new IllegalStateException(errorContext, e); } if (unitsCantFight.isEmpty()) { return false; } else { return !ui.getOKToLetUnitsDie(unitsCantFight, true); } } private void purchase(final boolean bid) { if (getPlayerBridge().isGameOver()) { return; } final PlayerID id = getPlayerID(); // play a sound for this phase if (!bid && !m_soundPlayedAlreadyPurchase) { ClipPlayer.play(SoundPath.CLIP_PHASE_PURCHASE, id); m_soundPlayedAlreadyPurchase = true; } // Check if any factories need to be repaired String error = null; if (id.getRepairFrontier() != null && id.getRepairFrontier().getRules() != null && !id.getRepairFrontier().getRules().isEmpty()) { final GameData data = getGameData(); if (isDamageFromBombingDoneToUnitsInsteadOfTerritories(data)) { final Match<Unit> myDamaged = new CompositeMatchAnd<>(Matches.unitIsOwnedBy(id), Matches.UnitHasTakenSomeBombingUnitDamage); final Collection<Unit> damagedUnits = new ArrayList<>(); for (final Territory t : data.getMap().getTerritories()) { damagedUnits.addAll(Match.getMatches(t.getUnits().getUnits(), myDamaged)); } if (damagedUnits.size() > 0) { final HashMap<Unit, IntegerMap<RepairRule>> repair = ui.getRepair(id, bid, GameStepPropertiesHelper.getRepairPlayers(data, id)); if (repair != null) { final IPurchaseDelegate purchaseDel; try { purchaseDel = (IPurchaseDelegate) getPlayerBridge().getRemoteDelegate(); } catch (final ClassCastException e) { final String errorContext = "PlayerBridge step name: " + getPlayerBridge().getStepName() + ", Remote class name: " + getPlayerBridge().getRemoteDelegate().getClass(); // for some reason the client is not seeing or getting these errors, so print to err too System.err.println(errorContext); ClientLogger.logQuietly(e); throw new IllegalStateException(errorContext, e); } error = purchaseDel.purchaseRepair(repair); if (error != null) { ui.notifyError(error); // dont give up, keep going purchase(bid); } } } } } final IntegerMap<ProductionRule> prod = ui.getProduction(id, bid); if (prod == null) { return; } final IPurchaseDelegate purchaseDel; try { purchaseDel = (IPurchaseDelegate) getPlayerBridge().getRemoteDelegate(); } catch (final ClassCastException e) { final String errorContext = "PlayerBridge step name: " + getPlayerBridge().getStepName() + ", Remote class name: " + getPlayerBridge().getRemoteDelegate().getClass(); // for some reason the client is not seeing or getting these errors, so print to err too System.err.println(errorContext); ClientLogger.logQuietly(e); throw new IllegalStateException(errorContext, e); } error = purchaseDel.purchase(prod); if (error != null) { ui.notifyError(error); // dont give up, keep going purchase(bid); } } private void battle() { if (getPlayerBridge().isGameOver()) { return; } final IBattleDelegate battleDel; try { battleDel = (IBattleDelegate) getPlayerBridge().getRemoteDelegate(); } catch (final ClassCastException e) { final String errorContext = "PlayerBridge step name: " + getPlayerBridge().getStepName() + ", Remote class name: " + getPlayerBridge().getRemoteDelegate().getClass(); ClientLogger.logQuietly(errorContext, e); // TODO: this code is triplicated in the code.. throw new IllegalStateException(errorContext, e); } final PlayerID id = getPlayerID(); while (true) { if (getPlayerBridge().isGameOver()) { return; } final BattleListing battles = battleDel.getBattles(); if (battles.isEmpty()) { final IBattle battle = battleDel.getCurrentBattle(); if (battle != null) { // this should never happen, but it happened once.... System.err.println("Current battle exists but is not on pending list: " + battle.toString()); battleDel.fightCurrentBattle(); } return; } if (!m_soundPlayedAlreadyBattle) { ClipPlayer.play(SoundPath.CLIP_PHASE_BATTLE, id); m_soundPlayedAlreadyBattle = true; } final FightBattleDetails details = ui.getBattle(id, battles.getBattles()); if (getPlayerBridge().isGameOver()) { return; } if (details != null) { final String error = battleDel.fightBattle(details.getWhere(), details.isBombingRaid(), details.getBattleType()); if (error != null) { ui.notifyError(error); } } } } private void place() { boolean bid = GameStepPropertiesHelper.isBid(getGameData()); if (getPlayerBridge().isGameOver()) { return; } final PlayerID id = getPlayerID(); final IAbstractPlaceDelegate placeDel; try { placeDel = (IAbstractPlaceDelegate) getPlayerBridge().getRemoteDelegate(); } catch (final ClassCastException e) { final String errorContext = "PlayerBridge step name: " + getPlayerBridge().getStepName() + ", Remote class name: " + getPlayerBridge().getRemoteDelegate().getClass(); // for some reason the client is not seeing or getting these errors, so print to err too System.err.println(errorContext); ClientLogger.logQuietly(e); throw new IllegalStateException(errorContext, e); } while (true) { if (!m_soundPlayedAlreadyPlacement) { ClipPlayer.play(SoundPath.CLIP_PHASE_PLACEMENT, id); m_soundPlayedAlreadyPlacement = true; } final PlaceData placeData = ui.waitForPlace(id, bid, getPlayerBridge()); if (placeData == null) { // this only happens in lhtr rules if (!GameStepPropertiesHelper.isRemoveAirThatCanNotLand(getGameData()) || canAirLand(false, id) || getPlayerBridge().isGameOver()) { return; } else { continue; } } final String error = placeDel.placeUnits(placeData.getUnits(), placeData.getAt(), bid ? IAbstractPlaceDelegate.BidMode.BID : IAbstractPlaceDelegate.BidMode.NOT_BID); if (error != null) { ui.notifyError(error); } } } private void endTurn() { if (getPlayerBridge().isGameOver()) { return; } final GameData data = getGameData(); // play a sound for this phase final IAbstractForumPosterDelegate endTurnDelegate; try { endTurnDelegate = (IAbstractForumPosterDelegate) getPlayerBridge().getRemoteDelegate(); } catch (final ClassCastException e) { final String errorContext = "PlayerBridge step name: " + getPlayerBridge().getStepName() + ", Remote class name: " + getPlayerBridge().getRemoteDelegate().getClass(); // for some reason the client is not seeing or getting these errors, so print to err too System.err.println(errorContext); ClientLogger.logQuietly(e); throw new IllegalStateException(errorContext, e); } if (!m_soundPlayedAlreadyEndTurn && TerritoryAttachment.doWeHaveEnoughCapitalsToProduce(getPlayerID(), data)) { // do not play if we are reloading a savegame from pbem (gets annoying) if (!endTurnDelegate.getHasPostedTurnSummary()) { ClipPlayer.play(SoundPath.CLIP_PHASE_END_TURN, getPlayerID()); } m_soundPlayedAlreadyEndTurn = true; } ui.waitForEndTurn(getPlayerID(), getPlayerBridge()); } @Override public CasualtyDetails selectCasualties(final Collection<Unit> selectFrom, final Map<Unit, Collection<Unit>> dependents, final int count, final String message, final DiceRoll dice, final PlayerID hit, final Collection<Unit> friendlyUnits, final PlayerID enemyPlayer, final Collection<Unit> enemyUnits, final boolean amphibious, final Collection<Unit> amphibiousLandAttackers, final CasualtyList defaultCasualties, final GUID battleID, final Territory battlesite, final boolean allowMultipleHitsPerUnit) { return ui.getBattlePanel().getCasualties(selectFrom, dependents, count, message, dice, hit, defaultCasualties, battleID, allowMultipleHitsPerUnit); } @Override public int[] selectFixedDice(final int numDice, final int hitAt, final boolean hitOnlyIfEquals, final String title, final int diceSides) { return ui.selectFixedDice(numDice, hitAt, hitOnlyIfEquals, title, diceSides); } @Override public Territory selectBombardingTerritory(final Unit unit, final Territory unitTerritory, final Collection<Territory> territories, final boolean noneAvailable) { return ui.getBattlePanel().getBombardment(unit, unitTerritory, territories, noneAvailable); } /* * Ask if the player wants to attack subs */ @Override public boolean selectAttackSubs(final Territory unitTerritory) { return ui.getBattlePanel().getAttackSubs(unitTerritory); } /* * Ask if the player wants to attack transports */ @Override public boolean selectAttackTransports(final Territory unitTerritory) { return ui.getBattlePanel().getAttackTransports(unitTerritory); } /* * Ask if the player wants to attack units */ @Override public boolean selectAttackUnits(final Territory unitTerritory) { return ui.getBattlePanel().getAttackUnits(unitTerritory); } /* * Ask if the player wants to shore bombard */ @Override public boolean selectShoreBombard(final Territory unitTerritory) { return ui.getBattlePanel().getShoreBombard(unitTerritory); } @Override public boolean shouldBomberBomb(final Territory territory) { return ui.getStrategicBombingRaid(territory); } @Override public Unit whatShouldBomberBomb(final Territory territory, final Collection<Unit> potentialTargets, final Collection<Unit> bombers) { return ui.getStrategicBombingRaidTarget(territory, potentialTargets, bombers); } @Override public Territory whereShouldRocketsAttack(final Collection<Territory> candidates, final Territory from) { return ui.getRocketAttack(candidates, from); } @Override public Collection<Unit> getNumberOfFightersToMoveToNewCarrier(final Collection<Unit> fightersThatCanBeMoved, final Territory from) { return ui.moveFightersToCarrier(fightersThatCanBeMoved, from); } @Override public Territory selectTerritoryForAirToLand(final Collection<Territory> candidates, final Territory currentTerritory, final String unitMessage) { return ui.selectTerritoryForAirToLand(candidates, currentTerritory, unitMessage); } @Override public boolean confirmMoveInFaceOfAA(final Collection<Territory> aaFiringTerritories) { final String question = "Your units will be fired on in: " + MyFormatter.defaultNamedToTextList(aaFiringTerritories, " and ", false) + ". Do you still want to move?"; return ui.getOK(question); } @Override public boolean confirmMoveKamikaze() { final String question = "Not all air units in destination territory can land, do you still want to move?"; return ui.getOK(question); } @Override public boolean confirmMoveHariKari() { final String question = "All units in destination territory will automatically die, do you still want to move?"; return ui.getOK(question); } @Override public Territory retreatQuery(final GUID battleID, final boolean submerge, final Territory battleTerritory, final Collection<Territory> possibleTerritories, final String message) { return ui.getBattlePanel().getRetreat(battleID, message, possibleTerritories, submerge); } @Override public HashMap<Territory, Collection<Unit>> scrambleUnitsQuery(final Territory scrambleTo, final Map<Territory, Tuple<Collection<Unit>, Collection<Unit>>> possibleScramblers) { return ui.scrambleUnitsQuery(scrambleTo, possibleScramblers); } @Override public Collection<Unit> selectUnitsQuery(final Territory current, final Collection<Unit> possible, final String message) { return ui.selectUnitsQuery(current, possible, message); } @Override public void confirmEnemyCasualties(final GUID battleId, final String message, final PlayerID hitPlayer) { // no need, we have already confirmed since we are firing player if (ui.getLocalPlayers().playing(hitPlayer)) { return; } // we dont want to confirm enemy casualties if (!BattleDisplay.getShowEnemyCasualtyNotification()) { return; } ui.getBattlePanel().confirmCasualties(battleId, message); } @Override public void confirmOwnCasualties(final GUID battleId, final String message) { ui.getBattlePanel().confirmCasualties(battleId, message); } public final boolean isDamageFromBombingDoneToUnitsInsteadOfTerritories(final GameData data) { return games.strategy.triplea.Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(data); } @Override public HashMap<Territory, HashMap<Unit, IntegerMap<Resource>>> selectKamikazeSuicideAttacks( final HashMap<Territory, Collection<Unit>> possibleUnitsToAttack) { final PlayerID id = getPlayerID(); final PlayerAttachment pa = PlayerAttachment.get(id); if (pa == null) { return null; } final IntegerMap<Resource> resourcesAndAttackValues = pa.getSuicideAttackResources(); if (resourcesAndAttackValues.size() <= 0) { return null; } final IntegerMap<Resource> playerResourceCollection = id.getResources().getResourcesCopy(); final IntegerMap<Resource> attackTokens = new IntegerMap<>(); for (final Resource possible : resourcesAndAttackValues.keySet()) { final int amount = playerResourceCollection.getInt(possible); if (amount > 0) { attackTokens.put(possible, amount); } } if (attackTokens.size() <= 0) { return null; } final HashMap<Territory, HashMap<Unit, IntegerMap<Resource>>> rVal = new HashMap<>(); for (final Entry<Resource, Integer> entry : attackTokens.entrySet()) { final Resource r = entry.getKey(); final int max = entry.getValue(); final HashMap<Territory, IntegerMap<Unit>> selection = ui.selectKamikazeSuicideAttacks(possibleUnitsToAttack, r, max); for (final Entry<Territory, IntegerMap<Unit>> selectionEntry : selection.entrySet()) { final Territory t = selectionEntry.getKey(); HashMap<Unit, IntegerMap<Resource>> currentTerr = rVal.get(t); if (currentTerr == null) { currentTerr = new HashMap<>(); } for (final Entry<Unit, Integer> unitEntry : selectionEntry.getValue().entrySet()) { final Unit u = unitEntry.getKey(); IntegerMap<Resource> currentUnit = currentTerr.get(u); if (currentUnit == null) { currentUnit = new IntegerMap<>(); } currentUnit.add(r, unitEntry.getValue()); currentTerr.put(u, currentUnit); } rVal.put(t, currentTerr); } } return rVal; } @Override public Tuple<Territory, Set<Unit>> pickTerritoryAndUnits(final List<Territory> territoryChoices, final List<Unit> unitChoices, final int unitsPerPick) { if (territoryChoices == null || territoryChoices.isEmpty() || unitsPerPick < 1) { return Tuple.of(null, new HashSet<Unit>()); } return ui.pickTerritoryAndUnits(this.getPlayerID(), territoryChoices, unitChoices, unitsPerPick); } }