/**
* 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.goal;
import java.util.ArrayList;
import java.util.Iterator;
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.IndianSettlement;
import net.sf.freecol.common.model.PathNode;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.Unit.Role;
import net.sf.freecol.server.ai.AIPlayer;
import net.sf.freecol.server.ai.AIUnit;
/**
* This {@link Goal} deals with all missionaries of one {@link AIPlayer}.
* </p><p>
* For each missionary unit that is being added, this goal will try to find
* an {@link IndianSettlement} needing a visit.
* Distance and reachability from the current position of the unit are
* taken into account, with the implicit assumption that the current
* location of the unit is sensible in that a nearby settlement is
* even worth visiting.
* Since missionary units are either created in a player-owned colony,
* or brought there from Europe, this assumption will most often be valid.
* </p><p>
* If a settlement has been found, a {@link CreateMissionAtSettlementGoal}
* will be created, and the unit be moved there.
*/
public class ManageMissionariesGoal extends Goal {
private static final Logger logger = Logger.getLogger(ManageMissionariesGoal.class.getName());
//Since all our subgoals are the same, we're keeping them on a simple list
private List<Goal> subGoalList;
public ManageMissionariesGoal(AIPlayer p, Goal g, float w) {
super(p,g,w);
subGoalList = new ArrayList<Goal>();
}
protected Iterator<AIUnit> getOwnedAIUnitsIterator() {
//we're managing units by directly putting them to individual subgoals,
//so all our own units at any moment are the unused ones
return availableUnitsList.iterator();
}
protected Iterator<Goal> getSubGoalIterator() {
//all our subgoals are on the subGoalList
return subGoalList.iterator();
}
protected void removeUnit(AIUnit u) {
Iterator<AIUnit> uit = availableUnitsList.iterator();
while (uit.hasNext()) {
AIUnit unit = uit.next();
if (unit.equals(u)) {
uit.remove();
}
}
}
/**
* Plans this goal.
* NOTE: This goal currently does not send unit requests, but only deals
* with the units it gets passively.
*/
protected void plan() {
isFinished = false;
//cancel already finished subgoals first
//most of the time, we won't get any units back from this
Iterator<Goal> git = subGoalList.iterator();
while (git.hasNext()) {
Goal g = git.next();
if (g.isFinished()) {
List<AIUnit> units = g.cancelGoal();
availableUnitsList.addAll(units);
git.remove();
}
}
//check whether our unit references are still valid,
//so that we can use them in the following step
validateOwnedUnits();
//Run through available units. If it's a missionary, create a subgoal
//for it. If not, return unit to AIPlayer.
Iterator<AIUnit> uit = availableUnitsList.iterator();
while (uit.hasNext()) {
AIUnit u = uit.next();
uit.remove();
if (u.getUnit().getRole() == Role.MISSIONARY) {
IndianSettlement i = findSettlement(u.getUnit().getTile());
if (i != null) {
PathNode pathNode = u.getUnit().findPath(i.getTile());
if (pathNode != null) {
logger.info("Creating subgoal CreateMissionAtSettlementGoal.");
CreateMissionAtSettlementGoal g = new CreateMissionAtSettlementGoal(player,this,1,u,i);
subGoalList.add(g);
}
}
} else {
//Setting goal=null will make the unit appear in the unit iterator next turn.
//TODO: What about this turn?
u.setGoal(null);
}
}
if (availableUnitsList.size()==0 && subGoalList.size()==0) {
//we don't have any units to deal with, and no active subgoals
//signal that we may safely be cancelled now
isFinished = true;
} else {
//set subgoal weights in case their number has changed
float newWeight = 1f/subGoalList.size();
git = subGoalList.iterator();
while (git.hasNext()) {
Goal g = git.next();
g.setWeight(newWeight);
}
}
}
public String getGoalDescription() {
String descr = super.getGoalDescription();
descr += ":"+availableUnitsList.size();
return descr;
}
/* INTERNAL *******************************************************************/
private IndianSettlement findSettlement(Tile tile) {
if (tile == null) {
//TODO: We're in europe - let's deal with it.
return null;
} else {
//Possible TODO: Slightly randomize findings?
//Otherwise, missionaries starting from the same position will find
//the same settlement.
for (Tile t : tile.getSurroundingTiles(MAX_SEARCH_RADIUS)) {
IndianSettlement is = t.getIndianSettlement();
if (is != null
&& (is.getMissionary() == null
|| is.getMissionary().getOwner() != player.getPlayer())) {
//TODO: Check if this settlement is reachable
return is;
}
}
}
//TODO: We didn't find a settlement in range - what now?
return null;
}
protected void toXMLImpl(XMLStreamWriter out) throws XMLStreamException {
//TODO
}
protected void readFromXMLImpl(XMLStreamReader in)
throws XMLStreamException {
//TODO
}
}