/** * 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.common.model; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; 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.util.RandomChoice; /** * Represents a lost city rumour. */ public class LostCityRumour extends TileItem { private static final Logger logger = Logger.getLogger(LostCityRumour.class.getName()); /** * The type of the rumour. A RumourType, or null if the type has * not yet been determined. */ private RumourType type = null; /** * The name of this rumour, or null, if it has none. Rumours such * as the Seven Cities of Gold and Fountains of Youth may have * individual names. */ private String name = null; /** Constants describing types of Lost City Rumours. */ public static enum RumourType { NO_SUCH_RUMOUR, BURIAL_GROUND, EXPEDITION_VANISHES, NOTHING, LEARN, TRIBAL_CHIEF, COLONIST, MOUNDS, RUINS, CIBOLA, FOUNTAIN_OF_YOUTH } /** * Creates a new <code>LostCityRumour</code> instance. * * @param game a <code>Game</code> value * @param tile a <code>Tile</code> value */ public LostCityRumour(Game game, Tile tile) { super(game, tile); } /** * Creates a new <code>LostCityRumour</code> instance. * * @param game a <code>Game</code> value * @param tile a <code>Tile</code> value * @param type a <code>RumourType</code> value * @param name a <code>String</code> value */ public LostCityRumour(Game game, Tile tile, RumourType type, String name) { super(game, tile); this.type = type; this.name = name; } /** * Creates a new <code>LostCityRumour</code> instance. * * @param game a <code>Game</code> value * @param in a <code>XMLStreamReader</code> value * @exception XMLStreamException if an error occurs */ public LostCityRumour(Game game, XMLStreamReader in) throws XMLStreamException { super(game, in); readFromXML(in); } /** * Get the <code>Type</code> value. * * @return a <code>RumourType</code> value */ public final RumourType getType() { return type; } /** * Set the <code>Type</code> value. * * @param newType The new Type value. */ public final void setType(final RumourType newType) { this.type = newType; } /** * Get the <code>Name</code> value. * * @return a <code>String</code> value */ public final String getName() { return name; } /** * Set the <code>Name</code> value. * * @param newName The new Name value. */ public final void setName(final String newName) { this.name = newName; } /** * Get the <code>ZIndex</code> value. * * @return an <code>int</code> value */ public final int getZIndex() { return RUMOUR_ZINDEX; } /** * {@inheritDoc} */ public boolean isTileTypeAllowed(TileType tileType) { return !tileType.isWater(); } /** * Chooses a type of Lost City Rumour. The type of rumour depends on the * exploring unit, as well as player settings. * * @param unit The <code>Unit</code> exploring (optional). * @param difficulty The difficulty level. * @param random A random number source. * @return The type of rumour. * * TODO: Make RumourType a FreeColGameObjectType and move all the * magic numbers in here to the specification. */ public RumourType chooseType(Unit unit, int difficulty, Random random) { Tile tile = getTile(); boolean allowLearn = unit != null && !unit.getType().getUnitTypesLearntInLostCity().isEmpty(); boolean isExpertScout = unit != null && unit.hasAbility(Ability.EXPERT_SCOUT) && unit.hasAbility("model.ability.scoutIndianSettlement"); boolean hasDeSoto = unit != null && unit.getOwner().hasAbility("model.ability.rumoursAlwaysPositive"); // The following arrays contain percentage values for // "good" and "bad" events when scouting with a non-expert // at the various difficulty levels [0..4] exact values // but generally "bad" should increase, "good" decrease final int BAD_EVENT_PERCENTAGE[] = { 11, 17, 23, 30, 37 }; final int GOOD_EVENT_PERCENTAGE[] = { 75, 62, 48, 33, 17 }; // remaining to 100, event NOTHING: 14, 21, 29, 37, 46 // The following arrays contain the modifiers applied when // expert scout is at work exact values; modifiers may // look slightly "better" on harder levels since we're // starting from a "worse" percentage. final int BAD_EVENT_MOD[] = { -6, -7, -7, -8, -9 }; final int GOOD_EVENT_MOD[] = { 14, 15, 16, 18, 20 }; // The scouting outcome is based on three factors: level, // expert scout or not, DeSoto or not. Based on this, we // are going to calculate probabilites for neutral, bad // and good events. int percentNeutral; int percentBad; int percentGood; difficulty = Math.max(0, Math.min(BAD_EVENT_PERCENTAGE.length-1, difficulty)); if (hasDeSoto) { percentBad = 0; percentGood = 100; percentNeutral = 0; } else { // First, get "basic" percentages percentBad = BAD_EVENT_PERCENTAGE[difficulty]; percentGood = GOOD_EVENT_PERCENTAGE[difficulty]; // Second, apply ExpertScout bonus if necessary if (isExpertScout) { percentBad += BAD_EVENT_MOD[difficulty]; percentGood += GOOD_EVENT_MOD[difficulty]; } // Third, get a value for the "neutral" percentage, // unless the other values exceed 100 already if (percentBad + percentGood < 100) { percentNeutral = 100 - percentBad - percentGood; } else { percentNeutral = 0; } } Map<RumourType, Integer> events = new EnumMap<RumourType, Integer>(RumourType.class); // The NEUTRAL events.put(RumourType.NOTHING, 100 * percentNeutral); // The BAD // If the tile is native-owned, allow burial grounds rumour. if (tile.getOwner() != null && tile.getOwner().isIndian()) { events.put(RumourType.EXPEDITION_VANISHES, 75 * percentBad); events.put(RumourType.BURIAL_GROUND, 25 * percentBad); } else { events.put(RumourType.EXPEDITION_VANISHES, 100 * percentBad); events.put(RumourType.BURIAL_GROUND, 0); } // The GOOD if (allowLearn) { // if the unit can learn events.put(RumourType.LEARN, 30 * percentGood); events.put(RumourType.TRIBAL_CHIEF, 30 * percentGood); events.put(RumourType.COLONIST, 20 * percentGood); } else { events.put(RumourType.LEARN, 0); events.put(RumourType.TRIBAL_CHIEF, 50 * percentGood); events.put(RumourType.COLONIST, 30 * percentGood); } // The SPECIAL // Right now, these are considered "good" events that happen randomly. events.put(RumourType.MOUNDS, 8 * percentGood); events.put(RumourType.RUINS, 6 * percentGood); events.put(RumourType.CIBOLA, 4 * percentGood); events.put(RumourType.FOUNTAIN_OF_YOUTH, 3 * percentGood); // Add all possible events to a RandomChoice List List<RandomChoice<RumourType>> choices = new ArrayList<RandomChoice<RumourType>>(); for (Entry<RumourType, Integer> entry : events.entrySet()) { if (entry.getValue() > 0) { choices.add(new RandomChoice<RumourType>(entry.getKey(), entry.getValue())); } } return RandomChoice.getWeightedRandom(logger, "Choose rumour", random, choices); } /** * This method writes an XML-representation of this object to the given * stream. * * <br> * <br> * * Only attributes visible to the given <code>Player</code> will be added * to that representation if <code>showAll</code> is set to * <code>false</code>. * * @param out The target stream. * @param player The <code>Player</code> this XML-representation should be * made for, or <code>null</code> if * <code>showAll == true</code>. * @param showAll Only attributes visible to <code>player</code> will be * added to the representation if <code>showAll</code> is set * to <i>false</i>. * @param toSavedGame If <code>true</code> then information that is only * needed when saving a game is added. * @throws XMLStreamException if there are any problems writing to the * stream. */ @Override protected void toXMLImpl(XMLStreamWriter out, Player player, boolean showAll, boolean toSavedGame) throws XMLStreamException { // Start element: out.writeStartElement(getXMLElementTagName()); // Add attributes: out.writeAttribute(ID_ATTRIBUTE, getId()); out.writeAttribute("tile", getTile().getId()); if (type != null && (showAll || toSavedGame)) { out.writeAttribute("type", getType().toString()); } if (name != null && (showAll || toSavedGame)) { out.writeAttribute("name", name); } // End element: out.writeEndElement(); } /** * Initialize this object from an XML-representation of this object. * * @param in The input stream with the XML. * @throws XMLStreamException if a problem was encountered during parsing. */ @Override protected void readFromXMLImpl(XMLStreamReader in) throws XMLStreamException { setId(in.getAttributeValue(null, ID_ATTRIBUTE)); tile = getFreeColGameObject(in, "tile", Tile.class); String typeString = getAttribute(in, "type", null); if (typeString != null) { type = Enum.valueOf(RumourType.class, typeString); } name = getAttribute(in, "name", null); in.nextTag(); } /** * Returns the tag name of the root element representing this object. * * @return "lostCityRumour". */ public static String getXMLElementTagName() { return "lostCityRumour"; } }