/** * Copyright (C) 2002-2012 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.server.ai; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.logging.Logger; import org.freecolandroid.xml.stream.XMLStreamException; import org.freecolandroid.xml.stream.XMLStreamReader; import net.sf.freecol.common.model.Ability; import net.sf.freecol.common.model.Colony; import net.sf.freecol.common.model.ColonyTradeItem; import net.sf.freecol.common.model.CombatModel; import net.sf.freecol.common.model.DiplomaticTrade; import net.sf.freecol.common.model.EquipmentType; import net.sf.freecol.common.model.FeatureContainer; import net.sf.freecol.common.model.GameOptions; import net.sf.freecol.common.model.GoldTradeItem; import net.sf.freecol.common.model.Goods; import net.sf.freecol.common.model.GoodsTradeItem; import net.sf.freecol.common.model.IndianSettlement; import net.sf.freecol.common.model.Location; import net.sf.freecol.common.model.Modifier; import net.sf.freecol.common.model.PathNode; import net.sf.freecol.common.model.Player; import net.sf.freecol.common.model.Player.Stance; import net.sf.freecol.common.model.Settlement; import net.sf.freecol.common.model.Specification; import net.sf.freecol.common.model.StanceTradeItem; import net.sf.freecol.common.model.Tension; import net.sf.freecol.common.model.Tile; import net.sf.freecol.common.model.TradeItem; import net.sf.freecol.common.model.Unit; import net.sf.freecol.common.model.Unit.Role; import net.sf.freecol.common.model.Unit.UnitState; import net.sf.freecol.common.model.UnitTradeItem; import net.sf.freecol.common.model.UnitType; import net.sf.freecol.common.networking.NetworkConstants; import net.sf.freecol.common.util.Utils; import net.sf.freecol.server.ai.mission.IdleAtColonyMission; import net.sf.freecol.server.ai.mission.IndianBringGiftMission; import net.sf.freecol.server.ai.mission.IndianDemandMission; import net.sf.freecol.server.ai.mission.Mission; import net.sf.freecol.server.ai.mission.PioneeringMission; import net.sf.freecol.server.ai.mission.UnitSeekAndDestroyMission; import net.sf.freecol.server.ai.mission.UnitWanderHostileMission; import net.sf.freecol.server.model.ServerPlayer; import org.w3c.dom.Element; /** * Objects of this class contains AI-information for a single {@link * Player} and is used for controlling this player. * * The method {@link #startWorking} gets called by the * {@link AIInGameInputHandler} when it is this player's turn. */ public class NativeAIPlayer extends AIPlayer { private static final Logger logger = Logger.getLogger(NativeAIPlayer.class.getName()); /** * The modifier to apply when a ship is trading. */ private static final String SHIP_TRADE_PENALTY = "model.modifier.shipTradePenalty"; /** * The modifier to apply when to trade with a settlement with a missionary * if the enhancedMissionaries option is enabled. */ private static final String MISSIONARY_TRADE_BONUS = "model.modifier.missionaryTradeBonus"; /** * Stores temporary information for sessions (trading with another player * etc). */ private HashMap<String, Integer> sessionRegister = new HashMap<String, Integer>(); /** * Creates a new <code>AIPlayer</code>. * * @param aiMain The main AI-class. * @param player The player that should be associated with this * <code>AIPlayer</code>. */ public NativeAIPlayer(AIMain aiMain, ServerPlayer player) { super(aiMain, player.getId()); setPlayer(player); } /** * Creates a new <code>AIPlayer</code> and reads the information from the * given <code>Element</code>. * * @param aiMain The main AI-class. * @param element The XML-element containing information. */ public NativeAIPlayer(AIMain aiMain, Element element) { super(aiMain, element.getAttribute(ID_ATTRIBUTE)); readFromXMLElement(element); } /** * Creates a new <code>AIPlayer</code>. * * @param aiMain The main AI-object. * @param in The input stream containing the XML. * @throws XMLStreamException if a problem was encountered during parsing. */ public NativeAIPlayer(AIMain aiMain, XMLStreamReader in) throws XMLStreamException { super(aiMain, in.getAttributeValue(null, ID_ATTRIBUTE)); readFromXML(in); } /* IMPLEMENTATION (AIPlayer interface) ****************************************/ /** * Tells this <code>AIPlayer</code> to make decisions. The * <code>AIPlayer</code> is done doing work this turn when this method * returns. */ public void startWorking() { final Player player = getPlayer(); logger.finest("Entering method startWorking: " + player + ", year " + getGame().getTurn()); sessionRegister.clear(); clearAIUnits(); determineStances(); abortInvalidAndOneTimeMissions(); secureSettlements(); giveNormalMissions(); bringGifts(); demandTribute(); doMissions(); abortInvalidMissions(); // Some of the mission might have been invalidated by a another mission. giveNormalMissions(); doMissions(); abortInvalidMissions(); clearAIUnits(); } public boolean acceptDiplomaticTrade(DiplomaticTrade agreement) { boolean validOffer = true; Stance stance = null; int value = 0; Iterator<TradeItem> itemIterator = agreement.iterator(); while (itemIterator.hasNext()) { TradeItem item = itemIterator.next(); if (item instanceof GoldTradeItem) { int gold = ((GoldTradeItem) item).getGold(); if (item.getSource() == getPlayer()) { value -= gold; } else { value += gold; } } else if (item instanceof StanceTradeItem) { // TODO: evaluate whether we want this stance change stance = ((StanceTradeItem) item).getStance(); switch (stance) { case UNCONTACTED: validOffer = false; //never accept invalid stance change break; case WAR: // always accept war without cost break; case CEASE_FIRE: value -= 500; break; case PEACE: if (!agreement.getSender().hasAbility("model.ability.alwaysOfferedPeace")) { // TODO: introduce some kind of counter in order to avoid // Benjamin Franklin exploit value -= 1000; } break; case ALLIANCE: value -= 2000; break; } } else if (item instanceof ColonyTradeItem) { // TODO: evaluate whether we might wish to give up a colony if (item.getSource() == getPlayer()) { validOffer = false; break; } else { value += 1000; } } else if (item instanceof UnitTradeItem) { // TODO: evaluate whether we might wish to give up a unit if (item.getSource() == getPlayer()) { validOffer = false; break; } else { value += 100; } } else if (item instanceof GoodsTradeItem) { Goods goods = ((GoodsTradeItem) item).getGoods(); if (item.getSource() == getPlayer()) { value -= getPlayer().getMarket().getBidPrice(goods.getType(), goods.getAmount()); } else { value += getPlayer().getMarket().getSalePrice(goods.getType(), goods.getAmount()); } } } if (validOffer) { logger.info("Trade value is " + value + ", accept if >=0"); } else { logger.info("Trade offer is considered invalid!"); } return (value>=0)&&validOffer; } /** * Called after another <code>Player</code> sends a * <code>trade</code> message. * * @param goods The goods which we are going to offer */ public void registerSellGoods(Goods goods) { String goldKey = "tradeGold#" + goods.getType().getId() + "#" + goods.getAmount() + "#" + goods.getLocation().getId(); sessionRegister.put(goldKey, null); } /** * Gets the appropriate missionary trade bonuses. * * @param missionary The missionary <code>Unit</code>. * @param sense The sense to apply the modifiers. * @return The missionary trade bonuses. */ private Set<Modifier> getMissionaryTradeBonuses(Unit missionary, boolean sense) { Set<Modifier> missionaryBonuses = missionary .getModifierSet(MISSIONARY_TRADE_BONUS); Set<Modifier> result; if (sense) { result = missionaryBonuses; } else { result = new HashSet<Modifier>(); for (Modifier m : missionaryBonuses) { result.add(new Modifier(m.getId(), m.getSource(), -m.getValue(), m.getType())); } } return result; } /** * Gets the appropriate ship trade penalties. * * @param sense The sense to apply the modifiers. * @return The ship trade penalties. */ private Set<Modifier> getShipTradePenalties(boolean sense) { Specification spec = getSpecification(); List<Modifier> shipPenalties = spec.getModifiers(SHIP_TRADE_PENALTY); Set<Modifier> result = new HashSet<Modifier>(); int penalty = spec.getInteger(GameOptions.SHIP_TRADE_PENALTY); for (Modifier m : shipPenalties) { result.add(new Modifier(m.getId(), m.getSource(), ((sense) ? penalty : -penalty), m.getType())); } return result; } /** * Called when another <code>Player</code> proposes to buy. * * * @param unit The foreign <code>Unit</code> trying to trade. * @param settlement The <code>Settlement</code> this player owns and * which the given <code>Unit</code> is trading. * @param goods The goods the given <code>Unit</code> is trying to sell. * @param gold The suggested price. * @return The price this <code>AIPlayer</code> suggests or * {@link NetworkConstants#NO_TRADE}. */ public int buyProposition(Unit unit, Settlement settlement, Goods goods, int gold) { logger.finest("Entering method buyProposition"); Specification spec = getSpecification(); IndianSettlement is = (IndianSettlement) settlement; Player buyer = unit.getOwner(); String goldKey = "tradeGold#" + goods.getType().getId() + "#" + goods.getAmount() + "#" + settlement.getId(); String hagglingKey = "tradeHaggling#" + unit.getId(); int price; Integer registered = sessionRegister.get(goldKey); if (registered == null) { price = is.getPriceToSell(goods); switch (is.getAlarm(buyer).getLevel()) { case HAPPY: case CONTENT: break; case DISPLEASED: price *= 2; break; default: return NetworkConstants.NO_TRADE_HOSTILE; } Set<Modifier> modifiers = new HashSet<Modifier>(); Unit missionary = is.getMissionary(buyer); if (missionary != null && spec.getBoolean(GameOptions.ENHANCED_MISSIONARIES)) { modifiers.addAll(getMissionaryTradeBonuses(missionary, false)); } if (unit.isNaval()) { modifiers.addAll(getShipTradePenalties(false)); } price = (int) FeatureContainer.applyModifierSet((float)price, getGame().getTurn(), modifiers); sessionRegister.put(goldKey, new Integer(price)); return price; } price = registered.intValue(); if (price < 0 || price == gold) return price; if (gold < (price * 9) / 10) { logger.warning("Cheating attempt: sending a offer too low"); sessionRegister.put(goldKey, new Integer(-1)); return NetworkConstants.NO_TRADE; } int haggling = 1; if (sessionRegister.containsKey(hagglingKey)) { haggling = sessionRegister.get(hagglingKey).intValue(); } if (Utils.randomInt(logger, "Haggle-buy", getAIRandom(), 3 + haggling) >= 3) { sessionRegister.put(goldKey, new Integer(-1)); return NetworkConstants.NO_TRADE_HAGGLE; } sessionRegister.put(goldKey, new Integer(gold)); sessionRegister.put(hagglingKey, new Integer(haggling + 1)); return gold; } /** * Called when another <code>Player</code> proposes a sale. * * @param unit The foreign <code>Unit</code> trying to trade. * @param settlement The <code>Settlement</code> this player owns and * which the given <code>Unit</code> if trying to sell goods. * @param goods The goods the given <code>Unit</code> is trying to sell. * @param gold The suggested price. * @return The price this <code>AIPlayer</code> suggests or * {@link NetworkConstants#NO_TRADE*}. */ public int sellProposition(Unit unit, Settlement settlement, Goods goods, int gold) { logger.finest("Entering method sellProposition"); Specification spec = getSpecification(); IndianSettlement is = (IndianSettlement) settlement; Player seller = unit.getOwner(); String goldKey = "tradeGold#" + goods.getType().getId() + "#" + goods.getAmount() + "#" + settlement.getId(); String hagglingKey = "tradeHaggling#" + unit.getId(); int price; if (sessionRegister.containsKey(goldKey)) { price = sessionRegister.get(goldKey).intValue(); } else { price = is.getPriceToBuy(goods); switch (is.getAlarm(seller).getLevel()) { case HAPPY: case CONTENT: break; case DISPLEASED: price /= 2; break; case ANGRY: if (!goods.getType().isMilitaryGoods()) return NetworkConstants.NO_TRADE_HOSTILE; price /= 2; break; default: return NetworkConstants.NO_TRADE_HOSTILE; } Set<Modifier> modifiers = new HashSet<Modifier>(); Unit missionary = is.getMissionary(seller); if (missionary != null && spec.getBoolean(GameOptions.ENHANCED_MISSIONARIES)) { modifiers.addAll(getMissionaryTradeBonuses(missionary, true)); } if (unit.isNaval()) { modifiers.addAll(getShipTradePenalties(true)); } price = (int) FeatureContainer.applyModifierSet((float)price, getGame().getTurn(), modifiers); if (price <= 0) return 0; sessionRegister.put(goldKey, new Integer(price)); } if (gold < 0 || price == gold) return price; if (gold > (price * 11) / 10) { logger.warning("Cheating attempt: haggling with a request too high"); sessionRegister.put(goldKey, new Integer(-1)); return NetworkConstants.NO_TRADE; } int haggling = 1; if (sessionRegister.containsKey(hagglingKey)) { haggling = sessionRegister.get(hagglingKey).intValue(); } if (Utils.randomInt(logger, "Haggle-sell", getAIRandom(), 3 + haggling) >= 3) { sessionRegister.put(goldKey, new Integer(-1)); return NetworkConstants.NO_TRADE_HAGGLE; } sessionRegister.put(goldKey, new Integer(gold)); sessionRegister.put(hagglingKey, new Integer(haggling + 1)); return gold; } /* Internal methods ***********************************************************/ /** * Takes the necessary actions to secure the settlements. This is done by * making new military units or to give existing units new missions. */ private void secureSettlements() { List<IndianSettlement> settlements = getPlayer().getIndianSettlements(); for (IndianSettlement is : settlements) { // Spread arms and horses between camps // TODO: maybe make this dependent on difficulty level? int n = Utils.randomInt(logger, "Secure", getAIRandom(), settlements.size()); IndianSettlement settlement = settlements.get(n); if (settlement != is) { is.tradeGoodsWithSetlement(settlement); } } for (IndianSettlement is : settlements) { equipBraves(is); secureIndianSettlement(is); } } /** * Greedily equips braves with horses and muskets. * * @param is The <code>IndianSettlement</code> where the equipping occurs. */ public void equipBraves(IndianSettlement is) { final Specification spec = getSpecification(); List<Unit> units = is.getUnitList(); roles: for (Role r : new Role[] { Role.DRAGOON, Role.SOLDIER, Role.SCOUT }) { List<EquipmentType> e = r.getRoleEquipment(spec); while (!units.isEmpty()) { Unit u = units.get(0); for (EquipmentType et : e) { if (u.canBeEquippedWith(et) && !is.canProvideEquipment(et)) { continue roles; } } if (u.getRole() != Role.DRAGOON && u.getRole() != r) { getAIUnit(u).equipForRole(r, false); } units.remove(0); } } } /** * Takes the necessary actions to secure an indian settlement * * TODO: Package for access by a test only - necessary? */ void secureIndianSettlement(IndianSettlement is) { // if not at war, no need to secure settlement if (!is.getOwner().isAtWar()) { return; } int defenders = is.getTile().getUnitCount(); int threat = 0; int worstThreat = 0; Location bestTarget = null; for (Tile t: is.getTile().getSurroundingTiles(2)) { // Do not check ocean tiles // Indians do not have naval power if(!t.isLand()){ continue; } // No units on tile if (t.getUnitCount() == 0) { continue; } Player enemy = t.getFirstUnit().getOwner(); // Own units on tile if (enemy == getPlayer()) { defenders++; continue; } if (!getPlayer().hasContacted(enemy)) continue; int value = getPlayer().getTension(enemy).getValue(); int threatModifier = 0; int unitThreat = 0; if (value >= Tension.TENSION_ADD_MAJOR) { threatModifier = 2; unitThreat = t.getUnitCount() * 2; } else if (value >= Tension.TENSION_ADD_MINOR) { threatModifier = 1; unitThreat = t.getUnitCount(); } threat += threatModifier; if (unitThreat > worstThreat) { if (t.getSettlement() != null) { bestTarget = t.getSettlement(); } else { bestTarget = t.getFirstUnit(); } worstThreat = unitThreat; } } //Note: this is totally arbitrary if (threat > defenders && bestTarget != null) { AIUnit newDefenderAI = getBraveForSeekAndDestroy(is); if (newDefenderAI != null) { Tile targetTile = bestTarget.getTile(); if (isTargetValidForSeekAndDestroy(newDefenderAI.getUnit(), targetTile)) { newDefenderAI.setMission(new UnitSeekAndDestroyMission(getAIMain(), newDefenderAI, bestTarget)); } } } } private AIUnit getBraveForSeekAndDestroy(final IndianSettlement indianSettlement) { final Iterator<Unit> it = indianSettlement.getOwnedUnitsIterator(); while (it.hasNext()) { final AIUnit chosenOne = getAIUnit(it.next()); if (chosenOne.getUnit().getLocation() instanceof Tile && (chosenOne.getMission() == null || chosenOne.getMission() instanceof UnitWanderHostileMission)) { return chosenOne; } } return null; } /** * Gives a mission to non-naval units. */ private void giveNormalMissions() { logger.finest("Entering method giveNormalMissions"); // Create a datastructure for the worker wishes: java.util.Map<UnitType, ArrayList<Wish>> workerWishes = new HashMap<UnitType, ArrayList<Wish>>(); for (UnitType unitType : getSpecification().getUnitTypeList()) { workerWishes.put(unitType, new ArrayList<Wish>()); } Iterator<AIUnit> aiUnitsIterator = getAIUnitIterator(); while (aiUnitsIterator.hasNext()) { AIUnit aiUnit = aiUnitsIterator.next(); if (aiUnit.hasMission()) { continue; } Unit unit = aiUnit.getUnit(); if (unit.isUninitialized()) { logger.warning("Trying to assign a mission to an uninitialized object: " + unit.getId()); continue; } if (unit.getState() == UnitState.IN_COLONY && unit.getSettlement().getUnitCount() <= 1) { // The unit has its hand full keeping the colony alive. continue; } if (unit.isOffensiveUnit() || unit.isDefensiveUnit()){ Player owner = unit.getOwner(); boolean isPastStart = getGame().getTurn().getNumber() > 5 && !owner.getSettlements().isEmpty(); if(!unit.isColonist() || isPastStart || owner.isIndian() || owner.isREF()){ giveMilitaryMission(aiUnit); continue; } } // Setup as a pioneer if unit is: // - already with tools, or // - an expert pioneer, or // - a non-expert unit and there are no other units assigned as pioneers boolean isPioneer = unit.hasAbility("model.ability.improveTerrain") || unit.hasAbility(Ability.EXPERT_PIONEER); boolean isExpert = unit.getSkillLevel() > 0; if (isPioneer && PioneeringMission.isValid(aiUnit)) { aiUnit.setMission(new PioneeringMission(getAIMain(), aiUnit)); continue; } if (!aiUnit.hasMission()) { if (aiUnit.getUnit().isOffensiveUnit()) { aiUnit.setMission(new UnitWanderHostileMission(getAIMain(), aiUnit)); } else { //non-offensive units should take shelter in a nearby colony, //not try to be hostile aiUnit.setMission(new IdleAtColonyMission(getAIMain(), aiUnit)); } } } } /** * Brings gifts to nice players with nearby colonies. */ private void bringGifts() { logger.finest("Entering method bringGifts"); for (IndianSettlement indianSettlement : getPlayer().getIndianSettlements()) { // Do not bring gifts all the time: if (Utils.randomInt(logger, "BringGifts", getAIRandom(), 10) != 1) { continue; } int alreadyAssignedUnits = 0; Iterator<Unit> ownedUnits = indianSettlement.getOwnedUnitsIterator(); while (ownedUnits.hasNext()) { if (getAIUnit(ownedUnits.next()).getMission() instanceof IndianBringGiftMission) { alreadyAssignedUnits++; } } if (alreadyAssignedUnits > MAX_NUMBER_OF_GIFTS_BEING_DELIVERED) { continue; } // Creates a list of nearby colonies: ArrayList<Colony> nearbyColonies = new ArrayList<Colony>(); for (Tile t: indianSettlement.getTile().getSurroundingTiles(MAX_DISTANCE_TO_BRING_GIFT)) { if (t.getColony() != null && IndianBringGiftMission.isValidMission(getPlayer(), t.getColony().getOwner())) { nearbyColonies.add(t.getColony()); } } if (nearbyColonies.size() > 0) { Colony target = nearbyColonies.get(Utils.randomInt(logger, "Choose colony", getAIRandom(), nearbyColonies.size())); Iterator<Unit> it2 = indianSettlement.getOwnedUnitsIterator(); AIUnit chosenOne = null; while (it2.hasNext()) { chosenOne = getAIUnit(it2.next()); if (chosenOne.getUnit().getLocation() instanceof Tile && chosenOne.getUnit().canCarryGoods() && (chosenOne.getMission() == null || chosenOne.getMission() instanceof UnitWanderHostileMission)) { // Check that the colony can be reached: PathNode pn = chosenOne.getUnit().findPath(indianSettlement.getTile(), target.getTile()); if (pn != null && pn.getTotalTurns() <= MAX_DISTANCE_TO_BRING_GIFT) { chosenOne.setMission(new IndianBringGiftMission(getAIMain(), chosenOne, target)); break; } } } } } } /** * Demands goods from players with nearby colonies. */ private void demandTribute() { logger.finest("Entering method demandTribute"); for (IndianSettlement indianSettlement : getPlayer().getIndianSettlements()) { // Do not demand goods all the time: if (Utils.randomInt(logger, "DemandTribute", getAIRandom(), 10) != 1) { continue; } int alreadyAssignedUnits = 0; Iterator<Unit> ownedUnits = indianSettlement.getOwnedUnitsIterator(); while (ownedUnits.hasNext()) { if (getAIUnit(ownedUnits.next()).getMission() instanceof IndianDemandMission) { alreadyAssignedUnits++; } } if (alreadyAssignedUnits > MAX_NUMBER_OF_DEMANDS) { continue; } // Creates a list of nearby colonies: ArrayList<Colony> nearbyColonies = new ArrayList<Colony>(); for (Tile t: indianSettlement.getTile().getSurroundingTiles(MAX_DISTANCE_TO_MAKE_DEMANDS)) { if (t.getColony() != null) { nearbyColonies.add(t. getColony()); } } if (nearbyColonies.size() > 0) { int targetTension = Integer.MIN_VALUE; Colony target = null; for (int i = 0; i < nearbyColonies.size(); i++) { Colony t = nearbyColonies.get(i); Player to = t.getOwner(); if (!getPlayer().hasContacted(to) || !indianSettlement.hasContactedSettlement(to)) { continue; } int tension = 1 + getPlayer().getTension(to).getValue() + indianSettlement.getAlarm(to).getValue(); tension = Utils.randomInt(logger, "Tension", getAIRandom(), tension); if (tension > targetTension) { targetTension = tension; target = t; } } if (target != null) { Iterator<Unit> it2 = indianSettlement.getOwnedUnitsIterator(); AIUnit chosenOne = null; while (it2.hasNext()) { chosenOne = getAIUnit(it2.next()); if (chosenOne.getUnit().getLocation() instanceof Tile && chosenOne.getUnit().canCarryGoods() && (chosenOne.getMission() == null || chosenOne.getMission() instanceof UnitWanderHostileMission)) { // Check that the colony can be reached: PathNode pn = chosenOne.getUnit().findPath(indianSettlement.getTile(), target.getTile()); if (pn != null && pn.getTotalTurns() <= MAX_DISTANCE_TO_MAKE_DEMANDS) { // Make it less probable that nice players get targeted // for a demand mission: Player tp = target.getOwner(); int tension = 1 + getPlayer().getTension(tp).getValue() + indianSettlement.getAlarm(tp).getValue(); if (Utils.randomInt(logger, "Unhappy?", getAIRandom(), tension) > Tension.Level.HAPPY.getLimit()) { chosenOne.setMission(new IndianDemandMission(getAIMain(), chosenOne, target)); break; } } } } } } } } /** * Evaluate a potential seek and destroy mission for a given unit * to a given tile. * TODO: revisit and rebalance the mass of magic numbers. * * @param unit The <code>Unit</code> to do the mission. * @param newTile The <code>Tile</code> to go to. * @param turns How long to travel to the tile. * @return A score for the proposed mission. */ int getUnitSeekAndDestroyMissionValue(Unit unit, Tile newTile, int turns) { if (!isTargetValidForSeekAndDestroy(unit, newTile)) { return Integer.MIN_VALUE; } Settlement settlement = newTile.getSettlement(); Unit defender = newTile.getDefendingUnit(unit); // Take distance to target into account int value = 10020 - turns * 100; if (settlement != null) { // Do not cheat and look inside the settlement. // Just use visible facts about it. // TODO: if we are the REF and there is a significant Tory // population inside, assume traitors have briefed us. if (settlement instanceof Colony) { // Favour high population and weak fortifications. Colony colony = (Colony) settlement; value += 50 * colony.getUnitCount(); if (colony.hasStockade()) { value -= 1000 * colony.getStockade().getLevel(); } } else if (settlement instanceof IndianSettlement) { // Favour the most hostile settlements IndianSettlement is = (IndianSettlement) settlement; Tension tension = is.getAlarm(unit.getOwner()); if (tension != null) value += tension.getValue(); } } else if (defender != null) { CombatModel combatModel = unit.getGame().getCombatModel(); float off = combatModel.getOffencePower(unit, defender); float def = combatModel.getDefencePower(unit, defender); if (defender.getType().getOffence() > 0) { value += 200 - def * 2 - turns * 50; } value += combatModel.getOffencePower(defender, unit) - combatModel.getDefencePower(defender, unit); if (!defender.isNaval() && defender.hasAbility(Ability.EXPERT_SOLDIER) && !defender.isArmed()) { value += 10 - def * 2 - turns * 25; } if (value < 0) value = 0; } logger.finest("getUnitSeekAndDestroyMissionValue " + unit.getId() + " v " + ((settlement != null) ? settlement.getId() : (defender != null) ? defender.getId() : "none") + " = " + value); return value; } /** * Gives a military mission to the given unit. * * Old comment: Temporary method for giving a military mission. * This method will be removed when "MilitaryStrategy" and the * "Tactic"-classes has been implemented. * * @param aiUnit The <code>AIUnit</code> to give a mission to. */ public void giveMilitaryMission(AIUnit aiUnit) { logger.finest("Entering giveMilitaryMission for: " + aiUnit.getUnit()); final AIMain aiMain = getAIMain(); Mission mission = new UnitWanderHostileMission(aiMain, aiUnit); aiUnit.setMission(mission); logger.finest("giveMilitaryMission found: " + mission + " for unit: " + aiUnit); } }