/** * 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.mission; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import org.freecolandroid.xml.stream.XMLStreamException; import org.freecolandroid.xml.stream.XMLStreamReader; import org.freecolandroid.xml.stream.XMLStreamWriter; import net.sf.freecol.common.model.Ability; import net.sf.freecol.common.model.Colony; import net.sf.freecol.common.model.CombatModel; import net.sf.freecol.common.model.Europe; import net.sf.freecol.common.model.Goods; import net.sf.freecol.common.model.Location; import net.sf.freecol.common.model.Map.Direction; import net.sf.freecol.common.model.Map.Position; import net.sf.freecol.common.model.PathNode; import net.sf.freecol.common.model.Player; import net.sf.freecol.common.model.Tile; import net.sf.freecol.common.model.Unit; import net.sf.freecol.common.model.Unit.MoveType; import net.sf.freecol.common.networking.Connection; import net.sf.freecol.server.ai.AIMain; import net.sf.freecol.server.ai.AIMessage; import net.sf.freecol.server.ai.AIUnit; import org.w3c.dom.Element; public class PrivateerMission extends Mission { private static final Logger logger = Logger.getLogger(PrivateerMission.class.getName()); private static enum PrivateerMissionState {HUNTING,TRANSPORTING}; private PrivateerMissionState state = PrivateerMissionState.HUNTING; private Location nearestPort = null; private Tile target = null; private boolean valid = true; /** * Creates a mission for the given <code>AIUnit</code>. * * @param aiMain The main AI-object. * @param aiUnit The <code>AIUnit</code> this mission * is created for. */ public PrivateerMission(AIMain aiMain, AIUnit aiUnit) { super(aiMain, aiUnit); Unit unit = aiUnit.getUnit(); logger.finest("Assigning PrivateerMission to unit=" + unit + " at " + unit.getTile()); } /** * Loads a mission from the given element. * * @param aiMain The main AI-object. * @param element An <code>Element</code> containing an * XML-representation of this object. */ public PrivateerMission(AIMain aiMain, Element element) { super(aiMain); readFromXMLElement(element); } /** * Creates a new <code>UnitWanderHostileMission</code> and reads the given element. * * @param aiMain The main AI-object. * @param in The input stream containing the XML. * @throws XMLStreamException if a problem was encountered * during parsing. * @see net.sf.freecol.server.ai.AIObject#readFromXML */ public PrivateerMission(AIMain aiMain, XMLStreamReader in) throws XMLStreamException { super(aiMain); readFromXML(in); } /** * Performs the mission. This is done by searching for hostile units * that are located within one tile and attacking them. If no such units * are found, then wander in a random direction. * * @param connection The <code>Connection</code> to the server. */ public void doMission(Connection connection) { logger.finest("Entering doMission"); Unit unit = getUnit(); while(isValid() && unit.getMovesLeft() > 0){ // Unit is between Europe and America, nothing to do if (unit.isAtSea()){ unit.setMovesLeft(0); return; } switch(state){ case HUNTING: hunt4Target(connection); break; case TRANSPORTING: gotoNearestPort(connection); break; } } } private void hunt4Target(Connection connection){ Unit unit = getUnit(); if(unit.getLocation() instanceof Europe){ moveUnitToAmerica(); unit.setMovesLeft(0); return; } logger.finest("Privateer (" + unit.getId() + ") at " + unit.getTile() + " hunting"); // has captured goods, must get them to port if(unit.getGoodsCount() > 0){ state = PrivateerMissionState.TRANSPORTING; return; } final int MAX_TURNS_TO_TARGET = 1; PathNode pathToTarget = findTarget(MAX_TURNS_TO_TARGET); // Found a target if (pathToTarget != null) { target = pathToTarget.getLastNode().getTile(); logger.finest("Privateer (" + unit.getId() + ") at " + unit.getTile() + " found target at " + target); // We need to find an updated path to target pathToTarget = unit.findPath(target); Direction direction = moveTowards(pathToTarget); if (direction == null) { // some movement points may still remain due to some // block or just not enough points for next node we need // to make sure the unit has no points left, so the game // can move to next unit logger.finest("Ending privateer (" + unit.getId() + ") turn, moves=" + unit.getMovesLeft()); unit.setMovesLeft(0); return; } // catch up with the prey if (unit.getMoveType(direction) == MoveType.ATTACK_UNIT) { logger.finest("Privateer (" + unit.getId() + ") at " + unit.getTile() + " attacking target"); AIMessage.askAttack(getAIUnit(), direction); } } else { // No target found, just make a random move target = null; logger.finest("Privateer at " + unit.getTile() + " without target, wandering"); moveRandomly(); } // some movement points may still remain //due to some block or just not enough points for next node // we need to make sure the unit has no points left, //so the game can move to next unit unit.setMovesLeft(0); } private void gotoNearestPort(Connection connection){ Unit unit = getUnit(); if(isUnitInPort()){ dumpCargoInPort(connection); state = PrivateerMissionState.HUNTING; return; } PathNode path = getValidPathForNearestPort(); if(path == null){ findNearestPort(); if (nearestPort == null) { logger.finest("Failed to find port for goods"); valid = false; return; } path = getValidPathForNearestPort(); if (path == null) { logger.finest("Failed to deliver goods to " + nearestPort + ", no path"); valid = false; return; } } boolean moveToEurope = nearestPort instanceof Europe; Direction direction = moveTowards(path); if (direction == null) { unit.setMovesLeft(0); return; } if (moveToEurope && unit.getMoveType(direction) == MoveType.MOVE_HIGH_SEAS) { moveUnitToEurope(); unit.setMovesLeft(0); return; } if(unit.getMoveType(direction) == MoveType.MOVE){ Position unitPos = unit.getTile().getPosition(); Position ColPos = unitPos.getAdjacent(direction); Colony colony = getGame().getMap().getTile(ColPos).getColony(); if(colony == nearestPort){ AIMessage.askMove(getAIUnit(), direction); return; } else{ String errMsg = "Privateer (" + unit.getId() + ") with PrivateerMission trying to enter settlement"; throw new IllegalStateException(errMsg); } } // some movement points may still remain //due to some block or just not enough points for next node // we need to make sure the unit has no points left, //so the game can move to next unit unit.setMovesLeft(0); } private PathNode getValidPathForNearestPort(){ Unit unit = getUnit(); Player player = unit.getOwner(); if(nearestPort == null){ return null; } if(nearestPort instanceof Europe){ if(player.getEurope() == null){ nearestPort = null; return null; } return unit.findPathToEurope(); } Colony nearestColony = (Colony) nearestPort; if(nearestColony == null || nearestColony.isDisposed() || nearestColony.getOwner() != player){ nearestPort = null; return null; } return unit.findPath(nearestColony.getTile()); } private void findNearestPort(){ nearestPort = null; Unit unit = getUnit(); PathNode path = findNearestOtherSettlement(unit); if(path != null){ nearestPort = path.getLastNode().getTile().getColony(); } else{ Europe europe = unit.getOwner().getEurope(); if(europe != null){ nearestPort = europe; } } } private boolean isUnitInPort(){ if(nearestPort == null){ return false; } Unit unit = getUnit(); if(nearestPort instanceof Europe){ return unit.getLocation() == nearestPort; } return unit.getTile() == nearestPort.getTile(); } private void dumpCargoInPort(Connection connection){ logger.finest("Dumping goods"); Unit unit = getUnit(); boolean inEurope = unit.getLocation() instanceof Europe; List<Goods> goodsLst = new ArrayList<Goods>(unit.getGoodsList()); for(Goods goods : goodsLst){ if(inEurope){ logger.finest("Before dumping: money=" + unit.getOwner().getGold()); sellCargoInEurope(goods); logger.finest("After dumping: money=" + unit.getOwner().getGold()); } else{ Colony colony = unit.getTile().getColony(); logger.finest("Before dumping: " + colony.getGoodsCount(goods.getType()) + " " + goods.getType()); unloadCargoInColony(goods); logger.finest("After dumping: " + colony.getGoodsCount(goods.getType()) + " " + goods.getType()); } } for (Unit u : unit.getUnitList()) { unitLeavesShip(getAIMain().getAIUnit(u)); } } /** * Checks if this mission is valid for the given unit. * * @param aiUnit The unit. * @return <code>true</code> if this mission is valid to perform * and <code>false</code> otherwise. */ public static boolean isValid(AIUnit aiUnit) { Unit unit = aiUnit.getUnit(); return Mission.isValid(aiUnit) && unit.isCarrier() && unit.isOffensiveUnit() && unit.hasAbility(Ability.PIRACY) && unit.getGoodsCount() == 0 && unit.getUnitCount() == 0; } /** * Checks if this mission is still valid to perform. * * @return True if the mission is still valid. */ public boolean isValid() { return super.isValid() && valid && isValid(getAIUnit()); } /** * Gets debugging information about this mission. This string is a short * representation of this object's state. * */ public String getDebuggingInfo() { StringBuffer sb = new StringBuffer("State: " + state.name()); if(state == PrivateerMissionState.HUNTING && target != null){ Unit targetUnit = target.getDefendingUnit(getUnit()); if(targetUnit != null){ String coord = " (" + target.getX() + "," + target.getY() + ")"; sb.append(" target=" + targetUnit + coord); } } return sb.toString(); } /** * Calculates the modifier used when assessing the value of a * target to a privateer. * Note: it gives a modifier value, other parameters should be * considered as well * Note: we assume the unit given is a privateer, no test is made * * @param combatModel The <code>Combat Model</code> used. * @param attacker The <code>Unit</code> attacking, should be a privateer. * @param defender The <code>Unit</code> the attacker is considering as a target. * @return The modifier value the defender is worth as a target to the privateer */ public static int getModifierValueForTarget(CombatModel combatModel, Unit attacker, Unit defender){ // pirates are greedy ;) int modifier = 100; modifier += defender.getGoodsCount() * 200; modifier += defender.getUnitCount() * 100; // they are also coward if(defender.isOffensiveUnit()){ modifier -= combatModel.getDefencePower(attacker, defender) * 100; } return modifier; } /** * Writes all of the <code>AIObject</code>s and other AI-related * information to an XML-stream. * * @param out The target stream. * @throws XMLStreamException if there are any problems writing to the * stream. */ protected void toXMLImpl(XMLStreamWriter out) throws XMLStreamException { toXML(out, getXMLElementTagName()); } protected void writeAttributes(XMLStreamWriter out) throws XMLStreamException { super.writeAttributes(out); out.writeAttribute("state", state.toString()); } protected void readAttributes(XMLStreamReader in) throws XMLStreamException { super.readAttributes(in); state = PrivateerMissionState.valueOf(in.getAttributeValue(null, "state")); } /** * Returns the tag name of the root element representing this object. * * @return "privateerMission" */ public static String getXMLElementTagName() { return "privateerMission"; } }