wesnoth/src/map.cpp
Philippe Plantier 1f249a3dc6 Fixed bug / feature request #9480:
changing a terrain using the [terrain] event also changes the map
border.
2004-08-05 21:53:31 +00:00

450 lines
11 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "game.hpp"
#include "map.hpp"
#include "pathfind.hpp"
#include "util.hpp"
#include <algorithm>
#include <cassert>
#include <cctype>
#include <cstdlib>
#include <iostream>
#include <sstream>
gamemap::location gamemap::location::null_location;
const std::string& gamemap::terrain_name(gamemap::TERRAIN terrain) const
{
static const std::string default_val;
const std::map<TERRAIN,terrain_type>::const_iterator i =
letterToTerrain_.find(terrain);
if(i == letterToTerrain_.end())
return default_val;
else
return i->second.name();
}
std::vector<std::string> gamemap::underlying_terrain_name(gamemap::TERRAIN terrain) const
{
const std::map<TERRAIN,terrain_type>::const_iterator i =
letterToTerrain_.find(terrain);
if(i == letterToTerrain_.end()) {
return std::vector<std::string>();
} else {
if(i->second.is_alias()) {
std::vector<std::string> res;
const std::string& type = i->second.type();
for(std::string::const_iterator j = type.begin(); j != type.end(); ++j) {
res.push_back(terrain_name(*j));
}
return res;
} else {
return std::vector<std::string>(1,i->second.name());
}
}
}
const std::string& gamemap::underlying_terrain(TERRAIN terrain) const
{
const std::map<TERRAIN,terrain_type>::const_iterator i = letterToTerrain_.find(terrain);
if(i == letterToTerrain_.end()) {
static std::string res;
res.resize(1);
res[0] = terrain;
return res;
} else {
return i->second.type();
}
}
bool gamemap::is_village(gamemap::TERRAIN terrain) const
{
return get_terrain_info(terrain).is_village();
}
bool gamemap::gives_healing(gamemap::TERRAIN terrain) const
{
return get_terrain_info(terrain).gives_healing();
}
bool gamemap::is_castle(gamemap::TERRAIN terrain) const
{
return get_terrain_info(terrain).is_castle();
}
bool gamemap::is_keep(gamemap::TERRAIN terrain) const
{
return get_terrain_info(terrain).is_keep();
}
bool gamemap::is_village(const gamemap::location& loc) const
{
return on_board(loc) && is_village(get_terrain(loc));
}
bool gamemap::gives_healing(const gamemap::location& loc) const
{
return is_village(loc);
}
bool gamemap::is_castle(const gamemap::location& loc) const
{
return on_board(loc) && is_castle(get_terrain(loc));
}
bool gamemap::is_keep(const gamemap::location& loc) const
{
return on_board(loc) && is_keep(get_terrain(loc));
}
gamemap::location::location(const config& cfg) : x(-1), y(-1)
{
const std::string& xstr = cfg["x"];
const std::string& ystr = cfg["y"];
//the co-ordinates in config files will be 1-based, while we
//want them as 0-based
if(xstr.empty() == false)
x = atoi(xstr.c_str()) - 1;
if(ystr.empty() == false)
y = atoi(ystr.c_str()) - 1;
}
void gamemap::location::write(config& cfg) const
{
char buf[50];
sprintf(buf,"%d",x+1);
cfg["x"] = buf;
sprintf(buf,"%d",y+1);
cfg["y"] = buf;
}
gamemap::location gamemap::location::operator-() const
{
location ret;
ret.x = -x;
ret.y = -y;
return ret;
}
gamemap::location gamemap::location::operator+(const gamemap::location& a) const
{
gamemap::location ret = *this;
ret += a;
return ret;
}
gamemap::location& gamemap::location::operator+=(const gamemap::location &a)
{
bool parity = (x & 1) != 0;
x += a.x;
y += a.y;
if((a.x > 0) && (a.x % 2) && parity)
y++;
if((a.x < 0) && (a.x % 2) && !parity)
y--;
return *this;
}
gamemap::location gamemap::location::operator-(const gamemap::location &a) const
{
return operator+(-a);
}
gamemap::location& gamemap::location::operator-=(const gamemap::location &a)
{
return operator+=(-a);
}
gamemap::location gamemap::location::get_direction(
gamemap::location::DIRECTION dir) const
{
switch(dir) {
case NORTH: return gamemap::location(x,y-1);
case NORTH_EAST: return gamemap::location(x+1,y-is_even(x));
case SOUTH_EAST: return gamemap::location(x+1,y+is_odd(x));
case SOUTH: return gamemap::location(x,y+1);
case SOUTH_WEST: return gamemap::location(x-1,y+is_odd(x));
case NORTH_WEST: return gamemap::location(x-1,y-is_even(x));
default:
assert(false);
return gamemap::location();
}
}
gamemap::gamemap(const config& cfg, const std::string& data) : tiles_(1)
{
std::cerr << "loading map: '" << data << "'\n";
const config::child_list& terrains = cfg.get_children("terrain");
create_terrain_maps(terrains,terrainPrecedence_,letterToTerrain_,terrain_);
read(data);
}
void gamemap::read(const std::string& data)
{
tiles_.clear();
villages_.clear();
std::fill(startingPositions_,startingPositions_+sizeof(startingPositions_)/sizeof(*startingPositions_),location());
size_t x = 0, y = 0;
for(std::string::const_iterator i = data.begin(); i != data.end(); ++i) {
char c = *i;
if(c == '\r') {
continue;
} if(c == '\n') {
x = 0;
++y;
} else {
if(letterToTerrain_.count(c) == 0) {
if(isdigit(*i)) {
startingPositions_[c - '0'] = location(x,y);
c = KEEP;
} else {
std::cerr << "Illegal character in map: (" << int(c) << ") '" << c << "'\n";
throw incorrect_format_exception("Illegal character");
}
}
if(is_village(c)) {
villages_.push_back(location(int(x),int(y)));
}
if(x >= tiles_.size()) {
tiles_.resize(x+1);
}
tiles_[x].push_back(c);
++x;
}
}
for(size_t n = 0; n != tiles_.size(); ++n) {
if(tiles_[n].size() != size_t(this->y())) {
std::cerr << "Map is not rectangular!\n";
tiles_.erase(tiles_.begin()+n);
--n;
}
}
std::cerr << "loaded map: " << this->x() << "," << this->y() << "\n";
}
std::string gamemap::write() const
{
std::stringstream str;
for(int j = 0; j != y(); ++j) {
for(int i = 0; i != x(); ++i) {
int n;
for(n = 0; n != 10; ++n) {
if(startingPositions_[n] == location(i,j))
break;
}
if(n < 10)
str << n;
else
str << tiles_[i][j];
}
str << "\n";
}
return str.str();
}
int gamemap::x() const { return tiles_.size(); }
int gamemap::y() const { return tiles_.empty() ? 0 : tiles_.front().size(); }
const std::vector<gamemap::TERRAIN>& gamemap::operator[](int index) const
{
return tiles_[index];
}
gamemap::TERRAIN gamemap::get_terrain(const gamemap::location& loc) const
{
if(on_board(loc))
return tiles_[loc.x][loc.y];
const std::map<location,TERRAIN>::const_iterator itor = borderCache_.find(loc);
if(itor != borderCache_.end())
return itor->second;
//if not on the board, decide based on what surrounding terrain is
TERRAIN items[6];
int nitems = 0;
location adj[6];
get_adjacent_tiles(loc,adj);
for(int n = 0; n != 6; ++n) {
if(on_board(adj[n])) {
items[nitems] = tiles_[adj[n].x][adj[n].y];
++nitems;
}
}
//count all the terrain types found, and see which one
//is the most common, and use it.
TERRAIN used_terrain = 0;
int terrain_count = 0;
for(int i = 0; i != nitems; ++i) {
if(items[i] != used_terrain && !is_village(items[i]) && !is_keep(items[i])) {
const int c = std::count(items+i+1,items+nitems,items[i]) + 1;
if(c > terrain_count) {
used_terrain = items[i];
terrain_count = c;
}
}
}
borderCache_.insert(std::pair<location,TERRAIN>(loc,used_terrain));
return used_terrain;
}
const gamemap::location& gamemap::starting_position(int n) const
{
if(n < sizeof(startingPositions_)/sizeof(*startingPositions_)) {
return startingPositions_[n];
} else {
static const gamemap::location null_loc;
return null_loc;
}
}
int gamemap::num_valid_starting_positions() const
{
const int res = is_starting_position(gamemap::location());
if(res == -1)
return num_starting_positions()-1;
else
return res;
}
int gamemap::num_starting_positions() const
{
return sizeof(startingPositions_)/sizeof(*startingPositions_);
}
int gamemap::is_starting_position(const gamemap::location& loc) const
{
const gamemap::location* const beg = startingPositions_+1;
const gamemap::location* const end = startingPositions_+num_starting_positions();
const gamemap::location* const pos = std::find(beg,end,loc);
return pos == end ? -1 : pos - beg;
}
void gamemap::set_starting_position(int side, const gamemap::location& loc)
{
if(side >= 0 && side < num_starting_positions()) {
startingPositions_[side] = loc;
}
}
const terrain_type& gamemap::get_terrain_info(TERRAIN terrain) const
{
static const terrain_type default_terrain;
const std::map<TERRAIN,terrain_type>::const_iterator i =
letterToTerrain_.find(terrain);
if(i != letterToTerrain_.end())
return i->second;
else
return default_terrain;
}
const terrain_type& gamemap::get_terrain_info(const gamemap::location &loc) const
{
return get_terrain_info(get_terrain(loc));
}
const std::vector<gamemap::TERRAIN>& gamemap::get_terrain_precedence() const
{
return terrainPrecedence_;
}
bool gamemap::is_built(const location &loc) const
{
return is_castle(loc);
}
void gamemap::set_terrain(const gamemap::location& loc, gamemap::TERRAIN ter)
{
if(!on_board(loc))
return;
tiles_[loc.x][loc.y] = ter;
location adj[6];
get_adjacent_tiles(loc,adj);
for(int i = 0; i < 6; ++i)
remove_from_border_cache(adj[i]);
}
std::vector<gamemap::location> parse_location_range(const std::string& x, const std::string& y)
{
std::vector<gamemap::location> res;
const std::vector<std::string> xvals = config::split(x);
const std::vector<std::string> yvals = config::split(y);
for(unsigned int i = 0; i != minimum(xvals.size(),yvals.size()); ++i) {
const std::pair<int,int> xrange = config::parse_range(xvals[i]);
const std::pair<int,int> yrange = config::parse_range(yvals[i]);
for(int x = xrange.first; x <= xrange.second; ++x) {
for(int y = yrange.first; y <= yrange.second; ++y) {
res.push_back(gamemap::location(x-1,y-1));
}
}
}
return res;
}
const std::map<gamemap::TERRAIN,size_t>& gamemap::get_weighted_terrain_frequencies() const
{
if(terrainFrequencyCache_.empty() == false) {
return terrainFrequencyCache_;
}
const location center(x()/2,y()/2);
const size_t furthest_distance = distance_between(location(0,0),center);
const size_t weight_at_edge = 100;
const size_t additional_weight_at_center = 200;
for(size_t i = 0; i != x(); ++i) {
for(size_t j = 0; j != y(); ++j) {
const size_t distance = distance_between(location(i,j),center);
terrainFrequencyCache_[(*this)[i][j]] += weight_at_edge + (furthest_distance-distance)*additional_weight_at_center;
}
}
return terrainFrequencyCache_;
}
void gamemap::remove_from_border_cache(const location &loc) {
borderCache_.erase(loc);
}