freecol/src/net/sf/freecol/common/model/CombatModel.java

309 lines
11 KiB
Java

/**
* Copyright (C) 2002-2022 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* FreeCol is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.common.model;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
/**
* A abstract requirements for a FreeCol combat model.
*/
public abstract class CombatModel {
public enum CombatEffectType {
// Special results that set the sense of the result.
NO_RESULT,
LOSE,
WIN,
// Specific actions
AUTOEQUIP_UNIT, // Defending unit auto-arms
BURN_MISSIONS, // Defending natives burn attackers missions
CAPTURE_AUTOEQUIP, // Winner captures loser auto-equipment
CAPTURE_COLONY, // Winning Europeans capture a colony
CAPTURE_CONVERT, // Winning Europeans cause native to convert
CAPTURE_EQUIP, // Winner captures loser equipment
CAPTURE_UNIT, // Losing unit is captured
DAMAGE_COLONY_SHIPS, // Ships in losing colony are damaged
DAMAGE_SHIP_ATTACK, // Losing ship is damaged by normal attack
DAMAGE_SHIP_BOMBARD, // Losing ship is damaged by bombardment
DEMOTE_UNIT, // Losing unit is demoted
DESTROY_COLONY, // Winning natives burn a colony
DESTROY_SETTLEMENT, // Winner destroys a native settlement
EVADE_ATTACK, // Defending ship evades normal attack
EVADE_BOMBARD, // Defending ship evades bombardment
LOOT_SHIP, // Losing ship is looted
LOSE_AUTOEQUIP, // Losing unit auto-arms and loses the arms
LOSE_EQUIP, // Losing unit loses some equipment
PILLAGE_COLONY, // Winning natives pillage an undefended colony
PROMOTE_UNIT, // Winning unit is promoted
SINK_COLONY_SHIPS, // Ships in losing colony are sunk
SINK_SHIP_ATTACK, // Losing ship is sunk by normal attack
SINK_SHIP_BOMBARD, // Losing ship is sunk by bombardment
SLAUGHTER_UNIT, // Losing unit is slaughtered
}
public static final class CombatResult {
private final List<CombatEffectType> effects;
private final int attackerHitpointsAfter;
private final int defenderHitpointsAfter;
public CombatResult(List<CombatEffectType> effects) {
this.effects = Objects.requireNonNull(effects);
this.attackerHitpointsAfter = -1;
this.defenderHitpointsAfter = -1;
}
public CombatResult(List<CombatEffectType> effects, int attackerHitpointsAfter, int defenderHitpointsAfter) {
this.effects = Objects.requireNonNull(effects);
this.attackerHitpointsAfter = attackerHitpointsAfter;
this.defenderHitpointsAfter = defenderHitpointsAfter;
}
public List<CombatEffectType> getEffects() {
return effects;
}
public boolean isAttackerHitpointsAffected() {
return attackerHitpointsAfter >= 0;
}
public boolean isDefenderHitpointsAffected() {
return defenderHitpointsAfter >= 0;
}
public int getAttackerHitpointsAfter() {
return attackerHitpointsAfter;
}
public int getDefenderHitpointsAfter() {
return defenderHitpointsAfter;
}
}
/**
* Odds a particular outcome will occur in combat.
*/
public static class CombatOdds {
public static final double UNKNOWN_ODDS = -1.0;
public final double win;
public CombatOdds(double win) {
this.win = win;
}
}
/**
* Empty constructor is sufficient.
*/
public CombatModel() {}
/**
* Is this just a measurement of offence power?
*
* @param attacker The attacker.
* @param defender The defender.
* @return True if no defender is provided.
*/
protected final boolean combatIsAttackMeasurement(FreeColGameObject attacker,
FreeColGameObject defender) {
return attacker instanceof Unit && defender == null;
}
/**
* Is this just a measurement of defence power?
*
* @param attacker The attacker.
* @param defender The defender.
* @return True if no attacker is provided.
*/
protected final boolean combatIsDefenceMeasurement(FreeColGameObject attacker,
FreeColGameObject defender) {
return attacker == null && defender instanceof Unit;
}
/**
* Is this combat a normal attack?
*
* @param attacker The attacker.
* @param defender The defender.
* @return True if the combat is a normal attack.
*/
public boolean combatIsAttack(FreeColGameObject attacker,
FreeColGameObject defender) {
return attacker instanceof Unit && defender instanceof Unit;
}
/**
* Is this combat a attack on a settlement? These happen on the client
* side only, for the purposes of the pre-combat display. In these cases
* the actual defender unit is hidden from the attacker player, so
* the defender is shown as the settlement itself.
*
* @param attacker The attacker.
* @param defender The defender.
* @return True if the combat is a client-side attack on a settlement.
*/
public boolean combatIsSettlementAttack(FreeColGameObject attacker,
FreeColGameObject defender) {
return attacker instanceof Unit && defender instanceof Settlement;
}
/**
* Is this combat a bombardment?
*
* @param attacker The attacker.
* @param defender The defender.
* @return True if the combat is a bombardment.
*/
public boolean combatIsBombard(FreeColGameObject attacker,
FreeColGameObject defender) {
return attacker instanceof Settlement && defender instanceof Unit
&& ((Unit)defender).isNaval();
}
/**
* Could this attack be an amphibious operation?
*
* @param attacker The attacker.
* @param defender The defender.
* @return True if the attack is amphibious.
*/
public boolean combatIsAmphibious(FreeColGameObject attacker,
FreeColGameObject defender) {
return attacker instanceof Unit
&& ((Unit)attacker).hasTile()
&& !((Unit)attacker).getTile().isLand()
&& defender instanceof Location
&& ((Location)defender).getTile() != null
&& ((Location)defender).getTile().isLand();
}
/**
* Is this a combat between a rebel player and the REF at a colony?
*
* @param attacker The attacker object.
* @param defender The defender object.
* @return True if the attack is a war of independence battle for a colony.
*/
protected final boolean combatIsWarOfIndependence(FreeColGameObject attacker,
FreeColGameObject defender) {
if (attacker instanceof Unit
&& defender instanceof Ownable) {
Player aPlayer = ((Unit)attacker).getOwner();
Player dPlayer = ((Ownable)defender).getOwner();
return (aPlayer.isRebel() && aPlayer.getREFPlayer() == dPlayer)
|| (dPlayer.isRebel() && dPlayer.getREFPlayer() == aPlayer);
}
return false;
}
/**
* Get a comparator to order units by relative military strength
* with respect to this combat model.
*
* @return A suitable unit {@code Comparator}.
*/
public final Comparator<Unit> getMilitaryStrengthComparator() {
return (u1, u2) -> Double.compare(calculateCombatOdds(u1, u2).win,
calculateCombatOdds(u2, u1).win);
}
/**
* Calculates the chance of the outcomes of a combat.
*
* @param attacker The attacker.
* @param defender The defender.
* @return The {@code CombatOdds}.
*/
public abstract CombatOdds calculateCombatOdds(FreeColGameObject attacker,
FreeColGameObject defender);
/**
* Get the offensive power of a attacker wrt a defender.
*
* Null can be passed for the defender when only the attacker
* stats are required.
*
* @param attacker The attacker.
* @param defender The defender.
* @return The offensive power.
*/
public abstract double getOffencePower(FreeColGameObject attacker,
FreeColGameObject defender);
/**
* Get the defensive power of a defender wrt an attacker.
*
* @param attacker The attacker.
* @param defender The defender.
* @return The defensive power.
*/
public abstract double getDefencePower(FreeColGameObject attacker,
FreeColGameObject defender);
/**
* Collect all the offensive modifiers that apply to an attack.
*
* Null can be passed as the defender when only the attacker unit
* stats are required.
*
* @param attacker The attacker.
* @param defender The defender.
* @return All the applicable offensive modifiers.
*/
public abstract Set<Modifier> getOffensiveModifiers(FreeColGameObject attacker,
FreeColGameObject defender);
/**
* Collect all defensive modifiers that apply to a unit defending
* against another.
*
* @param attacker The attacker.
* @param defender The defender.
* @return All the applicable defensive modifiers.
*/
public abstract Set<Modifier> getDefensiveModifiers(FreeColGameObject attacker,
FreeColGameObject defender);
/**
* Generates a list of results of an attack. The first must be one
* of NO_RESULT, LOSE or WIN. The rest can be any other CombatResult
* suitable to the situation.
* To be called by the server only.
*
* @param random A pseudo-random number source.
* @param attacker The attacker.
* @param defender The defender.
* @return The results of the combat.
*/
public abstract CombatResult generateAttackResult(Random random,
FreeColGameObject attacker,
FreeColGameObject defender);
}