/**
* 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.generator;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.logging.Logger;
import net.sf.freecol.client.gui.i18n.Messages;
import net.sf.freecol.common.FreeColException;
import net.sf.freecol.common.debug.FreeColDebugger;
import net.sf.freecol.common.io.FreeColSavegameFile;
import net.sf.freecol.common.model.Ability;
import net.sf.freecol.common.model.AbstractUnit;
import net.sf.freecol.common.model.Building;
import net.sf.freecol.common.model.BuildingType;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.ColonyTile;
import net.sf.freecol.common.model.EuropeanNationType;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.GameOptions;
import net.sf.freecol.common.model.Goods;
import net.sf.freecol.common.model.GoodsType;
import net.sf.freecol.common.model.IndianNationType;
import net.sf.freecol.common.model.IndianSettlement;
import net.sf.freecol.common.model.LostCityRumour;
import net.sf.freecol.common.model.Map;
import net.sf.freecol.common.model.Map.Direction;
import net.sf.freecol.common.model.Map.Position;
import net.sf.freecol.common.model.Nation;
import net.sf.freecol.common.model.NationType;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Specification;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.TileImprovement;
import net.sf.freecol.common.model.TileImprovementType;
import net.sf.freecol.common.model.TileItemContainer;
import net.sf.freecol.common.model.TileType;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.UnitType;
import net.sf.freecol.common.option.FileOption;
import net.sf.freecol.common.option.IntegerOption;
import net.sf.freecol.common.option.MapGeneratorOptions;
import net.sf.freecol.common.option.OptionGroup;
import net.sf.freecol.common.util.RandomChoice;
import net.sf.freecol.server.FreeColServer;
import net.sf.freecol.server.model.ServerBuilding;
import net.sf.freecol.server.model.ServerColony;
import net.sf.freecol.server.model.ServerIndianSettlement;
import net.sf.freecol.server.model.ServerRegion;
import net.sf.freecol.server.model.ServerUnit;
/**
* Creates random maps and sets the starting locations for the players.
*/
public class SimpleMapGenerator implements MapGenerator {
private static final Logger logger = Logger.getLogger(SimpleMapGenerator.class.getName());
private final Random random;
private final OptionGroup mapGeneratorOptions;
private final LandGenerator landGenerator;
private final TerrainGenerator terrainGenerator;
// To avoid starting positions to be too close to the poles
// percentage indicating how much of the half map close to the pole cannot be spawned on
private static final float MIN_DISTANCE_FROM_POLE = 0.30f;
/**
* Creates a <code>MapGenerator</code>
*
* @param random The <code>Random</code> number source to use.
* @param specification a <code>Specification</code> value
* @see #createMap
*/
public SimpleMapGenerator(Random random, Specification specification) {
this.random = random;
this.mapGeneratorOptions = specification.getOptionGroup("mapGeneratorOptions");
landGenerator = new LandGenerator(mapGeneratorOptions, random);
terrainGenerator = new TerrainGenerator(mapGeneratorOptions, random);
}
/**
* Gets the approximate number of land tiles.
*
* @return The approximate number of land tiles
*/
private int getApproximateLandCount() {
return mapGeneratorOptions.getInteger("model.option.mapWidth")
* mapGeneratorOptions.getInteger("model.option.mapHeight")
* mapGeneratorOptions.getInteger("model.option.landMass")
/ 100;
}
/**
* Creates a map given for a game.
*
* @param game The <code>Game</code> to use.
* @see net.sf.freecol.server.generator.MapGenerator#createMap(net.sf.freecol.common.model.Game)
*/
public void createMap(Game game) throws FreeColException {
// Prepare imports:
final File importFile = ((FileOption) getMapGeneratorOptions()
.getOption(MapGeneratorOptions.IMPORT_FILE)).getValue();
final Game importGame;
if (importFile != null) {
Game g = null;
try {
logger.info("Importing file " + importFile.getPath());
g = FreeColServer.readGame(new FreeColSavegameFile(importFile),
game.getSpecification(), null);
} catch (IOException ioe) {
g = null;
}
importGame = g;
} else {
importGame = null;
}
// Create land map.
boolean[][] landMap;
if (importGame != null) {
landMap = LandGenerator.importLandMap(importGame);
} else {
landMap = landGenerator.createLandMap();
}
// Create terrain:
terrainGenerator.createMap(game, importGame, landMap);
Map map = game.getMap();
createIndianSettlements(map, game.getPlayers());
createEuropeanUnits(map, game.getPlayers());
createLostCityRumours(map, importGame);
}
/**
* Creates a <code>Map</code> for the given <code>Game</code>.
*
* The <code>Map</code> is added to the <code>Game</code> after
* it is created.
*
* @param game The game.
* @param landMap Determines whether there should be land
* or ocean on a given tile. This array also
* specifies the size of the map that is going
* to be created.
* @see Map
* @see TerrainGenerator#createMap
*/
public void createEmptyMap(Game game, boolean[][] landMap) {
terrainGenerator.createMap(game, null, landMap);
}
public LandGenerator getLandGenerator() {
return landGenerator;
}
public TerrainGenerator getTerrainGenerator() {
return terrainGenerator;
}
/* (non-Javadoc)
* @see net.sf.freecol.server.generator.IMapGenerator#getMapGeneratorOptions()
*/
public OptionGroup getMapGeneratorOptions() {
return mapGeneratorOptions;
}
/**
* Creates lost city rumours on the given map.
* The number of rumours depends on the map size.
*
* @param map The map to use.
* @param importGame The game to lost city rumours from.
*/
private void createLostCityRumours(Map map, Game importGame) {
final boolean importRumours = getMapGeneratorOptions().getBoolean(MapGeneratorOptions.IMPORT_RUMOURS);
if (importGame != null && importRumours) {
for (Tile importTile : importGame.getMap().getAllTiles()) {
LostCityRumour rumor = importTile.getLostCityRumour();
// no rumor
if(rumor == null){
continue;
}
final Position p = importTile.getPosition();
if (map.isValid(p)) {
final Tile t = map.getTile(p);
t.add(rumor);
}
}
} else {
int number = getApproximateLandCount() / getMapGeneratorOptions().getInteger("model.option.rumourNumber");
int counter = 0;
// TODO: Remove temporary fix:
if (importGame != null) {
number = map.getWidth() * map.getHeight() * 25 / (100 * 35);
}
// END TODO
int difficulty = map.getGame().getSpecification()
.getInteger("model.option.rumourDifficulty");
for (int i = 0; i < number; i++) {
for (int tries=0; tries<100; tries++) {
Position p = terrainGenerator.getRandomLandPosition(map, random);
Tile t = map.getTile(p);
if (t.isPolar()) continue; // No polar lost cities
if (t.isLand() && !t.hasLostCityRumour()
&& t.getSettlement() == null && t.getUnitCount() == 0) {
LostCityRumour r = new LostCityRumour(t.getGame(), t);
if (r.chooseType(null, difficulty, random)
== LostCityRumour.RumourType.MOUNDS
&& t.getOwningSettlement() != null) {
r.setType(LostCityRumour.RumourType.MOUNDS);
}
t.addLostCityRumour(r);
counter++;
break;
}
}
}
logger.info("Created " + counter
+ " lost city rumours of maximum " + number + ".");
}
}
/**
* Create the Indian settlements, at least a capital for every nation and
* random numbers of other settlements.
*
* @param map The <code>Map</code> to place the indian settlements on.
* @param players The players to create <code>Settlement</code>s
* and starting locations for. That is; both indian and
* european players. If players does not contain any indian players,
* no settlements are added.
*/
private void createIndianSettlements(final Map map, List<Player> players) {
Specification spec = map.getGame().getSpecification();
float shares = 0f;
List<IndianSettlement> settlements = new ArrayList<IndianSettlement>();
List<Player> indians = new ArrayList<Player>();
HashMap<String, Territory> territoryMap
= new HashMap<String, Territory>();
for (Player player : players) {
if (!player.isIndian()) continue;
switch (((IndianNationType) player.getNationType())
.getNumberOfSettlements()) {
case HIGH:
shares += 4;
break;
case AVERAGE:
shares += 3;
break;
case LOW:
shares += 2;
break;
}
indians.add(player);
List<String> regionNames
= ((IndianNationType) player.getNationType()).getRegionNames();
Territory territory = null;
if (regionNames == null || regionNames.isEmpty()) {
territory = new Territory(player, terrainGenerator.getRandomLandPosition(map, random));
territoryMap.put(player.getId(), territory);
} else {
for (String name : regionNames) {
if (territoryMap.get(name) == null) {
ServerRegion region = (ServerRegion) map.getRegion(name);
if (region == null) {
territory = new Territory(player, terrainGenerator.getRandomLandPosition(map, random));
} else {
territory = new Territory(player, region);
}
territoryMap.put(name, territory);
logger.fine("Allocated region " + name
+ " for " + player
+ ". Center is " + territory.getCenter()
+ ".");
break;
}
}
if (territory == null) {
logger.warning("Failed to allocate preferred region " + regionNames.get(0)
+ " for " + player.getNation());
outer: for (String name : regionNames) {
Territory otherTerritory = territoryMap.get(name);
for (String otherName : ((IndianNationType) otherTerritory.player.getNationType())
.getRegionNames()) {
if (territoryMap.get(otherName) == null) {
ServerRegion foundRegion = otherTerritory.region;
otherTerritory.region = (ServerRegion) map.getRegion(otherName);
territoryMap.put(otherName, otherTerritory);
territory = new Territory(player, foundRegion);
territoryMap.put(name, territory);
break outer;
}
}
}
if (territory == null) {
logger.warning("Unable to find free region for "
+ player.getName());
territory = new Territory(player, terrainGenerator.getRandomLandPosition(map, random));
territoryMap.put(player.getId(), territory);
}
}
}
}
if (indians.isEmpty()) return;
// Examine all the non-polar settleable tiles in a random
// order picking out as many as possible suitable tiles for
// native settlements such that can be guaranteed at least one
// layer of surrounding tiles to own.
int minSettlementDistance
= spec.getRangeOption("model.option.settlementNumber").getValue();
List<Tile> settlementTiles = new ArrayList<Tile>();
tiles: for (Tile tile : map.getAllTiles()) {
if (!tile.isPolar() && suitableForNativeSettlement(tile)) {
for (Tile t : settlementTiles) {
if (tile.getDistanceTo(t) < minSettlementDistance) {
continue tiles;
}
}
settlementTiles.add(tile);
}
}
Collections.shuffle(settlementTiles, random);
// Check number of settlements.
int settlementsToPlace = settlementTiles.size();
float share = settlementsToPlace / shares;
if (settlementTiles.size() < indians.size()) {
// TODO: something drastic to boost the settlement number
logger.warning("There are only " + settlementTiles.size()
+ " settlement sites."
+ " This is smaller than " + indians.size()
+ " the number of tribes.");
}
// Find the capitals
List<Territory> territories
= new ArrayList<Territory>(territoryMap.values());
int settlementsPlaced = 0;
for (Territory territory : territories) {
switch (((IndianNationType) territory.player.getNationType())
.getNumberOfSettlements()) {
case HIGH:
territory.numberOfSettlements = Math.round(4 * share);
break;
case AVERAGE:
territory.numberOfSettlements = Math.round(3 * share);
break;
case LOW:
territory.numberOfSettlements = Math.round(2 * share);
break;
}
int radius = territory.player.getNationType().getCapitalType().getClaimableRadius();
ArrayList<Tile> capitalTiles = new ArrayList<Tile>(settlementTiles);
while (!capitalTiles.isEmpty()) {
Tile tile = getClosestTile(territory.getCenter(),
capitalTiles);
capitalTiles.remove(tile);
// Choose this tile if it is free and half the expected tile
// claim can succeed (preventing capitals on small islands).
if (map.getClaimableTiles(territory.player, tile, radius).size()
>= (2 * radius + 1) * (2 * radius + 1) / 2) {
String name = (territory.region == null) ? "default region"
: territory.region.getNameKey();
logger.fine("Placing the " + territory.player
+ " capital in region: " + name
+ " at Tile: "+ tile.getPosition());
settlements.add(placeIndianSettlement(territory.player,
true, tile.getPosition(), map));
territory.numberOfSettlements--;
territory.position = tile.getPosition();
settlementTiles.remove(tile);
settlementsPlaced++;
break;
}
}
}
// Sort tiles from the edges of the map inward
Collections.sort(settlementTiles, new Comparator<Tile>() {
public int compare(Tile tile1, Tile tile2) {
int distance1 = Math.min(Math.min(tile1.getX(), map.getWidth() - tile1.getX()),
Math.min(tile1.getY(), map.getHeight() - tile1.getY()));
int distance2 = Math.min(Math.min(tile2.getX(), map.getWidth() - tile2.getX()),
Math.min(tile2.getY(), map.getHeight() - tile2.getY()));
return (distance1 - distance2);
}
});
// Now place other settlements
while (!settlementTiles.isEmpty() && !territories.isEmpty()) {
Tile tile = settlementTiles.remove(0);
if (tile.getOwner() != null) continue; // No close overlap
Territory territory = getClosestTerritory(tile, territories);
int radius = territory.player.getNationType().getSettlementType(false)
.getClaimableRadius();
// Insist that the settlement can not be linear
if (map.getClaimableTiles(territory.player, tile, radius).size()
> 2 * radius + 1) {
String name = (territory.region == null) ? "default region"
: territory.region.getNameKey();
logger.fine("Placing a " + territory.player
+ " camp in region: " + name
+ " at Tile: " + tile.getPosition());
settlements.add(placeIndianSettlement(territory.player,
false, tile.getPosition(), map));
settlementsPlaced++;
territory.numberOfSettlements--;
if (territory.numberOfSettlements <= 0) {
territories.remove(territory);
}
}
}
// Grow some more tiles.
// TODO: move the magic numbers below to the spec RSN
// Also collect the skills provided
HashMap<UnitType, List<IndianSettlement>> skills
= new HashMap<UnitType, List<IndianSettlement>>();
Collections.shuffle(settlements, random);
for (IndianSettlement is : settlements) {
List<Tile> tiles = new ArrayList<Tile>();
for (Tile tile : is.getOwnedTiles()) {
for (Tile t : tile.getSurroundingTiles(1)) {
if (t.getOwningSettlement() == null) {
tiles.add(tile);
break;
}
}
}
Collections.shuffle(tiles, random);
int minGrow = is.getType().getMinimumGrowth();
int maxGrow = is.getType().getMaximumGrowth();
if (maxGrow > minGrow) {
for (int i = random.nextInt(maxGrow - minGrow) + minGrow;
i > 0; i--) {
Tile tile = findFreeNeighbouringTile(is, tiles, random);
if (tile == null) break;
tile.changeOwnership(is.getOwner(), is);
tiles.add(tile);
}
}
// Collect settlements by skill
UnitType skill = is.getLearnableSkill();
List<IndianSettlement> isList = skills.get(skill);
if (isList == null) {
isList = new ArrayList<IndianSettlement>();
isList.add(is);
skills.put(skill, isList);
} else {
isList.add(is);
}
}
// Require that there be experts for all the new world goods types.
// Collect the list of needed experts
List<UnitType> expertsNeeded = new ArrayList<UnitType>();
for (GoodsType goodsType : spec.getNewWorldGoodsTypeList()) {
UnitType expert = spec.getExpertForProducing(goodsType);
if (!skills.containsKey(expert)) expertsNeeded.add(expert);
}
// Extract just the settlement lists.
List<List<IndianSettlement>> isList
= new ArrayList<List<IndianSettlement>>(skills.values());
Comparator<List<IndianSettlement>> listComparator
= new Comparator<List<IndianSettlement>>() {
public int compare(List<IndianSettlement> l1,
List<IndianSettlement> l2) {
return l2.size() - l1.size();
}
};
// For each missing skill...
while (!expertsNeeded.isEmpty()) {
UnitType neededSkill = expertsNeeded.remove(0);
Collections.sort(isList, listComparator);
List<IndianSettlement> extras = isList.remove(0);
UnitType extraSkill = extras.get(0).getLearnableSkill();
List<RandomChoice<IndianSettlement>> choices
= new ArrayList<RandomChoice<IndianSettlement>>();
// ...look at the settlements with the most common skill
// with a bit of favoritism to capitals as the needed skill
// is so rare,...
for (IndianSettlement is : extras) {
IndianNationType nation
= (IndianNationType) is.getOwner().getNationType();
int cm = (is.isCapital()) ? 2 : 1;
RandomChoice<IndianSettlement> rc = null;
for (RandomChoice<UnitType> c : nation.generateSkillsForTile(is.getTile())) {
if (c.getObject() == neededSkill) {
rc = new RandomChoice<IndianSettlement>(is, c.getProbability() * cm);
break;
}
}
choices.add((rc != null) ? rc
: new RandomChoice<IndianSettlement>(is, 1));
}
if (!choices.isEmpty()) {
// ...and pick one that could do the missing job.
IndianSettlement chose
= RandomChoice.getWeightedRandom(logger, "expert", random,
choices);
logger.finest("At " + chose.getName()
+ " replaced " + extraSkill
+ " (one of " + extras.size() + ")"
+ " by missing " + neededSkill);
chose.setLearnableSkill(neededSkill);
extras.remove(chose);
isList.add(0, extras); // Try to stay well sorted
List<IndianSettlement> neededList
= new ArrayList<IndianSettlement>();
neededList.add(chose);
isList.add(neededList);
} else { // `can not happen'
logger.finest("Game is missing skill: " + neededSkill);
}
}
String msg = "Settlement skills:";
for (List<IndianSettlement> iss : isList) {
if (iss.isEmpty()) {
msg += " 0 x <none>";
} else {
msg += " " + iss.size() + " x " + iss.get(0).getLearnableSkill();
}
}
logger.info(msg);
logger.info("Created " + settlementsPlaced
+ " Indian settlements of maximum " + settlementsToPlace);
}
/**
* Is a tile suitable for a native settlement?
* Require the tile be settleable, and at least half its neighbours
* also be settleable. TODO: degrade the second test to usability,
* but fix this when the natives-use-water situation is sorted.
*
* @param tile The <code>Tile</code> to examine.
* @return True if this tile is suitable.
*/
private boolean suitableForNativeSettlement(Tile tile) {
if (!tile.getType().canSettle()) return false;
int good = 0, n = 0;
for (Tile t : tile.getSurroundingTiles(1)) {
if (t.getType().canSettle()) good++;
n++;
}
return good >= n / 2;
}
private Tile findFreeNeighbouringTile(IndianSettlement is,
List<Tile> tiles, Random random) {
for (Tile tile : tiles) {
for (Direction d : Direction.getRandomDirections("freeTile", random)) {
Tile t = tile.getNeighbourOrNull(d);
if ((t != null)
&& (t.getOwningSettlement() == null)
&& (is.getOwner().canClaimForSettlement(t))) return t;
}
}
return null;
}
private Tile getClosestTile(Position center, List<Tile> tiles) {
Tile result = null;
int minimumDistance = Integer.MAX_VALUE;
for (Tile tile : tiles) {
int distance = tile.getPosition().getDistance(center);
if (distance < minimumDistance) {
minimumDistance = distance;
result = tile;
}
}
return result;
}
private Territory getClosestTerritory(Tile tile, List<Territory> territories) {
Territory result = null;
int minimumDistance = Integer.MAX_VALUE;
for (Territory territory : territories) {
int distance = tile.getPosition().getDistance(territory.getCenter());
if (distance < minimumDistance) {
minimumDistance = distance;
result = territory;
}
}
return result;
}
/**
* Builds a <code>IndianSettlement</code> at the given position.
*
* @param player The player owning the new settlement.
* @param capital <code>true</code> if the settlement should be a
* {@link IndianSettlement#isCapital() capital}.
* @param position The position to place the settlement.
* @param map The map that should get a new settlement.
* @return The <code>IndianSettlement</code> just being placed
* on the map.
*/
private IndianSettlement placeIndianSettlement(Player player,
boolean capital, Position position, Map map) {
final Tile tile = map.getTile(position);
String name = (capital) ? player.getCapitalName(random)
: player.getSettlementName(random);
UnitType skill
= generateSkillForLocation(map, tile, player.getNationType());
IndianSettlement settlement
= new ServerIndianSettlement(map.getGame(), player, name, tile,
capital, skill,
new HashSet<Player>(), null);
player.addSettlement(settlement);
logger.fine("Generated skill: " + settlement.getLearnableSkill());
int low = settlement.getType().getMinimumSize();
int high = settlement.getType().getMaximumSize();
int unitCount = low + random.nextInt(high - low);
for (int i = 0; i < unitCount; i++) {
UnitType unitType = map.getSpecification().getUnitType("model.unit.brave");
Unit unit = new ServerUnit(map.getGame(), settlement, player,
unitType, unitType.getDefaultEquipment());
unit.setIndianSettlement(settlement);
if (i == 0) {
unit.setLocation(tile);
} else {
unit.setLocation(settlement);
}
}
settlement.placeSettlement(true);
if (FreeColDebugger.getDebugLevel() >= FreeColDebugger.DEBUG_FULL) {
for (GoodsType type : map.getSpecification().getGoodsTypeList()) {
if (type.isNewWorldGoodsType()) settlement.addGoods(type, 150);
}
}
return settlement;
}
/**
* Generates a skill that could be taught from a settlement on the given Tile.
*
* @param map The <code>Map</code>.
* @param tile The tile where the settlement will be located.
* @return A skill that can be taught to Europeans.
*/
private UnitType generateSkillForLocation(Map map, Tile tile, NationType nationType) {
List<RandomChoice<UnitType>> skills = ((IndianNationType) nationType).getSkills();
java.util.Map<GoodsType, Integer> scale = new HashMap<GoodsType, Integer>();
for (RandomChoice<UnitType> skill : skills) {
scale.put(skill.getObject().getExpertProduction(), 1);
}
for (Tile t: tile.getSurroundingTiles(1)) {
for (GoodsType goodsType : scale.keySet()) {
scale.put(goodsType, scale.get(goodsType).intValue() + t.potential(goodsType, null));
}
}
List<RandomChoice<UnitType>> scaledSkills = new ArrayList<RandomChoice<UnitType>>();
for (RandomChoice<UnitType> skill : skills) {
UnitType unitType = skill.getObject();
int scaleValue = scale.get(unitType.getExpertProduction()).intValue();
scaledSkills.add(new RandomChoice<UnitType>(unitType, skill.getProbability() * scaleValue));
}
UnitType skill = RandomChoice.getWeightedRandom(null, null,
random, scaledSkills);
if (skill == null) {
// Seasoned Scout
List<UnitType> unitList = map.getSpecification().getUnitTypesWithAbility(Ability.EXPERT_SCOUT);
return unitList.get(random.nextInt(unitList.size()));
} else {
return skill;
}
}
/**
* Create two ships, one with a colonist, for each player, and
* select suitable starting positions.
*
* @param map The <code>Map</code> to place the european units on.
* @param players The players to create <code>Settlement</code>s
* and starting locations for. That is; both indian and
* european players.
*/
private void createEuropeanUnits(Map map, List<Player> players) {
Game game = map.getGame();
Specification spec = game.getSpecification();
final int width = map.getWidth();
final int height = map.getHeight();
final int poleDistance = (int)(MIN_DISTANCE_FROM_POLE*height/2);
List<Player> europeanPlayers = new ArrayList<Player>();
for (Player player : players) {
if (player.isREF()) {
// eastern edge of the map
int x = width - 2;
// random latitude, not too close to the pole
int y = random.nextInt(height - 2*poleDistance) + poleDistance;
player.setEntryLocation(map.getTile(x, y));
continue;
}
if (player.isEuropean()) {
europeanPlayers.add(player);
logger.finest("found European player " + player);
}
}
List<Position> positions = generateStartingPositions(map, europeanPlayers);
List<Tile> startingTiles = new ArrayList<Tile>();
for (int index = 0; index < europeanPlayers.size(); index++) {
Player player = europeanPlayers.get(index);
Position position = positions.get(index);
logger.fine("generating units for player " + player);
List<Unit> carriers = new ArrayList<Unit>();
List<Unit> passengers = new ArrayList<Unit>();
List<AbstractUnit> unitList = ((EuropeanNationType) player.getNationType())
.getStartingUnits();
for (AbstractUnit startingUnit : unitList) {
UnitType type = startingUnit.getUnitType(spec);
Unit newUnit = new ServerUnit(game, null, player, type,
startingUnit.getEquipment(spec));
newUnit.setName(player.getUnitName(type, random));
if (newUnit.isNaval()) {
if (newUnit.canCarryUnits()) {
newUnit.setState(Unit.UnitState.ACTIVE);
carriers.add(newUnit);
}
} else {
newUnit.setState(Unit.UnitState.SENTRY);
passengers.add(newUnit);
}
}
boolean startAtSea = true;
if (carriers.isEmpty()) {
logger.warning("No carriers defined for player " + player);
startAtSea = false;
}
Tile startTile = null;
int x = position.getX();
int y = position.getY();
for (int i = 0; i < 2 * map.getHeight(); i++) {
int offset = (i % 2 == 0) ? i / 2 : -(1 + i / 2);
int row = y + offset;
if (row < 0 || row >= map.getHeight()) continue;
startTile = findTileFor(map, row, x, startAtSea);
if (startTile != null) {
if (startingTiles.contains(startTile)) {
startTile = null;
} else {
startingTiles.add(startTile);
break;
}
}
}
if (startTile == null) {
String err = "Failed to find start tile "
+ ((startAtSea) ? "at sea" : "on land")
+ " for player " + player
+ " from (" + x + "," + y + ")"
+ " avoiding:";
for (Tile t : startingTiles) err += " " + t.toString();
err += " with map: ";
for (int xx = 0; xx < map.getWidth(); xx++) {
err += map.getTile(xx, y);
}
throw new RuntimeException(err);
}
startTile.setExploredBy(player, true);
player.setEntryLocation(startTile);
if (startAtSea) {
for (Unit carrier : carriers) {
carrier.setLocation(startTile);
}
passengers: for (Unit unit : passengers) {
for (Unit carrier : carriers) {
if (carrier.canAdd(unit)) {
unit.setLocation(carrier);
continue passengers;
}
}
// no space left on carriers
unit.setLocation(player.getEurope());
}
} else {
for (Unit unit : passengers) {
unit.setLocation(startTile);
}
}
if (FreeColDebugger.getDebugLevel() >= FreeColDebugger.DEBUG_FULL) {
createDebugUnits(map, player, startTile);
IntegerOption op = spec.getIntegerOption(GameOptions.STARTING_MONEY);
if (op != null) op.setValue(10000);
}
}
}
private Tile findTileFor(Map map, int row, int start, boolean startAtSea) {
Tile tile = null;
Tile seas = null;
int offset = (start == 0) ? 1 : -1;
for (int x = start; 0 <= x && x < map.getWidth(); x += offset) {
tile = map.getTile(x, row);
if (tile.isDirectlyHighSeasConnected()) {
seas = tile;
} else if (tile.isLand()) {
if (startAtSea) {
if (seas == null) {
logger.warning("No high seas in row " + row);
}
return seas;
}
return tile;
}
}
logger.warning("No land in row " + row);
return null;
}
private void createDebugUnits(Map map, Player player, Tile startTile) {
Game game = map.getGame();
Specification spec = game.getSpecification();
// In debug mode give each player a few more units and a colony.
UnitType unitType = spec.getUnitType("model.unit.galleon");
Unit unit4 = new ServerUnit(game, startTile, player, unitType);
unitType = spec.getUnitType("model.unit.privateer");
@SuppressWarnings("unused")
Unit privateer = new ServerUnit(game, startTile, player, unitType);
unitType = spec.getUnitType("model.unit.freeColonist");
@SuppressWarnings("unused")
Unit unit5 = new ServerUnit(game, unit4, player, unitType);
unitType = spec.getUnitType("model.unit.veteranSoldier");
@SuppressWarnings("unused")
Unit unit6 = new ServerUnit(game, unit4, player, unitType);
unitType = spec.getUnitType("model.unit.jesuitMissionary");
@SuppressWarnings("unused")
Unit unit7 = new ServerUnit(game, unit4, player, unitType);
Tile colonyTile = null;
Iterator<Position> cti
= map.getFloodFillIterator(startTile.getPosition());
while (cti.hasNext()) {
Tile tempTile = map.getTile(cti.next());
if (tempTile.isPolar()) {
// do not place the initial colony at the pole
continue;
}
if (player.canClaimToFoundSettlement(tempTile)) {
colonyTile = tempTile;
break;
}
}
if (colonyTile == null) {
logger.warning("Could not find a debug colony site.");
return;
}
for (TileType t : spec.getTileTypeList()) {
if (!t.isWater()) {
colonyTile.setType(t);
break;
}
}
unitType = spec.getUnitType("model.unit.expertFarmer");
Unit buildColonyUnit = new ServerUnit(game, colonyTile,
player, unitType);
String colonyName = Messages.message(player.getNationName())
+ " Colony";
Colony colony = new ServerColony(game, player, colonyName, colonyTile);
player.addSettlement(colony);
colony.placeSettlement(true);
for (Tile tile : colonyTile.getSurroundingTiles(1)) {
if (tile.getSettlement() == null
&& (tile.getOwner() == null
|| !tile.getOwner().isEuropean())) {
tile.changeOwnership(player, colony);
if (tile.hasLostCityRumour()) {
tile.removeLostCityRumour();
}
}
}
buildColonyUnit.setLocation(colony);
if (buildColonyUnit.getLocation() instanceof ColonyTile) {
Tile ct = ((ColonyTile) buildColonyUnit.getLocation()).getWorkTile();
for (TileType t : spec.getTileTypeList()) {
if (!t.isWater()) {
ct.setType(t);
TileImprovementType plowType = map.getSpecification()
.getTileImprovementType("model.improvement.plow");
TileImprovementType roadType = map.getSpecification()
.getTileImprovementType("model.improvement.road");
TileImprovement road = new TileImprovement(game, ct, roadType);
road.setTurnsToComplete(0);
TileImprovement plow = new TileImprovement(game, ct, plowType);
plow.setTurnsToComplete(0);
ct.setTileItemContainer(new TileItemContainer(game, ct));
ct.getTileItemContainer().addTileItem(road);
ct.getTileItemContainer().addTileItem(plow);
break;
}
}
}
BuildingType schoolType = spec.getBuildingType("model.building.schoolhouse");
Building schoolhouse = new ServerBuilding(game, colony, schoolType);
colony.addBuilding(schoolhouse);
unitType = spec.getUnitType("model.unit.masterCarpenter");
Unit carpenter = new ServerUnit(game, colonyTile, player, unitType);
carpenter.setLocation(colony.getBuildingForProducing(unitType.getExpertProduction()));
unitType = spec.getUnitType("model.unit.elderStatesman");
Unit statesman = new ServerUnit(game, colonyTile, player, unitType);
statesman.setLocation(colony.getBuildingForProducing(unitType.getExpertProduction()));
unitType = spec.getUnitType("model.unit.expertLumberJack");
Unit lumberjack = new ServerUnit(game, colony, player, unitType);
if (lumberjack.getLocation() instanceof ColonyTile) {
Tile lt = ((ColonyTile) lumberjack.getLocation()).getWorkTile();
for (TileType t : spec.getTileTypeList()) {
if (t.isForested()) {
lt.setType(t);
break;
}
}
lumberjack.setWorkType(lumberjack.getType().getExpertProduction());
}
unitType = spec.getUnitType("model.unit.seasonedScout");
@SuppressWarnings("unused")
Unit scout = new ServerUnit(game, colonyTile, player, unitType);
unitType = spec.getUnitType("model.unit.veteranSoldier");
@SuppressWarnings("unused")
Unit unit8 = new ServerUnit(game, colonyTile, player, unitType);
@SuppressWarnings("unused")
Unit unit9 = new ServerUnit(game, colonyTile, player, unitType);
unitType = spec.getUnitType("model.unit.artillery");
@SuppressWarnings("unused")
Unit unit10 = new ServerUnit(game, colonyTile, player, unitType);
@SuppressWarnings("unused")
Unit unit11 = new ServerUnit(game, colonyTile, player, unitType);
@SuppressWarnings("unused")
Unit unit12 = new ServerUnit(game, colonyTile, player, unitType);
unitType = spec.getUnitType("model.unit.treasureTrain");
Unit unit13 = new ServerUnit(game, colonyTile, player, unitType);
unit13.setTreasureAmount(10000);
unitType = spec.getUnitType("model.unit.wagonTrain");
Unit unit14 = new ServerUnit(game, colonyTile, player, unitType);
GoodsType cigarsType = spec.getGoodsType("model.goods.cigars");
Goods cigards = new Goods(game, unit14, cigarsType, 5);
unit14.add(cigards);
unitType = spec.getUnitType("model.unit.jesuitMissionary");
@SuppressWarnings("unused")
Unit unit15 = new ServerUnit(game, colonyTile, player, unitType);
@SuppressWarnings("unused")
Unit unit16 = new ServerUnit(game, colonyTile, player, unitType);
// END DEBUG
}
private List<Position> generateStartingPositions(Map map, List<Player> players) {
int number = players.size();
List<Position> positions = new ArrayList<Position>(number);
if (number > 0) {
int west = 0;
int east = map.getWidth() - 1;
switch(map.getSpecification().getInteger(GameOptions.STARTING_POSITIONS)) {
case GameOptions.STARTING_POSITIONS_CLASSIC:
int distance = map.getHeight() / number;
int row = distance/2;
for (int index = 0; index < number; index++) {
positions.add(new Position(east, row));
row += distance;
}
Collections.shuffle(positions);
break;
case GameOptions.STARTING_POSITIONS_RANDOM:
distance = 2 * map.getHeight() / number;
row = distance/2;
for (int index = 0; index < number; index++) {
if (index % 2 == 0) {
positions.add(new Position(east, row));
} else {
positions.add(new Position(west, row));
row += distance;
}
}
Collections.shuffle(positions);
break;
case GameOptions.STARTING_POSITIONS_HISTORICAL:
for (Player player : players) {
Nation nation = player.getNation();
positions.add(new Position(nation.startsOnEastCoast() ? east : west,
map.getRow(nation.getPreferredLatitude())));
}
break;
}
}
return positions;
}
private class Territory {
public ServerRegion region;
public Position position;
public Player player;
public int numberOfSettlements;
public Territory(Player player, Position position) {
this.player = player;
this.position = position;
}
public Territory(Player player, ServerRegion region) {
this.player = player;
this.region = region;
}
public Position getCenter() {
if (position == null) {
return region.getCenter();
} else {
return position;
}
}
public String toString() {
return player + " territory at " + region.toString();
}
}
}