package net.sf.colossus.xmlparser; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import net.sf.colossus.game.Caretaker; import net.sf.colossus.game.RecruitGraph; import net.sf.colossus.server.CustomRecruitBase; import net.sf.colossus.server.VariantKnower; import net.sf.colossus.server.VariantSupport; import net.sf.colossus.util.HTMLColor; import net.sf.colossus.util.ObjectCreationException; import net.sf.colossus.util.StaticResourceLoader; import net.sf.colossus.variant.AllCreatureType; import net.sf.colossus.variant.CreatureType; import net.sf.colossus.variant.ICustomRecruitBase; import net.sf.colossus.variant.IVariantInitializer; import net.sf.colossus.variant.MasterBoardTerrain; import net.sf.colossus.variant.MasterHex; import net.sf.colossus.variant.RecruitingSubTree; import net.sf.colossus.variant.Variant.AcquirableData; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; /** * TerrainRecruitLoader load the terrains and recruits descriptions. * * TODO check if any of the methods still needs the "String terrain" parameter * * TODO we still use plenty of strings in here since the creatures are mixed with the * special recruit requirements such as Anything/Lord/AnyNonLord or the custom * recruits marked by the "Special:" keyword * * @author Romain Dolbeau * * @see net.sf.colossus.variant.CreatureType */ public class TerrainRecruitLoader implements IVariantInitializer { private static final Logger LOGGER = Logger .getLogger(TerrainRecruitLoader.class.getName()); public static final String Keyword_Anything = "Anything"; public static final String Keyword_AnyNonLord = "AnyNonLord"; public static final String Keyword_Lord = "Lord"; public static final String Keyword_DemiLord = "DemiLord"; public static final String Keyword_Special = "Special:"; /* Only needed during loaded. During game time, this should be * queried from the Variant. */ private int aquirableRecruitmentsValue; /** Base amount of points needed for Titan improvement. */ private int titanImprove = 100; /** Amount of points needed for Titan Teleport. */ private int titanTeleport = 400; /** * Map a terrain to a list of recruits. * * TODO integrate into {@link MasterBoardTerrain} */ private static Map<MasterBoardTerrain, List<RecruitNumber>> strToRecruits = new HashMap<MasterBoardTerrain, List<RecruitNumber>>(); /** * Map a terrain to a list of recruits. * * TODO integrate into {@link MasterBoardTerrain} */ private static Map<MasterBoardTerrain, List<StartingNumber>> strToStarters = new HashMap<MasterBoardTerrain, List<StartingNumber>>(); /** * Map a terrain to a boolean, * telling if a Creature can recruit in the usual way or not. * * TODO integrate into {@link MasterBoardTerrain} */ private static Map<MasterBoardTerrain, Boolean> strToBelow = new HashMap<MasterBoardTerrain, Boolean>(); /** * Map a terrain to an * optional BattlelandsRandomizer filename. * * TODO integrate into {@link MasterBoardTerrain} */ private static Map<MasterBoardTerrain, String> strToRnd = new HashMap<MasterBoardTerrain, String>(); /** * A map from the terrain names to the terrains. */ private static Map<String, MasterBoardTerrain> terrains = new HashMap<String, MasterBoardTerrain>(); /** * The list of Acquirable Creature, as acquirableData. * @see net.sf.colossus.variant.Variant.AcquirableData */ private static List<AcquirableData> acquirableList = null; /** support for the custom recruiting functions ; map the class name to an instance of the class. */ private static Map<String, CustomRecruitBase> nameToInstance = new HashMap<String, CustomRecruitBase>(); /** * Representation of the Recruiting Graph (for use) * (sometimes called Recruiting Tree). * TODO the VariantKnower is meant only as temporary solution; when * variant loading and all this stuff here is not static any more, * variant should be passed in or set afterwards or something... */ private static RecruitGraph graph = new RecruitGraph(new VariantKnower()); /** The AllCreatureType object to use, needed to convert from String (name) * to the actual CreatureType. */ private final AllCreatureType creatureTypes; /** * set the Caretaker used by the graph * (needed to know what creatures are still available) */ public static void setCaretaker(Caretaker caretaker) { LOGGER.log(Level.FINEST, "GRAPH: Setting the CaretakerInfo"); graph.setCaretaker(caretaker); } private static boolean isConcreteCreature(String name) { return (!(name.equals(Keyword_Anything)) && !(name.equals(Keyword_AnyNonLord)) && !(name.equals(Keyword_Lord)) && !(name.equals(Keyword_DemiLord)) && !(name .startsWith(Keyword_Special))); } /** * Add an entire terrain recruiting list to the Recruiting Graph. * @param rl The list of RecruitNumber to add to the graph. */ private static void addToGraph(List<RecruitNumber> rl, MasterBoardTerrain t) { Iterator<RecruitNumber> it = rl.iterator(); String v1 = null; boolean regularRecruit = strToBelow.get(t).booleanValue(); try { while (it.hasNext()) { RecruitNumber tr = it.next(); String v2 = tr.getName(); if ((v2 != null) && !(v2.equals("Titan")) && isConcreteCreature(v2) && !(tr.getNumber() < 0)) { // we musn't add the Edges going to non-recruitable if (v1 != null) { graph.addEdge(v1, v2, tr.getNumber(), t); } // add the self-recruit & below-recruit loop Iterator<RecruitNumber> it2 = rl.iterator(); boolean done = false; while (it2.hasNext() && !done) { RecruitNumber tr2 = it2.next(); if ((tr == tr2) || // same List, same objects regularRecruit) { // one can always recruit itself at one/zero // level, and also below if regularRecruit is on. String v3 = tr2.getName(); if (isConcreteCreature(v3) && !v3.equals("Titan")) { if ((tr2.getNumber() > 0)) { graph.addEdge(v2, v3, 1, t); } else if ((tr2.getNumber() == 0)) { graph.addEdge(v2, v3, 0, t); } } } if (tr == tr2) { done = true; } } } if ((v2 != null) && v2.startsWith(Keyword_Special)) { // special recruitment, need to add edge // between the special aned every possible recruit ICustomRecruitBase cri = getCustomRecruitBase(v2); List<CreatureType> allRecruits = cri .getAllPossibleSpecialRecruits(t); for (CreatureType cre : allRecruits) { // use 99 so no-one will rely on this graph.addEdge(v2, cre.getName(), RecruitGraph.BIGNUM, t); } } v1 = v2; } } catch (Exception e) { LOGGER.log(Level.SEVERE, "Couldn't fill graph.", e); } } /* make sure that all static elements are null or empty when creating a * new TerrainRecruitLoader object... */ { if (acquirableList != null) { LOGGER.log(Level.FINEST, "TerrainRecruitLoader: Destroying previous " + "``acquirableList'' ; this should never happen " + "during a game..."); acquirableList = null; } if (!terrains.isEmpty()) { LOGGER.log(Level.FINEST, "TerrainRecruitLoader: Destroying previous " + "``terrains'' ; this should never happen during " + "a game..."); terrains.clear(); } strToRecruits.clear(); strToBelow.clear(); strToRnd.clear(); nameToInstance.clear(); graph.clear(); } // we need to cast since JDOM is not generified @SuppressWarnings("unchecked") public TerrainRecruitLoader(InputStream terIS, AllCreatureType creatureTypes) { this.creatureTypes = creatureTypes; SAXBuilder builder = new SAXBuilder(); try { Document doc = builder.build(terIS); Element root = doc.getRootElement(); List<Element> lterrains = root.getChildren("terrain"); for (Element el : lterrains) { handleTerrain(el); } List<Element> aliases = root.getChildren("alias"); for (Element el : aliases) { handleAlias(el); } if (acquirableList == null) { acquirableList = new ArrayList<AcquirableData>(); } List<Element> acquirables = root.getChildren("acquirable"); for (Element el : acquirables) { handleAcquirable(el); } Element el = root.getChild("titan_improve"); if (el != null) { titanImprove = el.getAttribute("points").getIntValue(); } el = root.getChild("titan_teleport"); if (el != null) { titanTeleport = el.getAttribute("points").getIntValue(); } } catch (JDOMException ex) { LOGGER.log(Level.SEVERE, "JDOM exception caught", ex); } catch (IOException ex) { LOGGER.log(Level.SEVERE, "IO exception caught", ex); } catch (ParseException ex) { LOGGER.log(Level.SEVERE, "Parse exception caught", ex); } } // we need to cast since JDOM is not generified @SuppressWarnings("unchecked") private void handleTerrain(Element el) throws JDOMException { String name = el.getAttributeValue("name"); String displayName = el.getAttributeValue("display_name"); if (displayName == null) { displayName = name; } String color = el.getAttributeValue("color"); MasterBoardTerrain terrain = new MasterBoardTerrain(name, displayName, HTMLColor.stringToColor(color)); ArrayList<RecruitNumber> rl = new ArrayList<RecruitNumber>(); boolean regularRecruit = el.getAttribute("regular_recruit") .getBooleanValue(); List<Element> recruits = el.getChildren("recruit"); for (Element recruit : recruits) { String recruitName = recruit.getAttributeValue("name"); int recruitNum = recruit.getAttribute("number").getIntValue(); RecruitNumber rn = new RecruitNumber(recruitName, recruitNum); rl.add(rn); } ArrayList<StartingNumber> sl = new ArrayList<StartingNumber>(); List<Element> starters = el.getChildren("starting"); int total = 0; for (Element starter : starters) { String starterName = starter.getAttributeValue("name"); int starterNum = starter.getAttribute("number").getIntValue(); if (starterNum != 2) { LOGGER.warning("Only '2' is a supported value for starting" + " creatures at the moment ..."); } StartingNumber rn = new StartingNumber(starterName, starterNum); sl.add(rn); total += starterNum; } if (!sl.isEmpty()) { if (total != 6) { LOGGER.warning("There isn't exactly 6 starting creatures in" + " this terrain ! " + total + " were found in " + name); } TerrainRecruitLoader.strToStarters.put(terrain, sl); } TerrainRecruitLoader.strToRecruits.put(terrain, rl); TerrainRecruitLoader.strToBelow.put(terrain, Boolean.valueOf(regularRecruit)); // XXX Random not yet supported: TerrainRecruitLoader.strToRnd.put(terrain, null); terrains.put(name, terrain); addToGraph(rl, terrain); RecruitingSubTree rst = buildRecruitingSubTree(rl, regularRecruit); terrain.setRecruitingSubTree(rst); } private RecruitingSubTree buildRecruitingSubTree(List<RecruitNumber> rl, boolean regularRecruit) { RecruitingSubTree rst = new RecruitingSubTree(this.creatureTypes); RecruitNumber recruiter = null; for (RecruitNumber recruit : rl) { if (recruit.getName().equals(Keyword_Anything) || recruit.getName().equals(Keyword_AnyNonLord) || recruit.getName().equals(Keyword_Lord) || recruit.getName().equals(Keyword_DemiLord) || recruit.getName().equals("Titan")) { recruiter = recruit; continue; } if (recruit.getName().startsWith(Keyword_Special)) { rst.addCustom(getCustomRecruitBase(recruit.getName())); recruiter = null; continue; } if (recruit.getNumber() < 0) { assert regularRecruit == false : "Oups, number for recruit is " + recruit.getNumber() + " but regularRecruit is true"; recruiter = recruit; continue; } if (recruiter != null) { if (recruiter.getName().equals(Keyword_Anything)) { rst.addAny( creatureTypes.getCreatureTypeByName(recruit.getName()), recruit.getNumber()); } else if (recruiter.getName().equals(Keyword_AnyNonLord)) { rst.addNonLord( creatureTypes.getCreatureTypeByName(recruit.getName()), recruit.getNumber()); } else if (recruiter.getName().equals(Keyword_Lord)) { rst.addLord( creatureTypes.getCreatureTypeByName(recruit.getName()), recruit.getNumber()); } else if (recruiter.getName().equals(Keyword_DemiLord)) { rst.addDemiLord( creatureTypes.getCreatureTypeByName(recruit.getName()), recruit.getNumber()); } else { rst.addRegular( creatureTypes.getCreatureTypeByName(recruiter .getName()), creatureTypes.getCreatureTypeByName(recruit.getName()), recruit.getNumber()); } } recruiter = recruit; } rst.complete(regularRecruit); return rst; } // we need to cast since JDOM is not generified private void handleAlias(Element el) throws ParseException { String name = el.getAttributeValue("name"); String source = el.getAttributeValue("source"); String displayName = el.getAttributeValue("display_name"); if (displayName == null) { displayName = name; } String color = el.getAttributeValue("color"); MasterBoardTerrain source_terrain = terrains.get(source); if (source_terrain == null) { throw new ParseException("Alias uses an invalid source name"); } MasterBoardTerrain terrain = new MasterBoardTerrain(name, displayName, HTMLColor.stringToColor(color), true); TerrainRecruitLoader.strToRecruits.put(terrain, strToRecruits.get(source_terrain)); TerrainRecruitLoader.strToBelow.put(terrain, strToBelow.get(source_terrain)); // XXX Random not yet supported: TerrainRecruitLoader.strToRnd.put(terrain, null); List<StartingNumber> lsn = TerrainRecruitLoader.strToStarters .get(source_terrain); if (lsn != null) { TerrainRecruitLoader.strToStarters.put(terrain, lsn); } terrains.put(name, terrain); addToGraph(strToRecruits.get(source_terrain), terrain); source_terrain.addAlias(terrain); terrain.setRecruitingSubTree(source_terrain.getRecruitingSubTree()); } private void handleAcquirable(Element el) throws JDOMException, ParseException { String name = el.getAttribute("name").getValue(); if (name == null) { throw new ParseException("Acquirable is missing name attribute"); } // TODO the name attribute should be validated to be an actual creature // name. Currently this is not yet possible since we don't necessarily // have the creatures yet. It could be fixed with a quick hack, reordering // the loading process and passing the List<CreatureType> around, but the // proper solution would be passing the results of this class as objects // around instead of using static code, then validate consistency on // construction of the Variant instance, which would get all the necessary // information to do that int points = el.getAttribute("points").getIntValue(); if (points == 0) { throw new ParseException("Acquirable '" + name + "' has invalid points"); } List<MasterBoardTerrain> terrains = new ArrayList<MasterBoardTerrain>(); String terrainId = el.getAttributeValue("terrain"); if (terrainId != null) { MasterBoardTerrain terrain = getTerrainById(terrainId); if (terrain == null) { throw new ParseException("Illegal terrainId '" + terrainId + "' in variant for aquirable '" + name + "'"); } terrains.add(terrain); } if (acquirableList.size() == 0) { // First acquirable - initialize the base value aquirableRecruitmentsValue = points; } if ((points % aquirableRecruitmentsValue) != 0) { throw new ParseException("Wrong point value " + points + " for Acquirable with name=" + name + " in terrain=" + terrainId + " : should be multiple of " + aquirableRecruitmentsValue); } AcquirableData ad = new AcquirableData(name, points, terrains); acquirableList.add(ad); } /** * Return a collection of all possible terrains. * NOTE: Only meant to be used for Variant Initialization! * In normal cases this list should be get from variant object. * * @return A collection containing all instances of {@link MasterBoardTerrain}. */ public Collection<MasterBoardTerrain> getTerrains() { return Collections.unmodifiableCollection(terrains.values()); } protected static MasterBoardTerrain getTerrainById(String id) { return terrains.get(id); } /** * Helper class, associating a Creature and a number. * * The basic identification is the name (because of the hack of using * special name for special stuff...) but the CreatureType is there to * avoid reloading from the Variant all the time. * We can't look-up at creation time, because the variant isn't available * yet, so we delay until the first call to getCreature. * * @author Romain Dolbeau */ private abstract class CreatureAndNumber { /** * The Creature in the pair (if it exists) */ private CreatureType creature = null; /** * The Name */ private final String name; /** * The number in the pair */ private final int number; private boolean checked = false; /** * @param n The Name of the creature * @param i The Number */ public CreatureAndNumber(String n, int i) { name = n; number = i; } String getName() { return name; } CreatureType getCreature() { if (!checked) { if (isConcreteCreature(name)) { // TODO avoid static access to VariantSupport // (get variant passed in, -OR- after loading make a loop // that resolves all of them (and has variant passed in) creature = VariantSupport.getCurrentVariant() .getCreatureByName(name); } else { creature = null; } // set to true AFTERwards to avoid potential race condition checked = true; } assert (creature != null); return creature; } int getNumber() { return number; } /** * Textual representation of the data. * @return Textual representation of the data as a String. */ @Override public String toString() { return ("(" + getNumber() + "," + getName() + ")"); } } /** * Used internally to associate a creature name and the number of * creatures needed to recruit it. * * @author Romain Dolbeau */ private class RecruitNumber extends CreatureAndNumber { /** * @param n Name of the creature * @param i Number of creatures needed to recruit it in the * terrain considered. */ public RecruitNumber(String n, int i) { super(n, i); } } /** * Used internally to associate a creature name and the number received * when starting a game. * * @author Romain Dolbeau */ private class StartingNumber extends CreatureAndNumber { /** * @param n Name of the creature * @param i Number of creatures when starting. */ public StartingNumber(String n, int i) { super(n, i); } } public static ICustomRecruitBase getCustomRecruitBase(String specialString) { CustomRecruitBase cri = nameToInstance.get(specialString); if (cri != null) { return cri; } String className = specialString.substring(8); try { Object o = StaticResourceLoader.getNewObject(className, VariantSupport.getVarDirectoriesList()); cri = (CustomRecruitBase)o; nameToInstance.put(specialString, cri); return cri; } catch (ObjectCreationException e) { // TODO it might be better to throw an exception to avoid fail late scenarios LOGGER.log(Level.SEVERE, "CustomRecruitBase doesn't exist for: " + specialString); return null; } } /** * Give an array of the starting creatures, those available in the first * turn and in a particular kind of Tower. * TODO this heuristic (first 3 creatures in the tower) should * be replaced by a real entry in the Tower terrain (similar to startlist). * @param hex The specific Tower considered. * @return an array of Creature representing the starting creatures. * @see net.sf.colossus.variant.CreatureType */ public static CreatureType[] getStartingCreatures(MasterHex hex) { List<StartingNumber> sl = strToStarters.get(hex.getTerrain()); if ((sl == null) || sl.isEmpty()) { if (!hex.getTerrain().isTower()) { LOGGER.warning("getStartingCreatures should not be called" + " on a terrain that is'nt a Tower and hasn't a list " + "of starting creatures."); return null; } LOGGER.warning("No starting creatures found in Tower " + hex.getLabel() + ", please fix the variant. Using first three creatures in" + " the recruiting tree instead."); CreatureType[] bc = new CreatureType[3]; List<CreatureType> to = getPossibleRecruits(hex.getTerrain(), hex); bc[0] = to.get(0); bc[1] = to.get(1); bc[2] = to.get(2); return (bc); } CreatureType[] bc = new CreatureType[sl.size()]; for (int i = 0; i < bc.length; i++) { bc[i] = sl.get(i).getCreature(); } return bc; } /** * Tell whether given type is in the loaded variant a start creature, * i.e. one of those one gets in the initial legion in the tower (any * tower). * * I plan to use this for e.g. HexRecruitTreePanel, to show there * how one can get to have a certain creature: * start creature * -or- acquirable * -or- recruitable by N of from prev. in tree, * -or- recruitable by any/Lord/DemiLord/anyNonLord * -or- recruitable by N of something else (e.g. Titan=>Warlock) * @param type * @return true if this is a start creature in the loaded variant */ public static boolean isStartCreature(CreatureType type) { String name = type.getName(); for (List<StartingNumber> sl : strToStarters.values()) { for (StartingNumber sn : sl) { if (sn.getName().equals(name)) { return true; } } } return false; } /** * Give the name of the random filename to use to generate this terrain, * or null if it's a static Battlelands. * * @param masterBoardTerrain A master board terrain. * @return The name of the random source file as a String */ public static String getTerrainRandomName( MasterBoardTerrain masterBoardTerrain) { return strToRnd.get(masterBoardTerrain); } /** * Give a modifiable list of the possible recruits in a terrain. * @param terrain The terrain to consider. * @param hex The specific hex to consider. It shouldn't be null during * the actual recruiting, but it can be null when doing evaluation (it's * only used for special recruiting in custom variants). * @return List of Creatures that can be recruited in the terrain. * @see net.sf.colossus.variant.CreatureType */ public static List<CreatureType> getPossibleRecruits( MasterBoardTerrain terrain, MasterHex hex) { List<RecruitNumber> al = strToRecruits.get(terrain); List<CreatureType> result = new ArrayList<CreatureType>(); Iterator<RecruitNumber> it = al.iterator(); while (it.hasNext()) { RecruitNumber tr = it.next(); if ((tr.getNumber() >= 0) && isConcreteCreature(tr.getName()) && !tr.getName().equals("Titan")) { result.add(tr.getCreature()); } if (tr.getName().startsWith(Keyword_Special)) { ICustomRecruitBase cri = getCustomRecruitBase(tr.getName()); if (cri != null) { List<? extends CreatureType> temp = cri .getPossibleSpecialRecruits(hex); result.addAll(temp); } } } Set<CreatureType> theSet = terrain.getRecruitingSubTree() .getPossibleRecruits(hex); Set<CreatureType> theSet2 = new TreeSet<CreatureType>(result); if (!theSet.equals(theSet2)) { LOGGER.warning("Oups, discrepancy between old (graph-based) and " + "new (RST-based) values for getPossibleRecruits"); LOGGER.warning("Old one is:"); for (CreatureType ct : theSet2) { LOGGER.warning("\t" + ct.getName()); } LOGGER.warning("New one is:"); for (CreatureType ct : theSet) { LOGGER.warning("\t" + ct.getName()); } } return result; } /** * Give a modifiable list of the possible recruiters in a terrain. * * TODO if clients need to modify they should make copies themselves, it * seems better if have this class return an unmodifiable list * * @param terrain String representing a terrain. * @return List of Creatures that can recruit in the terrain. * @see net.sf.colossus.variant.CreatureType */ public static List<CreatureType> getPossibleRecruiters( MasterBoardTerrain terrain, MasterHex hex) { List<RecruitNumber> al = strToRecruits.get(terrain); List<CreatureType> re = new ArrayList<CreatureType>(); Iterator<RecruitNumber> it = al.iterator(); while (it.hasNext()) { RecruitNumber tr = it.next(); if (isConcreteCreature(tr.getName())) { re.add(tr.getCreature()); } else { if (tr.getName().equals(Keyword_Anything)) { // anyone can recruit here... return new ArrayList<CreatureType>(VariantSupport .getCurrentVariant().getCreatureTypesAsList()); } if (tr.getName().equals(Keyword_AnyNonLord)) { // anyone can recruit here... // TODO: why two cases if the same result as the last one return new ArrayList<CreatureType>(VariantSupport .getCurrentVariant().getCreatureTypesAsList()); } if (tr.getName().equals(Keyword_Lord)) { List<CreatureType> potential = VariantSupport .getCurrentVariant().getCreatureTypesAsList(); Iterator<CreatureType> itCr = potential.iterator(); while (itCr.hasNext()) { CreatureType creature = itCr.next(); if (creature.isLord()) { re.add(creature); } } } if (tr.getName().equals(Keyword_DemiLord)) { List<CreatureType> potential = VariantSupport .getCurrentVariant().getCreatureTypesAsList(); Iterator<CreatureType> itCr = potential.iterator(); while (itCr.hasNext()) { CreatureType creature = itCr.next(); if (creature.isDemiLord()) { re.add(creature); } } } if (tr.getName().startsWith(Keyword_Special)) { ICustomRecruitBase cri = getCustomRecruitBase(tr.getName()); if (cri != null) { List<CreatureType> temp = cri .getPossibleSpecialRecruiters(hex); re.addAll(temp); } } } } Set<CreatureType> theSet = terrain.getRecruitingSubTree() .getPossibleRecruiters(hex); Set<CreatureType> theSet2 = new TreeSet<CreatureType>(re); if (!theSet.equals(theSet2)) { LOGGER.warning("Oups, discrepancy between old (graph-based) and " + "new (RST-based) values for getPossibleRecruiters"); LOGGER.warning("Old one is:"); for (CreatureType ct : theSet2) { LOGGER.warning("\t" + ct.getName()); } LOGGER.warning("New one is:"); for (CreatureType ct : theSet) { LOGGER.warning("\t" + ct.getName()); } } return (re); } /** * Give the number of a given recruiters needed to recruit a given * Creature. * * TODO do we need the terrain parameter * * @param recruiter The Creature that wish to recruit. * @param recruit The Creature that is to be recruited. * @param terrain String representing a terrain, in which the * recruiting occurs. * @return Number of recruiter needed. * @see net.sf.colossus.variant.CreatureType */ public static int numberOfRecruiterNeeded(CreatureType recruiter, CreatureType recruit, MasterBoardTerrain terrain, MasterHex hex) { int g_value = graph.numberOfRecruiterNeeded(recruiter.getName(), recruit.getName(), terrain, hex); int theNumber = terrain.getRecruitingSubTree() .numberOfRecruiterNeeded(recruiter, recruit, hex); if (g_value != theNumber) { LOGGER.warning("Oups, discrepancy between old (graph-based) and " + "new (RST-based) values for numberOfRecruiterNeeded ; " + "old is " + g_value + " while new is " + theNumber + " when " + recruiter.getName() + " recruits " + recruit.getName() + " in " + terrain.getId() + " on hex " + (hex != null ? hex.getLabel() : "[hex is null]")); LOGGER.warning("The RST is\n" + terrain.getRecruitingSubTree().toString()); } return g_value; } public static boolean anonymousRecruitLegal(CreatureType recruit, MasterBoardTerrain terrain, MasterHex hex) { int g_value = graph.numberOfRecruiterNeeded(Keyword_Anything, recruit.getName(), terrain, hex); if (g_value != 0) { // we really should ensure the caller *has* AnyNonLord... g_value = graph.numberOfRecruiterNeeded(Keyword_AnyNonLord, recruit.getName(), terrain, hex); } return (g_value == 0); } /** * To obtain the base amount of points needed for Titan improvement. * @return The base amount of points needed for Titan improvement. */ public int getTitanImprovementValue() { return titanImprove; } /** * To obtain the amount of points needed for Titan teleport. * @return The amount of points needed for Titan teleport. */ public int getTitanTeleportValue() { return titanTeleport; } /** * to obtain the recruit graph */ public static RecruitGraph getRecruitGraph() { return graph; } public List<AcquirableData> getAcquirablesList() { return acquirableList; } public static class NullTerrainRecruitLoader implements IVariantInitializer { private static final Logger LOGGER = Logger .getLogger(TerrainRecruitLoader.NullTerrainRecruitLoader.class .getName()); private final boolean showNullWarning; /** * Create an do-basically-Nothing TerrainRecruitLoader that can * be used as TerrainInitialiser e.g. during Unit Testing. * In real games normally a real TerrainRecruitLoader should be used, * accessed via the IVariantInitializer interface. * But the variable to hold the trl should be initialized with * something to avoid NPEs... * This one here serves that purpose, but it will then show warnings * when querying values from it. * * @param showNullWarning Set to true if you really want to use the * defaults and not get warnings about querying them. * Intended for unit testing setup. */ public NullTerrainRecruitLoader(boolean showNullWarning) { this.showNullWarning = showNullWarning; } public NullTerrainRecruitLoader() { this(true); } public List<AcquirableData> getAcquirablesList() { // TODO Auto-generated method stub return new ArrayList<AcquirableData>(); } public Collection<MasterBoardTerrain> getTerrains() { // TODO Auto-generated method stub return new ArrayList<MasterBoardTerrain>(); } public int getTitanImprovementValue() { // TODO Auto-generated method stub warnThatNullTerrainRecruitLoader("getTitanImprovementValue"); return 100; } public int getTitanTeleportValue() { // TODO Auto-generated method stub return 400; } private void warnThatNullTerrainRecruitLoader(String message) { if (showNullWarning) { LOGGER .warning("You are querying the value for " + message + " from a NullTerrainRecruitLoader. Are you sure this is what you want?"); } } } }