package games.strategy.triplea.delegate;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import games.strategy.engine.data.Change;
import games.strategy.engine.data.CompositeChange;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.Route;
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.Unit;
import games.strategy.engine.data.changefactory.ChangeFactory;
import games.strategy.engine.delegate.IDelegateBridge;
import games.strategy.triplea.TripleAUnit;
import games.strategy.triplea.attachments.AbstractTriggerAttachment;
import games.strategy.triplea.attachments.ICondition;
import games.strategy.triplea.attachments.TriggerAttachment;
import games.strategy.triplea.attachments.UnitAttachment;
import games.strategy.triplea.delegate.dataObjects.MoveValidationResult;
import games.strategy.triplea.formatter.MyFormatter;
import games.strategy.util.CompositeMatchAnd;
import games.strategy.util.CompositeMatchOr;
import games.strategy.util.IntegerMap;
import games.strategy.util.Match;
/**
* Responsible for moving units on the board.
*
* <p>
* Responsible for checking the validity of a move, and for moving the units.
* </p>
*/
public class MoveDelegate extends AbstractMoveDelegate {
public static String CLEANING_UP_DURING_MOVEMENT_PHASE = "Cleaning up during movement phase";
// needToInitialize means we only do certain things once, so that if a game is saved then
// loaded, they aren't done again
private boolean m_needToInitialize = true;
private boolean m_needToDoRockets = true;
private IntegerMap<Territory> m_PUsLost = new IntegerMap<>();
public MoveDelegate() {}
/**
* Called before the delegate will run, AND before "start" is called.
*/
@Override
public void setDelegateBridgeAndPlayer(final IDelegateBridge iDelegateBridge) {
super.setDelegateBridgeAndPlayer(new GameDelegateBridge(iDelegateBridge));
}
/**
* Called before the delegate will run.
*/
@Override
public void start() {
super.start();
final GameData data = getData();
if (m_needToInitialize) {
// territory property changes triggered at beginning of combat move
// TODO create new delegate called "start of turn" and move them there.
// First set up a match for what we want to have fire as a default in this delegate. List out as a composite match
// OR. use 'null, null' because this is the Default firing location for any trigger that does NOT have 'when' set.
HashMap<ICondition, Boolean> testedConditions = null;
final Match<TriggerAttachment> moveCombatDelegateBeforeBonusTriggerMatch =
new CompositeMatchAnd<>(AbstractTriggerAttachment.availableUses,
AbstractTriggerAttachment.whenOrDefaultMatch(null, null),
new CompositeMatchOr<TriggerAttachment>(AbstractTriggerAttachment.notificationMatch(),
TriggerAttachment.playerPropertyMatch(), TriggerAttachment.relationshipTypePropertyMatch(),
TriggerAttachment.territoryPropertyMatch(), TriggerAttachment.territoryEffectPropertyMatch(),
TriggerAttachment.removeUnitsMatch(), TriggerAttachment.changeOwnershipMatch()));
final Match<TriggerAttachment> moveCombatDelegateAfterBonusTriggerMatch =
new CompositeMatchAnd<>(AbstractTriggerAttachment.availableUses,
AbstractTriggerAttachment.whenOrDefaultMatch(null, null),
new CompositeMatchOr<TriggerAttachment>(TriggerAttachment.placeMatch()));
final Match<TriggerAttachment> moveCombatDelegateAllTriggerMatch = new CompositeMatchOr<>(
moveCombatDelegateBeforeBonusTriggerMatch, moveCombatDelegateAfterBonusTriggerMatch);
if (GameStepPropertiesHelper.isCombatMove(data) && games.strategy.triplea.Properties.getTriggers(data)) {
final HashSet<TriggerAttachment> toFirePossible = TriggerAttachment.collectForAllTriggersMatching(
new HashSet<>(Collections.singleton(m_player)), moveCombatDelegateAllTriggerMatch, m_bridge);
if (!toFirePossible.isEmpty()) {
// collect conditions and test them for ALL triggers, both those that we will fire before and those we will
// fire after.
testedConditions = TriggerAttachment.collectTestsForAllTriggers(toFirePossible, m_bridge);
final HashSet<TriggerAttachment> toFireBeforeBonus =
TriggerAttachment.collectForAllTriggersMatching(new HashSet<>(Collections.singleton(m_player)),
moveCombatDelegateBeforeBonusTriggerMatch, m_bridge);
if (!toFireBeforeBonus.isEmpty()) {
// get all triggers that are satisfied based on the tested conditions.
final Set<TriggerAttachment> toFireTestedAndSatisfied = new HashSet<>(
Match.getMatches(toFireBeforeBonus, AbstractTriggerAttachment.isSatisfiedMatch(testedConditions)));
// now list out individual types to fire, once for each of the matches above.
TriggerAttachment.triggerNotifications(toFireTestedAndSatisfied, m_bridge, null, null, true, true, true,
true);
TriggerAttachment.triggerPlayerPropertyChange(toFireTestedAndSatisfied, m_bridge, null, null, true, true,
true, true);
TriggerAttachment.triggerRelationshipTypePropertyChange(toFireTestedAndSatisfied, m_bridge, null, null,
true, true, true, true);
TriggerAttachment.triggerTerritoryPropertyChange(toFireTestedAndSatisfied, m_bridge, null, null, true,
true, true, true);
TriggerAttachment.triggerTerritoryEffectPropertyChange(toFireTestedAndSatisfied, m_bridge, null, null,
true, true, true, true);
TriggerAttachment.triggerChangeOwnership(toFireTestedAndSatisfied, m_bridge, null, null, true, true, true,
true);
TriggerAttachment.triggerUnitRemoval(toFireTestedAndSatisfied, m_bridge, null, null, true, true, true,
true);
}
}
}
// repair 2-hit units at beginning of turn (some maps have combat move before purchase, so i think it is better to
// do this at beginning of combat move)
if (GameStepPropertiesHelper.isRepairUnits(data)) {
MoveDelegate.repairMultipleHitPointUnits(m_bridge, m_player);
}
// reset any bonus of units, and give movement to units which begin the turn in the same territory as units with
// giveMovement (like air and naval bases)
if (GameStepPropertiesHelper.isGiveBonusMovement(data)) {
resetAndGiveBonusMovement();
}
// take away all movement from allied fighters sitting on damaged carriers
removeMovementFromAirOnDamagedAlliedCarriers(m_bridge, m_player);
// placing triggered units at beginning of combat move, but after bonuses and repairing, etc, have been done.
if (GameStepPropertiesHelper.isCombatMove(data) && games.strategy.triplea.Properties.getTriggers(data)) {
final HashSet<TriggerAttachment> toFireAfterBonus = TriggerAttachment.collectForAllTriggersMatching(
new HashSet<>(Collections.singleton(m_player)), moveCombatDelegateAfterBonusTriggerMatch, m_bridge);
if (!toFireAfterBonus.isEmpty()) {
// get all triggers that are satisfied based on the tested conditions.
final Set<TriggerAttachment> toFireTestedAndSatisfied = new HashSet<>(
Match.getMatches(toFireAfterBonus, AbstractTriggerAttachment.isSatisfiedMatch(testedConditions)));
// now list out individual types to fire, once for each of the matches above.
TriggerAttachment.triggerUnitPlacement(toFireTestedAndSatisfied, m_bridge, null, null, true, true, true,
true);
}
}
if (GameStepPropertiesHelper.isResetUnitStateAtStart(data)) {
resetUnitStateAndDelegateState();
}
m_needToInitialize = false;
}
}
private void resetAndGiveBonusMovement() {
boolean addedHistoryEvent = false;
final Change changeReset = resetBonusMovement();
if (!changeReset.isEmpty()) {
m_bridge.getHistoryWriter().startEvent("Resetting and Giving Bonus Movement to Units");
m_bridge.addChange(changeReset);
addedHistoryEvent = true;
}
Change changeBonus = null;
if (games.strategy.triplea.Properties.getUnitsMayGiveBonusMovement(getData())) {
changeBonus = giveBonusMovement(m_bridge, m_player);
}
if (changeBonus != null && !changeBonus.isEmpty()) {
if (!addedHistoryEvent) {
m_bridge.getHistoryWriter().startEvent("Resetting and Giving Bonus Movement to Units");
}
m_bridge.addChange(changeBonus);
}
}
/**
* Called before the delegate will stop running.
*/
@Override
public void end() {
super.end();
final GameData data = getData();
if (GameStepPropertiesHelper.isRemoveAirThatCanNotLand(data)) {
removeAirThatCantLand();
}
// WW2V2/WW2V3, fires at end of combat move
// WW2V1, fires at end of non combat move
if (GameStepPropertiesHelper.isFireRockets(data)) {
if (m_needToDoRockets && TechTracker.hasRocket(m_bridge.getPlayerID())) {
final RocketsFireHelper helper = new RocketsFireHelper();
helper.fireRockets(m_bridge, m_bridge.getPlayerID());
m_needToDoRockets = false;
}
}
// do at the end of the round, if we do it at the start of non combat, then we may do it in the middle of the round,
// while loading.
if (GameStepPropertiesHelper.isResetUnitStateAtEnd(data)) {
resetUnitStateAndDelegateState();
}
m_needToInitialize = true;
m_needToDoRockets = true;
}
@Override
public Serializable saveState() {
final MoveExtendedDelegateState state = new MoveExtendedDelegateState();
state.superState = super.saveState();
state.m_needToInitialize = m_needToInitialize;
state.m_needToDoRockets = m_needToDoRockets;
state.m_PUsLost = m_PUsLost;
return state;
}
@Override
public void loadState(final Serializable state) {
final MoveExtendedDelegateState s = (MoveExtendedDelegateState) state;
super.loadState(s.superState);
m_needToInitialize = s.m_needToInitialize;
m_needToDoRockets = s.m_needToDoRockets;
m_PUsLost = s.m_PUsLost;
}
@Override
public boolean delegateCurrentlyRequiresUserInput() {
final CompositeMatchAnd<Unit> moveableUnitOwnedByMe = new CompositeMatchAnd<>();
moveableUnitOwnedByMe.add(Matches.unitIsOwnedBy(m_player));
// right now, land units on transports have movement taken away when they their transport moves
moveableUnitOwnedByMe.add(new CompositeMatchOr<>(Matches.unitHasMovementLeft,
new CompositeMatchAnd<Unit>(Matches.UnitIsLand, Matches.unitIsBeingTransported())));
// if not non combat, cannot move aa units
if (GameStepPropertiesHelper.isCombatMove(getData())) {
moveableUnitOwnedByMe.add(Matches.UnitCanNotMoveDuringCombatMove.invert());
}
for (final Territory item : getData().getMap().getTerritories()) {
if (item.getUnits().someMatch(moveableUnitOwnedByMe)) {
return true;
}
}
return false;
}
private Change resetBonusMovement() {
final GameData data = getData();
final CompositeChange change = new CompositeChange();
for (final Unit u : data.getUnits()) {
if (TripleAUnit.get(u).getBonusMovement() != 0) {
change.add(ChangeFactory.unitPropertyChange(u, 0, TripleAUnit.BONUS_MOVEMENT));
}
}
return change;
}
private void resetUnitStateAndDelegateState() {
// while not a 'unit state', this is fine here for now. since we only have one instance of this delegate, as long as
// it gets cleared once per player's turn block, we are fine.
m_PUsLost.clear();
final Change change = getResetUnitStateChange(getData());
if (!change.isEmpty()) {
// if no non-combat occurred, we may have cleanup left from combat
// that we need to spawn an event for
m_bridge.getHistoryWriter().startEvent(CLEANING_UP_DURING_MOVEMENT_PHASE);
m_bridge.addChange(change);
}
}
public static Change getResetUnitStateChange(final GameData data) {
final CompositeChange change = new CompositeChange();
for (final Unit u : data.getUnits()) {
final TripleAUnit taUnit = TripleAUnit.get(u);
if (taUnit.getAlreadyMoved() != 0) {
change.add(ChangeFactory.unitPropertyChange(u, 0, TripleAUnit.ALREADY_MOVED));
}
if (taUnit.getWasInCombat()) {
change.add(ChangeFactory.unitPropertyChange(u, false, TripleAUnit.WAS_IN_COMBAT));
}
if (taUnit.getSubmerged()) {
change.add(ChangeFactory.unitPropertyChange(u, false, TripleAUnit.SUBMERGED));
}
if (taUnit.getAirborne()) {
change.add(ChangeFactory.unitPropertyChange(u, false, TripleAUnit.AIRBORNE));
}
if (taUnit.getLaunched() != 0) {
change.add(ChangeFactory.unitPropertyChange(u, 0, TripleAUnit.LAUNCHED));
}
if (!taUnit.getUnloaded().isEmpty()) {
change.add(ChangeFactory.unitPropertyChange(u, Collections.EMPTY_LIST, TripleAUnit.UNLOADED));
}
if (taUnit.getWasLoadedThisTurn()) {
change.add(ChangeFactory.unitPropertyChange(u, Boolean.FALSE, TripleAUnit.LOADED_THIS_TURN));
}
if (taUnit.getUnloadedTo() != null) {
change.add(ChangeFactory.unitPropertyChange(u, null, TripleAUnit.UNLOADED_TO));
}
if (taUnit.getWasUnloadedInCombatPhase()) {
change.add(ChangeFactory.unitPropertyChange(u, Boolean.FALSE, TripleAUnit.UNLOADED_IN_COMBAT_PHASE));
}
if (taUnit.getWasAmphibious()) {
change.add(ChangeFactory.unitPropertyChange(u, Boolean.FALSE, TripleAUnit.UNLOADED_AMPHIBIOUS));
}
}
return change;
}
private void removeMovementFromAirOnDamagedAlliedCarriers(final IDelegateBridge aBridge, final PlayerID player) {
final GameData data = aBridge.getData();
final Match<Unit> crippledAlliedCarriersMatch = new CompositeMatchAnd<>(Matches.isUnitAllied(player, data),
Matches.unitIsOwnedBy(player).invert(), Matches.UnitIsCarrier,
Matches.UnitHasWhenCombatDamagedEffect(UnitAttachment.UNITSMAYNOTLEAVEALLIEDCARRIER));
final Match<Unit> ownedFightersMatch =
new CompositeMatchAnd<>(Matches.unitIsOwnedBy(player), Matches.UnitIsAir,
Matches.UnitCanLandOnCarrier, Matches.unitHasMovementLeft);
final CompositeChange change = new CompositeChange();
for (final Territory t : data.getMap().getTerritories()) {
final Collection<Unit> ownedFighters = t.getUnits().getMatches(ownedFightersMatch);
if (ownedFighters.isEmpty()) {
continue;
}
final Collection<Unit> crippledAlliedCarriers =
Match.getMatches(t.getUnits().getUnits(), crippledAlliedCarriersMatch);
if (crippledAlliedCarriers.isEmpty()) {
continue;
}
for (final Unit fighter : ownedFighters) {
final TripleAUnit taUnit = (TripleAUnit) fighter;
if (taUnit.getTransportedBy() != null) {
if (crippledAlliedCarriers.contains(taUnit.getTransportedBy())) {
change.add(ChangeFactory.markNoMovementChange(fighter));
}
}
}
}
if (!change.isEmpty()) {
aBridge.addChange(change);
}
}
private Change giveBonusMovement(final IDelegateBridge aBridge, final PlayerID player) {
final GameData data = aBridge.getData();
final CompositeChange change = new CompositeChange();
for (final Territory t : data.getMap().getTerritories()) {
for (final Unit u : t.getUnits().getUnits()) {
if (Matches.UnitCanBeGivenBonusMovementByFacilitiesInItsTerritory(t, player, data).match(u)) {
if (!Matches.isUnitAllied(player, data).match(u)) {
continue;
}
int bonusMovement = Integer.MIN_VALUE;
final Collection<Unit> givesBonusUnits = new ArrayList<>();
final Match<Unit> givesBonusUnit = new CompositeMatchAnd<>(Matches.alliedUnit(player, data),
Matches.UnitCanGiveBonusMovementToThisUnit(u));
givesBonusUnits.addAll(Match.getMatches(t.getUnits().getUnits(), givesBonusUnit));
if (Matches.UnitIsSea.match(u)) {
final Match<Unit> givesBonusUnitLand = new CompositeMatchAnd<>(givesBonusUnit, Matches.UnitIsLand);
final List<Territory> neighbors =
new ArrayList<>(data.getMap().getNeighbors(t, Matches.TerritoryIsLand));
for (final Territory current : neighbors) {
givesBonusUnits.addAll(Match.getMatches(current.getUnits().getUnits(), givesBonusUnitLand));
}
} else if (Matches.UnitIsLand.match(u)) {
final Match<Unit> givesBonusUnitSea = new CompositeMatchAnd<>(givesBonusUnit, Matches.UnitIsSea);
final List<Territory> neighbors =
new ArrayList<>(data.getMap().getNeighbors(t, Matches.TerritoryIsWater));
for (final Territory current : neighbors) {
givesBonusUnits.addAll(Match.getMatches(current.getUnits().getUnits(), givesBonusUnitSea));
}
}
for (final Unit bonusGiver : givesBonusUnits) {
final int tempBonus = UnitAttachment.get(bonusGiver.getType()).getGivesMovement().getInt(u.getType());
if (tempBonus > bonusMovement) {
bonusMovement = tempBonus;
}
}
if (bonusMovement != Integer.MIN_VALUE && bonusMovement != 0) {
bonusMovement = Math.max(bonusMovement, (UnitAttachment.get(u.getType()).getMovement(player) * -1));
change.add(ChangeFactory.unitPropertyChange(u, bonusMovement, TripleAUnit.BONUS_MOVEMENT));
}
}
}
}
return change;
}
public static void repairMultipleHitPointUnits(final IDelegateBridge aBridge, final PlayerID player) {
final GameData data = aBridge.getData();
final boolean repairOnlyOwn =
games.strategy.triplea.Properties.getBattleshipsRepairAtBeginningOfRound(aBridge.getData());
final Match<Unit> damagedUnits =
new CompositeMatchAnd<>(Matches.UnitHasMoreThanOneHitPointTotal, Matches.UnitHasTakenSomeDamage);
final Match<Unit> damagedUnitsOwned = new CompositeMatchAnd<>(damagedUnits, Matches.unitIsOwnedBy(player));
final Map<Territory, Set<Unit>> damagedMap = new HashMap<>();
final Iterator<Territory> iterTerritories = data.getMap().getTerritories().iterator();
while (iterTerritories.hasNext()) {
final Territory current = iterTerritories.next();
final Set<Unit> damaged;
if (!games.strategy.triplea.Properties.getTwoHitPointUnitsRequireRepairFacilities(data)) {
if (repairOnlyOwn) {
// we only repair ours
damaged = new HashSet<>(current.getUnits().getMatches(damagedUnitsOwned));
} else {
// we repair everyone's
damaged = new HashSet<>(current.getUnits().getMatches(damagedUnits));
}
} else {
damaged = new HashSet<>(current.getUnits().getMatches(new CompositeMatchAnd<>(damagedUnitsOwned,
Matches.UnitCanBeRepairedByFacilitiesInItsTerritory(current, player, data))));
}
if (!damaged.isEmpty()) {
damagedMap.put(current, damaged);
}
}
if (damagedMap.isEmpty()) {
return;
}
final Map<Unit, Territory> fullyRepaired = new HashMap<>();
final IntegerMap<Unit> newHitsMap = new IntegerMap<>();
for (final Entry<Territory, Set<Unit>> entry : damagedMap.entrySet()) {
for (final Unit u : entry.getValue()) {
final int repairAmount = getLargestRepairRateForThisUnit(u, entry.getKey(), data);
final int currentHits = u.getHits();
final int newHits = Math.max(0, Math.min(currentHits, (currentHits - repairAmount)));
if (newHits != currentHits) {
newHitsMap.put(u, newHits);
}
if (newHits <= 0) {
fullyRepaired.put(u, entry.getKey());
}
}
}
aBridge.getHistoryWriter().startEvent(
newHitsMap.size() + " " + MyFormatter.pluralize("unit", newHitsMap.size()) + " repaired.",
new HashSet<>(newHitsMap.keySet()));
aBridge.addChange(ChangeFactory.unitsHit(newHitsMap));
// now if damaged includes any carriers that are repairing, and have damaged abilities set for not allowing air
// units to leave while damaged, we need to remove those air units now
final Collection<Unit> damagedCarriers = Match.getMatches(fullyRepaired.keySet(),
Matches.UnitHasWhenCombatDamagedEffect(UnitAttachment.UNITSMAYNOTLEAVEALLIEDCARRIER));
// now cycle through those now-repaired carriers, and remove allied air from being dependent
final CompositeChange clearAlliedAir = new CompositeChange();
for (final Unit carrier : damagedCarriers) {
final CompositeChange change = MustFightBattle.clearTransportedByForAlliedAirOnCarrier(
Collections.singleton(carrier), fullyRepaired.get(carrier), carrier.getOwner(), data);
if (!change.isEmpty()) {
clearAlliedAir.add(change);
}
}
if (!clearAlliedAir.isEmpty()) {
aBridge.addChange(clearAlliedAir);
}
}
/**
* This has to be the exact same as Matches.UnitCanBeRepairedByFacilitiesInItsTerritory()
*/
private static int getLargestRepairRateForThisUnit(final Unit unitToBeRepaired, final Territory territoryUnitIsIn,
final GameData data) {
if (!games.strategy.triplea.Properties.getTwoHitPointUnitsRequireRepairFacilities(data)) {
return 1;
}
final Set<Unit> repairUnitsForThisUnit = new HashSet<>();
final PlayerID owner = unitToBeRepaired.getOwner();
final Match<Unit> repairUnit = new CompositeMatchAnd<>(Matches.alliedUnit(owner, data),
Matches.UnitCanRepairOthers, Matches.UnitCanRepairThisUnit(unitToBeRepaired));
repairUnitsForThisUnit.addAll(territoryUnitIsIn.getUnits().getMatches(repairUnit));
if (Matches.UnitIsSea.match(unitToBeRepaired)) {
final Match<Unit> repairUnitLand = new CompositeMatchAnd<>(repairUnit, Matches.UnitIsLand);
final List<Territory> neighbors =
new ArrayList<>(data.getMap().getNeighbors(territoryUnitIsIn, Matches.TerritoryIsLand));
for (final Territory current : neighbors) {
repairUnitsForThisUnit.addAll(current.getUnits().getMatches(repairUnitLand));
}
} else if (Matches.UnitIsLand.match(unitToBeRepaired)) {
final Match<Unit> repairUnitSea = new CompositeMatchAnd<>(repairUnit, Matches.UnitIsSea);
final List<Territory> neighbors =
new ArrayList<>(data.getMap().getNeighbors(territoryUnitIsIn, Matches.TerritoryIsWater));
for (final Territory current : neighbors) {
repairUnitsForThisUnit.addAll(current.getUnits().getMatches(repairUnitSea));
}
}
int largest = 0;
for (final Unit u : repairUnitsForThisUnit) {
final int repair = UnitAttachment.get(u.getType()).getRepairsUnits().getInt(unitToBeRepaired.getType());
if (largest < repair) {
largest = repair;
}
}
return largest;
}
@Override
public String move(final Collection<Unit> units, final Route route, final Collection<Unit> transportsThatCanBeLoaded,
final Map<Unit, Collection<Unit>> newDependents) {
final GameData data = getData();
// the reason we use this, is if we are in edit mode, we may have a different unit owner than the current player
final PlayerID player = getUnitsOwner(units);
final MoveValidationResult result = MoveValidator.validateMove(units, route, player, transportsThatCanBeLoaded,
newDependents, GameStepPropertiesHelper.isNonCombatMove(data, false), m_movesToUndo, data);
final StringBuilder errorMsg = new StringBuilder(100);
final int numProblems = result.getTotalWarningCount() - (result.hasError() ? 0 : 1);
final String numErrorsMsg =
numProblems > 0 ? ("; " + numProblems + " " + MyFormatter.pluralize("error", numProblems) + " not shown") : "";
if (result.hasError()) {
return errorMsg.append(result.getError()).append(numErrorsMsg).toString();
}
if (result.hasDisallowedUnits()) {
return errorMsg.append(result.getDisallowedUnitWarning(0)).append(numErrorsMsg).toString();
}
boolean isKamikaze = false;
final boolean getKamikazeAir = games.strategy.triplea.Properties.getKamikaze_Airplanes(data);
Collection<Unit> kamikazeUnits = new ArrayList<>();
// confirm kamikaze moves, and remove them from unresolved units
if (getKamikazeAir || Match.someMatch(units, Matches.UnitIsKamikaze)) {
kamikazeUnits = result.getUnresolvedUnits(MoveValidator.NOT_ALL_AIR_UNITS_CAN_LAND);
if (kamikazeUnits.size() > 0 && getRemotePlayer().confirmMoveKamikaze()) {
for (final Unit unit : kamikazeUnits) {
if (getKamikazeAir || Matches.UnitIsKamikaze.match(unit)) {
result.removeUnresolvedUnit(MoveValidator.NOT_ALL_AIR_UNITS_CAN_LAND, unit);
isKamikaze = true;
}
}
}
}
if (result.hasUnresolvedUnits()) {
return errorMsg.append(result.getUnresolvedUnitWarning(0)).append(numErrorsMsg).toString();
}
// allow user to cancel move if aa guns will fire
final AAInMoveUtil aaInMoveUtil = new AAInMoveUtil();
aaInMoveUtil.initialize(m_bridge);
final Collection<Territory> aaFiringTerritores = aaInMoveUtil.getTerritoriesWhereAAWillFire(route, units);
if (!aaFiringTerritores.isEmpty()) {
if (!getRemotePlayer().confirmMoveInFaceOfAA(aaFiringTerritores)) {
return null;
}
}
// do the move
final UndoableMove currentMove = new UndoableMove(units, route);
final String transcriptText = MyFormatter.unitsToTextNoOwner(units) + " moved from " + route.getStart().getName()
+ " to " + route.getEnd().getName();
m_bridge.getHistoryWriter().startEvent(transcriptText, currentMove.getDescriptionObject());
if (isKamikaze) {
m_bridge.getHistoryWriter().addChildToEvent("This was a kamikaze move, for at least some of the units",
kamikazeUnits);
}
m_tempMovePerformer = new MovePerformer();
m_tempMovePerformer.initialize(this);
m_tempMovePerformer.moveUnits(units, route, player, transportsThatCanBeLoaded, newDependents, currentMove);
m_tempMovePerformer = null;
return null;
}
public static Collection<Territory> getEmptyNeutral(final Route route) {
final Match<Territory> emptyNeutral =
new CompositeMatchAnd<>(Matches.TerritoryIsEmpty, Matches.TerritoryIsNeutralButNotWater);
final Collection<Territory> neutral = route.getMatches(emptyNeutral);
return neutral;
}
private void removeAirThatCantLand() {
final GameData data = getData();
final boolean lhtrCarrierProd = AirThatCantLandUtil.isLHTRCarrierProduction(data)
|| AirThatCantLandUtil.isLandExistingFightersOnNewCarriers(data);
boolean hasProducedCarriers = false;
for (final PlayerID p : GameStepPropertiesHelper.getCombinedTurns(data, m_player)) {
if (p.getUnits().someMatch(Matches.UnitIsCarrier)) {
hasProducedCarriers = true;
break;
}
}
final AirThatCantLandUtil util = new AirThatCantLandUtil(m_bridge);
util.removeAirThatCantLand(m_player, lhtrCarrierProd && hasProducedCarriers);
// if edit mode has been on, we need to clean up after all players
for (final PlayerID player : data.getPlayerList()) {
// Check if player still has units to place
if (!player.equals(m_player)) {
util.removeAirThatCantLand(player,
((player.getUnits().someMatch(Matches.UnitIsCarrier) || hasProducedCarriers) && lhtrCarrierProd));
}
}
}
/**
* @return The number of PUs that have been lost by bombing, rockets, etc.
*/
@Override
public int PUsAlreadyLost(final Territory t) {
return m_PUsLost.getInt(t);
}
/**
* Add more PUs lost to a territory due to bombing, rockets, etc.
*/
@Override
public void PUsLost(final Territory t, final int amt) {
m_PUsLost.add(t, amt);
}
}
class MoveExtendedDelegateState implements Serializable {
private static final long serialVersionUID = 5352248885420819215L;
Serializable superState;
// add other variables here:
public boolean m_firstRun = true;
public boolean m_needToInitialize;
public boolean m_needToDoRockets;
public IntegerMap<Territory> m_PUsLost;
}