package games.strategy.triplea.delegate;
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 games.strategy.engine.data.Change;
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.TerritoryEffect;
import games.strategy.engine.data.Unit;
import games.strategy.engine.delegate.IDelegateBridge;
import games.strategy.net.GUID;
import games.strategy.sound.ISound;
import games.strategy.triplea.TripleA;
import games.strategy.triplea.ai.weakAI.WeakAI;
import games.strategy.triplea.attachments.UnitAttachment;
import games.strategy.triplea.delegate.dataObjects.BattleRecord.BattleResultDescription;
import games.strategy.triplea.player.ITripleAPlayer;
import games.strategy.triplea.ui.display.ITripleADisplay;
import games.strategy.util.IntegerMap;
public abstract class AbstractBattle implements IBattle {
private static final long serialVersionUID = 871090498661731337L;
final GUID m_battleID = new GUID();
/**
* In headless mode we should NOT access any Delegates. In headless mode we are just being used to calculate results
* for an odds
* calculator so we can skip some steps for efficiency.
*/
boolean m_headless = false;
final Territory m_battleSite;
final PlayerID m_attacker;
PlayerID m_defender;
final BattleTracker m_battleTracker;
int m_round = 1;
final boolean m_isBombingRun;
boolean m_isAmphibious = false;
BattleType m_battleType;
boolean m_isOver = false;
/**
* Dependent units, maps unit -> Collection of units, if unit is lost in a battle we are dependent on
* then we lose the corresponding collection of units.
*/
final Map<Unit, Collection<Unit>> m_dependentUnits = new HashMap<>();
List<Unit> m_attackingUnits = new ArrayList<>();
List<Unit> m_defendingUnits = new ArrayList<>();
List<Unit> m_amphibiousLandAttackers = new ArrayList<>();
List<Unit> m_bombardingUnits = new ArrayList<>();
Collection<TerritoryEffect> m_territoryEffects;
BattleResultDescription m_battleResultDescription;
WhoWon m_whoWon = WhoWon.NOTFINISHED;
int m_attackerLostTUV = 0;
int m_defenderLostTUV = 0;
protected final GameData m_data;
AbstractBattle(final Territory battleSite, final PlayerID attacker, final BattleTracker battleTracker,
final boolean isBombingRun, final BattleType battleType, final GameData data) {
m_battleTracker = battleTracker;
m_attacker = attacker;
m_battleSite = battleSite;
m_territoryEffects = TerritoryEffectHelper.getEffects(battleSite);
m_isBombingRun = isBombingRun;
m_battleType = battleType;
m_data = data;
m_defender = findDefender(battleSite, attacker, data);
// Make sure that if any of the incoming data is null, we are still OK
// (tests and mockbattle use null for a lot of this stuff)
}
@Override
public Collection<Unit> getDependentUnits(final Collection<Unit> units) {
final Collection<Unit> rVal = new ArrayList<>();
for (final Unit unit : units) {
final Collection<Unit> dependent = m_dependentUnits.get(unit);
if (dependent != null) {
rVal.addAll(dependent);
}
}
return rVal;
}
protected void removeUnitsThatNoLongerExist() {
if (m_headless) {
return;
}
// we were having a problem with units that had been killed previously were still part of
// MFB's variables, so we double check that the stuff still exists here.
m_defendingUnits.retainAll(m_battleSite.getUnits().getUnits());
m_attackingUnits.retainAll(m_battleSite.getUnits().getUnits());
}
@Override
public void addBombardingUnit(final Unit unit) {
m_bombardingUnits.add(unit);
}
@Override
public Collection<Unit> getBombardingUnits() {
return new ArrayList<>(m_bombardingUnits);
}
@Override
public boolean isAmphibious() {
return m_isAmphibious;
}
@Override
public Collection<Unit> getAmphibiousLandAttackers() {
return new ArrayList<>(m_amphibiousLandAttackers);
}
@Override
public Collection<Unit> getAttackingUnits() {
return new ArrayList<>(m_attackingUnits);
}
@Override
public Collection<Unit> getDefendingUnits() {
return new ArrayList<>(m_defendingUnits);
}
@Override
public List<Unit> getRemainingAttackingUnits() {
return new ArrayList<>(m_attackingUnits);
}
@Override
public List<Unit> getRemainingDefendingUnits() {
return new ArrayList<>(m_defendingUnits);
}
@Override
public abstract boolean isEmpty();
@Override
public final boolean isOver() {
return m_isOver;
}
@Override
public void cancelBattle(final IDelegateBridge bridge) {}
@Override
public boolean isBombingRun() {
return m_isBombingRun;
}
@Override
public BattleType getBattleType() {
return m_battleType;
}
@Override
public int getBattleRound() {
return m_round;
}
@Override
public WhoWon getWhoWon() {
return m_whoWon;
}
@Override
public BattleResultDescription getBattleResultDescription() {
return m_battleResultDescription;
}
@Override
public GUID getBattleID() {
return m_battleID;
}
@Override
public final Territory getTerritory() {
return m_battleSite;
}
public final Collection<TerritoryEffect> getTerritoryEffects() {
return m_territoryEffects;
}
@Override
public PlayerID getAttacker() {
return m_attacker;
}
@Override
public PlayerID getDefender() {
return m_defender;
}
public void setHeadless(final boolean aBool) {
m_headless = aBool;
}
@Override
public abstract void fight(IDelegateBridge bridge);
@Override
public abstract Change addAttackChange(final Route route, final Collection<Unit> units,
final HashMap<Unit, HashSet<Unit>> targets);
@Override
public abstract void removeAttack(Route route, Collection<Unit> units);
@Override
public abstract void unitsLostInPrecedingBattle(IBattle battle, Collection<Unit> units, IDelegateBridge bridge,
boolean withdrawn);
@Override
public int hashCode() {
return m_battleSite.hashCode();
}
/**
* 2 Battles are equal if they occur in the same territory,
* and are both of the same type (bombing / not-bombing),
* and are both of the same sub-type of bombing/normal
* (ex: MustFightBattle, or StrategicBombingRaidBattle, or StrategicBombingRaidPreBattle, or NonFightingBattle, etc).
* <br>
* Equals in the sense that they should never occupy the same Set if these conditions are met.
*/
@Override
public boolean equals(final Object o) {
if (o == null || !(o instanceof IBattle)) {
return false;
}
final IBattle other = (IBattle) o;
return other.getTerritory().equals(this.m_battleSite) && other.isBombingRun() == this.isBombingRun()
&& other.getBattleType().equals(this.getBattleType());
}
@Override
public String toString() {
return "Battle in:" + m_battleSite + " battle type:" + m_battleType + " defender:" + m_defender.getName()
+ " attacked by:" + m_attacker.getName() + " attacking with: " + m_attackingUnits;
}
static PlayerID findDefender(final Territory battleSite, final PlayerID attacker, final GameData data) {
if (battleSite == null) {
return PlayerID.NULL_PLAYERID;
}
PlayerID defender = null;
if (!battleSite.isWater()) {
defender = battleSite.getOwner();
}
if (data == null || attacker == null) {
// This is needed for many TESTs, so do not delete
if (defender == null) {
return PlayerID.NULL_PLAYERID;
}
return defender;
}
if (defender == null || battleSite.isWater() || !data.getRelationshipTracker().isAtWar(attacker, defender)) {
// if water find the defender based on who has the most units in the territory
final IntegerMap<PlayerID> players = battleSite.getUnits().getPlayerUnitCounts();
int max = -1;
for (final PlayerID current : players.keySet()) {
if (current.equals(attacker) || !data.getRelationshipTracker().isAtWar(attacker, current)) {
continue;
}
final int count = players.getInt(current);
if (count > max) {
max = count;
defender = current;
}
}
}
if (defender == null) {
return PlayerID.NULL_PLAYERID;
}
return defender;
}
static PlayerID findPlayerWithMostUnits(final Collection<Unit> units) {
final IntegerMap<PlayerID> playerUnitCount = new IntegerMap<>();
for (final Unit unit : units) {
playerUnitCount.add(unit.getOwner(), 1);
}
int max = -1;
PlayerID rVal = null;
for (final PlayerID current : playerUnitCount.keySet()) {
final int count = playerUnitCount.getInt(current);
if (count > max) {
max = count;
rVal = current;
}
}
return rVal;
}
/**
* The maximum number of hits that this collection of units can sustain, taking into account units
* with two hits, and accounting for existing damage.
*/
static int getMaxHits(final Collection<Unit> units) {
int count = 0;
for (final Unit unit : units) {
count += UnitAttachment.get(unit.getUnitType()).getHitPoints();
count -= unit.getHits();
}
return count;
}
void markDamaged(final Collection<Unit> damaged, final IDelegateBridge bridge) {
BattleDelegate.markDamaged(damaged, bridge);
}
protected static ITripleADisplay getDisplay(final IDelegateBridge bridge) {
return (ITripleADisplay) bridge.getDisplayChannelBroadcaster();
}
// TODO: is this called via reflection? If not, can be removed since it is unused.
protected static ISound getSoundChannel(final IDelegateBridge bridge) {
return bridge.getSoundChannelBroadcaster();
}
protected static ITripleAPlayer getRemote(final IDelegateBridge bridge) {
return (ITripleAPlayer) bridge.getRemotePlayer();
}
protected static ITripleAPlayer getRemote(final PlayerID player, final IDelegateBridge bridge) {
// if its the null player, return a do nothing proxy
if (player.isNull()) {
return new WeakAI(player.getName(), TripleA.WEAK_COMPUTER_PLAYER_TYPE);
}
return (ITripleAPlayer) bridge.getRemotePlayer(player);
}
}