/**
* 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.Collections;
import java.util.Comparator;
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 javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
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.Map;
import net.sf.freecol.common.model.Modifier;
import net.sf.freecol.common.model.Ownable;
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.Turn;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.Unit.Role;
import net.sf.freecol.common.model.UnitTradeItem;
import net.sf.freecol.common.model.pathfinding.CostDeciders;
import net.sf.freecol.common.networking.NetworkConstants;
import net.sf.freecol.common.util.Utils;
import net.sf.freecol.server.ai.mission.DefendSettlementMission;
import net.sf.freecol.server.ai.mission.IdleAtSettlementMission;
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.UnitSeekAndDestroyMission;
import net.sf.freecol.server.ai.mission.UnitWanderHostileMission;
import net.sf.freecol.server.model.ServerPlayer;
/**
* 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";
public static final int MAX_DISTANCE_TO_BRING_GIFTS = 5;
public static final int MAX_NUMBER_OF_GIFTS_BEING_DELIVERED = 1;
public static final int MAX_DISTANCE_TO_MAKE_DEMANDS = 5;
public static final int MAX_NUMBER_OF_DEMANDS = 1;
/**
* A settlement with a surplus chooses to send a gift GIFT_PERCENT
* of the time.
*/
public static final int GIFT_PERCENT = 5;
/**
* 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);
uninitialized = getPlayer() == null;
}
/**
* 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);
uninitialized = getPlayer() == null;
}
/**
* 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() {
Turn turn = getGame().getTurn();
logger.finest(getClass().getName() + " in " + turn
+ ": " + getPlayer().getNationID());
sessionRegister.clear();
clearAIUnits();
determineStances();
if (turn.isFirstTurn()) {
initializeMissions();
} else {
abortInvalidAndOneTimeMissions();
secureSettlements();
bringGifts();
demandTribute();
giveNormalMissions();
doMissions();
abortInvalidMissions();
giveNormalMissions();
}
doMissions();
abortInvalidMissions();
clearAIUnits();
}
/**
* Simple initialization of AI missions given that we know the starting
* conditions.
*/
private void initializeMissions() {
AIMain aiMain = getAIMain();
Player player = getPlayer();
// Give defensive missions up to the minimum expected defence,
// leave the rest with the default wander-hostile mission.
List<Unit> units = new ArrayList<Unit>();
for (IndianSettlement is : player.getIndianSettlements()) {
int defence = is.getType().getMinimumSize() - 1;
units.clear();
units.addAll(is.getTile().getUnitList());
units.addAll(is.getUnitList());
while (units.size() > defence) units.remove(0);
for (Unit u : units) {
AIUnit aiu = getAIUnit(u);
aiu.setMission(new DefendSettlementMission(aiMain, aiu, is));
}
}
}
/**
* 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.tradeGoodsWithSettlement(settlement);
}
}
for (IndianSettlement is : settlements) {
equipBraves(is);
secureIndianSettlement(is);
}
}
/**
* Greedily equips braves with horses and muskets.
* Public for the test suite.
*
* @param is The <code>IndianSettlement</code> where the equipping occurs.
*/
public void equipBraves(IndianSettlement is) {
final Specification spec = getSpecification();
List<Unit> units = is.getUnitList();
units.addAll(is.getTile().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
* Public for the test suite.
*
* @param is The <code>IndianSettlement</code> to secure.
*/
public void secureIndianSettlement(final IndianSettlement is) {
final AIMain aiMain = getAIMain();
final Player player = getPlayer();
final CombatModel cm = getGame().getCombatModel();
final int minimumDefence = is.getType().getMinimumSize() - 1;
// Collect native units and defenders
List<Unit> units = new ArrayList<Unit>();
List<Unit> defenders = new ArrayList<Unit>();
units.addAll(is.getUnitList());
units.addAll(is.getTile().getUnitList());
for (Unit u : is.getOwnedUnits()) {
if (!units.contains(u)) units.add(u);
}
// Collect the current defenders
String logMe = "Defending settlement " + is.getName() + " with:";
for (Unit u : new ArrayList<Unit>(units)) {
AIUnit aiu = aiMain.getAIUnit(u);
if (aiu == null) {
units.remove(u);
} else if (aiu.getMission() instanceof DefendSettlementMission
&& (((DefendSettlementMission)aiu.getMission())
.getTarget() == is)) {
logMe += " " + u.getId();
defenders.add(u);
units.remove(u);
} else if (Mission.invalidNewMissionReason(aiu) != null) {
units.remove(u);
}
}
// Collect threats and other potential defenders
final HashMap<Tile, Float> threats = new HashMap<Tile, Float>();
Player enemy;
Tension tension;
for (Tile t : is.getTile().getSurroundingTiles(2)) {
if (!t.isLand() || t.getUnitCount() == 0) {
; // Do nothing
} else if ((enemy = t.getFirstUnit().getOwner()) == player) {
// Its one of ours!
for (Unit u : t.getUnitList()) {
AIUnit aiu;
if (defenders.contains(u) || units.contains(u)
|| (aiu = aiMain.getAIUnit(u)) == null) {
; // Do nothing
} else if (aiu.getMission() instanceof DefendSettlementMission
&& ((DefendSettlementMission)aiu.getMission())
.getTarget() == is) {
logMe += " " + u.getId();
defenders.add(u);
} else if (Mission.invalidNewMissionReason(aiu) == null) {
units.add(u);
}
}
} else if ((tension = is.getAlarm(enemy)) == null
|| tension.getLevel().compareTo(Tension.Level.CONTENT) <= 0) {
; // Not regarded as a threat
} else {
// Evaluate the threat
float threshold, bonus, value = 0.0f;
if (tension.getLevel().compareTo(Tension.Level.DISPLEASED) <= 0) {
threshold = 1.0f;
bonus = 0.0f;
} else {
threshold = 0.0f;
bonus = (float)tension.getLevel().ordinal()
- Tension.Level.CONTENT.ordinal();
}
for (Unit u : t.getUnitList()) {
float offence = cm.getOffencePower(u, is);
if (offence > threshold) value += offence + bonus;
}
if (value > 0.0f) threats.put(t, new Float(value));
}
}
// Sort the available units by proximity to the settlement.
// Simulates favouring the first warriors found by outgoing messengers.
// Also favour units native to the settlement.
final int homeBonus = 3;
Collections.sort(units, new Comparator<Unit>() {
public int compare(Unit u1, Unit u2) {
int s1 = u1.getTile().getDistanceTo(is.getTile());
int s2 = u2.getTile().getDistanceTo(is.getTile());
if (u1.getIndianSettlement() == is) s1 -= homeBonus;
if (u2.getIndianSettlement() == is) s2 -= homeBonus;
return s1 - s2;
}
});
// Do we need more defenders? If so, call some in.
if (defenders.size() < minimumDefence + threats.size()) {
int needed = minimumDefence + threats.size();
logger.finest(is.getName() + " has " + defenders.size()
+ " initial defenders but needs " + minimumDefence + "(base)"
+ " + " + threats.size() + "(threats)" + " = " + needed);
while (!units.isEmpty()) {
Unit u = units.remove(0);
AIUnit aiu = aiMain.getAIUnit(u);
aiu.setMission(new DefendSettlementMission(aiMain, aiu, is));
logMe += " [" + u.getId() + "]";
defenders.add(u);
if (defenders.size() >= needed) break;
}
}
logger.finest(logMe);
if (units.isEmpty()) return;
// Sort threat tiles by threat value.
List<Tile> threatTiles = new ArrayList<Tile>(threats.keySet());
Collections.sort(threatTiles, new Comparator<Tile>() {
public int compare(Tile t1, Tile t2) {
return Float.compare(threats.get(t2).floatValue(),
threats.get(t1).floatValue());
}
});
// Assign units to attack the threats, greedily chosing closest unit.
while (!threatTiles.isEmpty() && !units.isEmpty()) {
Tile tile = threatTiles.remove(0);
int bestDistance = Integer.MAX_VALUE;
Unit unit = null;
for (Unit u : units) {
AIUnit aiu = aiMain.getAIUnit(u);
if (UnitSeekAndDestroyMission.invalidReason(aiu,
tile.getDefendingUnit(u)) != null) continue;
int distance = u.getTile().getDistanceTo(tile);
if (bestDistance > distance) {
bestDistance = distance;
unit = u;
}
}
if (unit == null) continue; // Declined to attack.
units.remove(unit);
AIUnit aiUnit = aiMain.getAIUnit(unit);
Unit target = tile.getDefendingUnit(unit);
logger.finest(is.getName() + " sends unit to attack " + target
+ " at " + tile + ": " + aiUnit);
aiUnit.setMission(new UnitSeekAndDestroyMission(aiMain, aiUnit,
target));
}
}
/**
* Gives a mission to all units.
*/
private void giveNormalMissions() {
final AIMain aiMain = getAIMain();
final Specification spec = aiMain.getGame().getSpecification();
final int turnNumber = getGame().getTurn().getNumber();
final List<EquipmentType> scoutEq
= Unit.Role.SCOUT.getRoleEquipment(spec);
final List<EquipmentType> soldierEq
= Unit.Role.SOLDIER.getRoleEquipment(spec);
List<AIUnit> aiUnits = getAIUnits();
String report = "";
int allUnits = aiUnits.size(), i = 0;
while (i < aiUnits.size()) {
final AIUnit aiUnit = aiUnits.get(i);
final Unit unit = aiUnit.getUnit();
Mission m = aiUnit.getMission();
String reason = null;
if (unit.isUninitialized() || unit.isDisposed()) {
reason = "Invalid-" + aiUnit.toString();
} else if (m != null && m.isValid() && !m.isOneTime()) {
reason = "Valid-" + m.toString();
}
if (reason == null) {
i++;
} else {
report += "\n " + reason;
aiUnits.remove(i);
}
}
report = Utils.lastPart(getPlayer().getNationID(), ".")
+ ".giveNormalMissions(turn=" + turnNumber
+ " all-units=" + allUnits + " free-land-units=" + aiUnits.size()
+ ")" + report;
i = 0;
while (i < aiUnits.size()) {
final AIUnit aiUnit = aiUnits.get(i);
final Unit unit = aiUnit.getUnit();
Mission m;
Settlement settlement = unit.getSettlement();
IndianSettlement is = unit.getIndianSettlement();
// First see to local settlement defence
if ((settlement != null && (settlement.getUnitCount()
+ unit.getTile().getUnitCount() <= 1)
&& (m = new DefendSettlementMission(aiMain, aiUnit,
settlement)) != null)
// Go home for new equipment if the home settlement has them.
|| (is != null
&& ((!unit.isMounted() && is.canProvideEquipment(scoutEq))
|| (!unit.isArmed() && is.canProvideEquipment(soldierEq)))
&& (m = new DefendSettlementMission(aiMain, aiUnit, is))
!= null)
// Go out looking for trouble
|| (UnitWanderHostileMission.invalidReason(aiUnit) == null
&& (m = new UnitWanderHostileMission(aiMain, aiUnit))
!= null)
) {
aiUnit.setMission(m);
report += "\n New-" + m.toString();
aiUnits.remove(i);
} else {
i++;
}
}
for (AIUnit aiUnit : aiUnits) {
Mission m;
if (aiUnit.getMission() instanceof IdleAtSettlementMission) {
m = aiUnit.getMission();
} else {
m = new IdleAtSettlementMission(aiMain, aiUnit);
aiUnit.setMission(m);
}
report += "\n UNUSED-" + m
+ " at " + aiUnit.getUnit().getLocation();
}
logger.fine(report);
}
/**
* Brings gifts to nice players with nearby colonies.
*/
private void bringGifts() {
final Player player = getPlayer();
final Map map = getGame().getMap();
for (IndianSettlement is : player.getIndianSettlements()) {
// Check if the settlement has anything to give first.
Goods gift = is.getRandomGift(getAIRandom());
if (gift == null) continue;
// Do not bring gifts all the time.
if (Utils.randomInt(logger, is.getName() + " bring gifts",
getAIRandom(), 100) >= GIFT_PERCENT) continue;
// Check if there are available units, and if there are already
// enough missions in operation.
List<Unit> availableUnits = new ArrayList<Unit>();
int alreadyAssignedUnits = 0;
for (Unit ou : is.getOwnedUnits()) {
AIUnit aiu = getAIUnit(ou);
if (aiu == null) {
continue;
} else if (aiu.getMission() instanceof IndianBringGiftMission) {
alreadyAssignedUnits++;
} else if (Mission.invalidNewMissionReason(aiu) == null) {
availableUnits.add(ou);
}
}
if (alreadyAssignedUnits > MAX_NUMBER_OF_GIFTS_BEING_DELIVERED) {
logger.finest(is.getName() + " has " + alreadyAssignedUnits
+ " already.");
continue;
} else if (availableUnits.isEmpty()) {
logger.finest(is.getName() + " has no gift units.");
continue;
}
// Pick a random available capable unit.
Unit unit = null;
AIUnit aiUnit = null;
while (unit == null && !availableUnits.isEmpty()) {
Unit u = availableUnits.get(Utils.randomInt(logger,
"Choose gift unit", getAIRandom(),
availableUnits.size()));
availableUnits.remove(u);
aiUnit = getAIUnit(u);
if (IndianBringGiftMission.invalidReason(aiUnit) == null
&& u.findPath(u.getTile(), is.getTile(), null,
CostDeciders.numberOfLegalTiles()) != null) {
unit = u;
}
}
if (unit == null) {
logger.finest(is.getName() + " has no suitable gift units.");
continue;
}
// Collect nearby colonies. Filter out ones which are unreachable
// or with which the settlement is on bad terms.
List<Colony> nearbyColonies = new ArrayList<Colony>();
for (Tile t : is.getTile()
.getSurroundingTiles(MAX_DISTANCE_TO_BRING_GIFTS)) {
Colony c = t.getColony();
if (c != null
&& is.getAlarm(c.getOwner()) != null
&& IndianBringGiftMission.invalidReason(aiUnit, c) == null
&& unit.findPath(is.getTile(), c.getTile(), null,
CostDeciders.numberOfLegalTiles()) != null) {
nearbyColonies.add(c);
}
}
// If there are any suitable colonies, pick a random one
// to send a gift to.
if (nearbyColonies.isEmpty()) {
logger.finest(is.getName() + " has no nearby gift colonies.");
continue;
}
Colony target = nearbyColonies.get(Utils.randomInt(logger,
"Choose gift colony", getAIRandom(),
nearbyColonies.size()));
// Send the unit.
logger.finest("Assigning gift from " + is.getName()
+ " to " + target.getName() + ": " + unit);
aiUnit.setMission(new IndianBringGiftMission(getAIMain(),
aiUnit, target));
}
}
/**
* Demands tribute from nasty players with nearby colonies.
*/
private void demandTribute() {
final Map map = getGame().getMap();
final Player player = getPlayer();
for (IndianSettlement is : player.getIndianSettlements()) {
// Do not demand tribute all of the time.
if (Utils.randomInt(logger, is.getName() + " demand tribute",
getAIRandom(), 10) != 0) continue;
// Check if there are available units, and if there are already
// enough missions in operation.
List<Unit> availableUnits = new ArrayList<Unit>();
int alreadyAssignedUnits = 0;
for (Unit ou : is.getOwnedUnits()) {
AIUnit aiu = getAIUnit(ou);
if (Mission.invalidNewMissionReason(aiu) == null) {
if (aiu.getMission() instanceof IndianDemandMission) {
alreadyAssignedUnits++;
} else {
availableUnits.add(ou);
}
}
}
if (alreadyAssignedUnits > MAX_NUMBER_OF_DEMANDS) {
logger.finest(is.getName() + " has " + alreadyAssignedUnits
+ " already.");
continue;
} else if (availableUnits.isEmpty()) {
logger.finest(is.getName() + " has no demand units.");
continue;
}
// Pick a random available capable unit.
Unit unit = null;
AIUnit aiUnit = null;
while (unit == null && !availableUnits.isEmpty()) {
Unit u = availableUnits.get(Utils.randomInt(logger,
"Choose demand unit", getAIRandom(),
availableUnits.size()));
availableUnits.remove(u);
aiUnit = getAIUnit(u);
if (IndianDemandMission.invalidReason(aiUnit) == null
&& u.findPath(u.getTile(), is.getTile(), null,
CostDeciders.numberOfLegalTiles()) != null) {
unit = u;
}
}
if (unit == null) {
logger.finest(is.getName() + " has no suitable demand units.");
continue;
}
// Collect nearby colonies. Filter out ones which are unreachable
// or with which the settlement is on adequate terms.
List<Colony> nearbyColonies = new ArrayList<Colony>();
for (Tile t : is.getTile()
.getSurroundingTiles(MAX_DISTANCE_TO_MAKE_DEMANDS)) {
Colony c = t.getColony();
if (c != null
&& is.getAlarm(c.getOwner()) != null
&& IndianDemandMission.invalidReason(aiUnit, c) == null
&& unit.findPath(is.getTile(), c.getTile(), null,
CostDeciders.numberOfLegalTiles()) != null) {
nearbyColonies.add(c);
}
}
// If there are any suitable colonies, pick one to demand from.
// Sometimes a random one, sometimes the weakest, sometimes the
// most annoying.
if (nearbyColonies.isEmpty()) {
logger.finest(is.getName() + " has no nearby demand colonies.");
continue;
}
int rnd = Utils.randomInt(logger, "Choose demand colony",
getAIRandom(), 3 * nearbyColonies.size());
Colony target = null;
if (rnd < nearbyColonies.size()) {
target = nearbyColonies.get(rnd);
} else if (rnd < 2 * nearbyColonies.size()) {
int bestValue = Integer.MAX_VALUE;
for (Colony c : nearbyColonies) {
int value = ((c.getStockade() == null) ? 0
: (c.getStockade().getLevel() * 10))
+ c.getUnitCount();
if (value < bestValue) {
value = bestValue;
target = c;
}
}
} else {
int bestValue = -1;
for (Colony c : nearbyColonies) {
if (is.getAlarm(c.getOwner()).getValue() > bestValue) {
bestValue = is.getAlarm(c.getOwner()).getValue();
target = c;
}
}
}
if (target == null) {
throw new IllegalStateException("No target!?!");
}
// Send the unit.
logger.finest("Assigning demand from " + is.getName()
+ " to " + target.getName() + ": " + unit);
aiUnit.setMission(new IndianDemandMission(getAIMain(),
aiUnit, target));
}
}
/**
* Evaluates a proposed mission type for a unit.
*
* @param aiUnit The <code>AIUnit</code> to perform the mission.
* @param path A <code>PathNode</code> to the target of this mission.
* @param type The mission type.
* @return A score representing the desirability of this mission.
*/
public int scoreMission(AIUnit aiUnit, PathNode path, Class type) {
int value = super.scoreMission(aiUnit, path, type);
if (type == DefendSettlementMission.class) {
// Reduce value in proportion to the number of active defenders.
Settlement settlement = (Settlement)DefendSettlementMission
.extractTarget(aiUnit, path);
value -= 75 * getSettlementDefenders(settlement);
} else if (type == UnitSeekAndDestroyMission.class) {
// Natives prefer to attack when DISPLEASED.
Location target = UnitSeekAndDestroyMission
.extractTarget(aiUnit, path);
Player targetPlayer = (target instanceof Ownable)
? ((Ownable)target).getOwner()
: null;
IndianSettlement is = aiUnit.getUnit().getIndianSettlement();
if (targetPlayer != null
&& is != null && is.getAlarm(targetPlayer) != null) {
value += is.getAlarm(targetPlayer).getValue()
- Tension.Level.DISPLEASED.getLimit();
}
}
return value;
}
/**
* Resolves a diplomatic trade offer.
*
* @param agreement The proposed <code>DiplomaticTrade</code>.
* @return True if the agreement is accepted.
*/
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:
// Invalid, never accept.
validOffer = false;
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 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 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;
}
// Use default acceptTax, acceptMercenaries, determineStances,
// selectFoundingFather.
}