/** * 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.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.Location; import net.sf.freecol.common.model.PathNode; import net.sf.freecol.common.model.Player; import net.sf.freecol.common.model.Settlement; import net.sf.freecol.common.model.Tile; import net.sf.freecol.common.model.Unit; import net.sf.freecol.common.model.pathfinding.GoalDecider; import net.sf.freecol.server.ai.mission.DefendSettlementMission; import net.sf.freecol.server.ai.mission.Mission; import net.sf.freecol.server.ai.mission.TransportMission; 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 REF player. * * For now, mostly just the EuropeanAIPlayer, with a few tweaks. */ public class REFAIPlayer extends EuropeanAIPlayer { private static final Logger logger = Logger.getLogger(REFAIPlayer.class.getName()); /** * Creates a new <code>REFAIPlayer</code>. * * @param aiMain The main AI-class. * @param player The player that should be associated with this * <code>REFAIPlayer</code>. */ public REFAIPlayer(AIMain aiMain, ServerPlayer player) { super(aiMain, player); uninitialized = getPlayer() == null; } /** * Creates a new <code>REFAIPlayer</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 REFAIPlayer(AIMain aiMain, XMLStreamReader in) throws XMLStreamException { super(aiMain, in); uninitialized = getPlayer() == null; } /** * Initialize the REF. * - Find the initial target * - Give valid missions to all the units * * Note that we can not rely on normal AI processing as the * "teleporting" Col1-style REF needs to be placed on the map * before its first turn starts, so the server should ask this * AI where it should arrive on the map. * * Note also that to find a target we can not just call * getMilitaryMission and aim for it as the getMilitaryMission * scoring includes distance from starting point, which is what we * are trying to determine. * So, just choose the best coastal colony. * * FIXME: Mission assignment is done here because ATM the European * AI is prone to send ships full of troops off to attack the * rebel navy. If this is fixed check if the normal mission * assignment works and drop it from here. * * @param teleport "Teleporting" in is allowed. * @return The preferred entry location, or null if no useful preference. */ public Tile initialize(boolean teleport) { // Find a representative offensive land unit to use to search // for the initial target. AIUnit aiUnit = null; for (AIUnit aiu : getAIUnits()) { if (!aiu.getUnit().isNaval() && aiu.getUnit().isOffensiveUnit()) { aiUnit = aiu; break; } } if (aiUnit == null) { logger.warning("REF has no army?!?"); return null; } final Unit unit = aiUnit.getUnit(); // Find the best coastal colony. Location target = UnitSeekAndDestroyMission.findTarget(aiUnit,INFINITY); if (!(target instanceof Colony)) { logger.warning("Rebels have no connected colonies?!?"); return null; } Colony colony = (Colony)target; Tile tile = colony.getTile(); // Give the army seek-and-destroy missions for the target, // then once the army has missions it is possible to give the // navy valid transport missions where needed. final AIMain aiMain = getAIMain(); for (AIUnit aiu : getAIUnits()) { if (!aiu.getUnit().isNaval()) { aiu.setMission(new UnitSeekAndDestroyMission(aiMain, aiu, colony)); } } for (AIUnit aiu : getAIUnits()) { if (aiu.getUnit().isNaval()) { Unit ship = aiu.getUnit(); if (ship.getUnitCount() > 0) { TransportMission tm = new TransportMission(aiMain, aiu); for (Unit u : ship.getUnitList()) { tm.addToTransportList(aiMain.getAIUnit(u)); } aiu.setMission(tm); } } } // Search from the target position to find a Tile to disembark // to, which must be: // - Unoccupied // - Have an unoccupied connected neighbour // // TODO: pick the tile with the best defence and try to avoid // hostile fortifications. final GoalDecider gd = new GoalDecider() { private PathNode goal = null; public PathNode getGoal() { return goal; } public boolean hasSubGoals() { return true; } public boolean check(Unit u, PathNode pathNode) { Tile tile = pathNode.getTile(); if (tile == null || !tile.isEmpty()) return false; for (Tile t : pathNode.getTile().getSurroundingTiles(1)) { if (t.isHighSeasConnected() && t.isEmpty()) { goal = pathNode; return true; } } return false; } }; PathNode path = unit.search(tile, gd, null, 10, unit.getCarrier()); if (path == null) { logger.warning("Can not find suitable REF landing site for: " + colony); return null; } tile = path.getTile(); // If teleporting in, the connected tile is an acceptable target. for (Tile t : tile.getSurroundingTiles(1)) { if (t.isHighSeasConnected() && t.isEmpty()) { tile = t; break; } } if (teleport) return tile; // Unit should be aboard a man-o-war which we can use to find a // path to Europe. Use the end of that path. if (unit.isOnCarrier()) { Tile entry = unit.getCarrier().getBestEntryTile(tile); if (entry != null) return entry; logger.warning("Can not find path to Europe from: " + tile); return null; } logger.warning("REF land unit not aboard a ship: " + unit); return null; } // AI Player interface /** * Tells this <code>REFAIPlayer</code> to make decisions. */ public void startWorking() { final Player player = getPlayer(); logger.finest("Entering method startWorking: " + player + ", year " + getGame().getTurn()); if (!player.isWorkForREF()) { logger.warning("No work for REF: " + player); return; } super.startWorking(); } /** * Evaluates a proposed mission type for a unit, specialized for * REF players. * * @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 (value > 0) { if (type == DefendSettlementMission.class) { // REF garrisons thinly. Location loc = DefendSettlementMission.extractTarget(aiUnit, path); if (loc instanceof Settlement && getSettlementDefenders((Settlement)loc) > 0) value = 0; } else if (type == UnitSeekAndDestroyMission.class) { Location target = UnitSeekAndDestroyMission .extractTarget(aiUnit, path); if (target instanceof Settlement) { // Value connected settlements highly. // Initially, accept no others. if (((Settlement)target).isConnectedPort()) { value += 500; } else { if (getPlayer().getNumberOfSettlements() <= 0) { return Integer.MIN_VALUE; } } } else if (target instanceof Unit) { // Do not chase units until at least one colony is captured. if (getPlayer().getNumberOfSettlements() <= 0) { return Integer.MIN_VALUE; } // Do not chase the same unit! for (AIUnit au : getAIUnits()) { Mission m = au.getMission(); Location loc; if (m != null && m instanceof UnitSeekAndDestroyMission && (loc = ((UnitSeekAndDestroyMission)m) .getTarget()) != null && loc instanceof Unit && loc == target) return Integer.MIN_VALUE; } // The REF is more interested in colonies. value /= 2; } } } return value; } /** * Gives a mission to non-naval units. */ @Override public void giveNormalMissions() { // Give military missions to all REF units. for (AIUnit aiu : getAIUnits()) { Unit u = aiu.getUnit(); if (u.isNaval() || aiu.hasMission()) continue; if (u.isOffensiveUnit()) { Location target = UnitSeekAndDestroyMission.findTarget(aiu, 12); Mission m = (target == null) ? new UnitWanderHostileMission(getAIMain(), aiu) : new UnitSeekAndDestroyMission(getAIMain(), aiu, target); aiu.setMission(m); } } // Fall back to the normal EuropeanAI behaviour for non-army. super.giveNormalMissions(); } }