00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "global.hpp"
00022
00023 #include <cassert>
00024
00025 #include "map.hpp"
00026
00027 #include "formula_string_utils.hpp"
00028 #include "gettext.hpp"
00029 #include "log.hpp"
00030 #include "map_exception.hpp"
00031 #include "serialization/parser.hpp"
00032 #include "util.hpp"
00033 #include "wml_exception.hpp"
00034
00035 static lg::log_domain log_config("config");
00036 #define ERR_CF LOG_STREAM(err, log_config)
00037 #define LOG_G LOG_STREAM(info, lg::general)
00038 #define DBG_G LOG_STREAM(debug, lg::general)
00039
00040 const gamemap::tborder gamemap::default_border = gamemap::SINGLE_TILE_BORDER;
00041
00042 const t_translation::t_list& gamemap::underlying_mvt_terrain(t_translation::t_terrain terrain) const
00043 {
00044 const std::map<t_translation::t_terrain,terrain_type>::const_iterator i =
00045 tcodeToTerrain_.find(terrain);
00046
00047 if(i == tcodeToTerrain_.end()) {
00048 static t_translation::t_list result(1);
00049 result[0] = terrain;
00050 return result;
00051 } else {
00052 return i->second.mvt_type();
00053 }
00054 }
00055
00056 const t_translation::t_list& gamemap::underlying_def_terrain(t_translation::t_terrain terrain) const
00057 {
00058 const std::map<t_translation::t_terrain, terrain_type>::const_iterator i =
00059 tcodeToTerrain_.find(terrain);
00060
00061 if(i == tcodeToTerrain_.end()) {
00062 static t_translation::t_list result(1);
00063 result[0] = terrain;
00064 return result;
00065 } else {
00066 return i->second.def_type();
00067 }
00068 }
00069
00070 const t_translation::t_list& gamemap::underlying_union_terrain(t_translation::t_terrain terrain) const
00071 {
00072 const std::map<t_translation::t_terrain,terrain_type>::const_iterator i =
00073 tcodeToTerrain_.find(terrain);
00074
00075 if(i == tcodeToTerrain_.end()) {
00076 static t_translation::t_list result(1);
00077 result[0] = terrain;
00078 return result;
00079 } else {
00080 return i->second.union_type();
00081 }
00082 }
00083
00084
00085
00086 std::string gamemap::get_terrain_string(const t_translation::t_terrain& terrain) const
00087 {
00088 std::string str =
00089 get_terrain_info(terrain).description();
00090
00091 str += get_underlying_terrain_string(terrain);
00092
00093 return str;
00094 }
00095
00096 std::string gamemap::get_terrain_editor_string(const t_translation::t_terrain& terrain) const
00097 {
00098 std::string str =
00099 get_terrain_info(terrain).editor_name();
00100 const std::string desc =
00101 get_terrain_info(terrain).description();
00102
00103 if(str != desc) {
00104 str += "/";
00105 str += desc;
00106 }
00107
00108 str += get_underlying_terrain_string(terrain);
00109
00110 return str;
00111 }
00112
00113 std::string gamemap::get_underlying_terrain_string(const t_translation::t_terrain& terrain) const
00114 {
00115 std::string str;
00116
00117 const t_translation::t_list& underlying = underlying_union_terrain(terrain);
00118 assert(!underlying.empty());
00119
00120 if(underlying.size() > 1 || underlying[0] != terrain) {
00121 str += " (";
00122 t_translation::t_list::const_iterator i = underlying.begin();
00123 str += get_terrain_info(*i).name();
00124 while (++i != underlying.end()) {
00125 str += ", " + get_terrain_info(*i).name();
00126 }
00127 str += ")";
00128 }
00129
00130 return str;
00131 }
00132
00133 void gamemap::write_terrain(const map_location &loc, config& cfg) const
00134 {
00135 cfg["terrain"] = t_translation::write_terrain_code(get_terrain(loc));
00136 }
00137
00138 gamemap::gamemap(const config& cfg, const std::string& data):
00139 tiles_(1),
00140 terrainList_(),
00141 tcodeToTerrain_(),
00142 villages_(),
00143 borderCache_(),
00144 terrainFrequencyCache_(),
00145 w_(-1),
00146 h_(-1),
00147 total_width_(0),
00148 total_height_(0),
00149 border_size_(gamemap::SINGLE_TILE_BORDER),
00150 usage_(IS_MAP)
00151 {
00152 DBG_G << "loading map: '" << data << "'\n";
00153 const config::const_child_itors &terrains = cfg.child_range("terrain_type");
00154 create_terrain_maps(terrains, terrainList_, tcodeToTerrain_);
00155
00156 read(data);
00157 }
00158
00159 gamemap::gamemap(const config& cfg, const config& level):
00160 tiles_(1),
00161 terrainList_(),
00162 tcodeToTerrain_(),
00163 villages_(),
00164 borderCache_(),
00165 terrainFrequencyCache_(),
00166 w_(-1),
00167 h_(-1),
00168 total_width_(0),
00169 total_height_(0),
00170 border_size_(gamemap::SINGLE_TILE_BORDER),
00171 usage_(IS_MAP)
00172 {
00173 DBG_G << "loading map: '" << level.debug() << "'\n";
00174 const config::const_child_itors &terrains = cfg.child_range("terrain_type");
00175 create_terrain_maps(terrains, terrainList_, tcodeToTerrain_);
00176
00177 const config& map_child = level.child_or_empty("map");
00178
00179 if (map_child.empty()) {
00180 const std::string& map_data = level["map_data"];
00181 if (!map_data.empty()) {
00182 read(map_data);
00183 } else {
00184 w_ = 0;
00185 h_ = 0;
00186 total_width_ = 0;
00187 total_height_ = 0;
00188 }
00189 } else {
00190 read(map_child["data"], true, map_child["border_size"], map_child["usage"]);
00191 }
00192 }
00193
00194 gamemap::~gamemap()
00195 {
00196 }
00197
00198 void gamemap::read(const std::string& data, const bool allow_invalid, int border_size, std::string usage) {
00199
00200
00201 border_size_ = border_size;
00202 set_usage(usage);
00203 tiles_.clear();
00204 villages_.clear();
00205 std::fill(startingPositions_, startingPositions_ +
00206 sizeof(startingPositions_) / sizeof(*startingPositions_), map_location());
00207 std::map<int, t_translation::coordinate> starting_positions;
00208
00209 if(data.empty()) {
00210 w_ = 0;
00211 h_ = 0;
00212 total_width_ = 0;
00213 total_height_ = 0;
00214 if(allow_invalid) return;
00215 }
00216
00217 int offset = read_header(data);
00218
00219 const std::string& data_only = std::string(data, offset);
00220
00221 try {
00222 tiles_ = t_translation::read_game_map(data_only, starting_positions);
00223
00224 } catch(t_translation::error& e) {
00225
00226
00227 throw incorrect_map_format_error(e.message);
00228 }
00229
00230
00231 std::map<int, t_translation::coordinate>::const_iterator itor =
00232 starting_positions.begin();
00233
00234 for(; itor != starting_positions.end(); ++itor) {
00235
00236
00237
00238
00239 if(itor->first < 1 || itor->first >= MAX_PLAYERS+1) {
00240 std::stringstream ss;
00241 ss << "Starting position " << itor->first << " out of range\n";
00242 ERR_CF << ss.str();
00243 ss << "The map cannot be loaded.";
00244 throw incorrect_map_format_error(ss.str().c_str());
00245 }
00246
00247
00248 startingPositions_[itor->first] = map_location(itor->second.x - 1, itor->second.y - 1);
00249 }
00250
00251
00252 total_width_ = tiles_.size();
00253 total_height_ = total_width_ > 0 ? tiles_[0].size() : 0;
00254 w_ = total_width_ - 2 * border_size_;
00255 h_ = total_height_ - 2 * border_size_;
00256
00257
00258
00259
00260
00261 for(int x = 0; x < total_width_; ++x) {
00262 for(int y = 0; y < total_height_; ++y) {
00263
00264
00265 if(tcodeToTerrain_.count(tiles_[x][y]) == 0) {
00266 if(!try_merge_terrains(tiles_[x][y])) {
00267 std::stringstream ss;
00268 ss << "Illegal tile in map: (" << t_translation::write_terrain_code(tiles_[x][y])
00269 << ") '" << tiles_[x][y] << "'\n";
00270 ERR_CF << ss.str();
00271 ss << "The map cannot be loaded.";
00272 throw incorrect_map_format_error(ss.str().c_str());
00273 }
00274 }
00275
00276
00277 if(x >= border_size_ && y >= border_size_
00278 && x < total_width_-border_size_ && y < total_height_-border_size_
00279 && is_village(tiles_[x][y])) {
00280 villages_.push_back(map_location(x-border_size_, y-border_size_));
00281 }
00282 }
00283 }
00284 }
00285
00286 void gamemap::set_usage(const std::string& usage)
00287 {
00288 utils::string_map symbols;
00289 symbols["border_size_key"] = "border_size";
00290 symbols["usage_key"] = "usage";
00291 symbols["usage_val"] = usage;
00292 const std::string msg = "'$border_size_key|' should be "
00293 "'$border_size_val|' when '$usage_key| = $usage_val|'";
00294
00295 if(usage == "map") {
00296 usage_ = IS_MAP;
00297 symbols["border_size_val"] = "1";
00298 VALIDATE(border_size_ == 1, vgettext(msg.c_str(), symbols));
00299 } else if(usage == "mask") {
00300 usage_ = IS_MASK;
00301 symbols["border_size_val"] = "0";
00302 VALIDATE(border_size_ == 0, vgettext(msg.c_str(), symbols));
00303 } else if(usage == "") {
00304 throw incorrect_map_format_error("Map has a header but no usage");
00305 } else {
00306 std::string msg = "Map has a header but an unknown usage:" + usage;
00307 throw incorrect_map_format_error(msg);
00308 }
00309 }
00310
00311 int gamemap::read_header(const std::string& data)
00312 {
00313
00314 size_t header_offset = data.find("\n\n");
00315 if(header_offset == std::string::npos) {
00316
00317
00318
00319
00320 header_offset = data.find("\r\n\r\n");
00321 }
00322 const size_t comma_offset = data.find(",");
00323
00324
00325
00326
00327 if (!(!(header_offset == std::string::npos || comma_offset < header_offset)))
00328 return 0;
00329
00330 std::string header_str(std::string(data, 0, header_offset + 1));
00331 config header;
00332 ::read(header, header_str);
00333
00334 border_size_ = header["border_size"];
00335 set_usage(header["usage"]);
00336
00337 return header_offset + 2;
00338 }
00339
00340
00341 void gamemap::write(config& cfg) const
00342 {
00343
00344 std::map<int, t_translation::coordinate> starting_positions;
00345 for (int i = 0; i < MAX_PLAYERS + 1; ++i)
00346 {
00347 if (!on_board(startingPositions_[i])) continue;
00348 t_translation::coordinate position(
00349 startingPositions_[i].x + border_size_
00350 , startingPositions_[i].y + border_size_);
00351 starting_positions[i] = position;
00352 }
00353
00354 cfg["border_size"] = border_size_;
00355 cfg["usage"] = (usage_ == IS_MAP ? "map" : "mask");
00356
00357 std::ostringstream s;
00358 s << t_translation::write_game_map(tiles_, starting_positions);
00359 cfg["data"] = s.str();
00360 }
00361
00362 void gamemap::overlay(const gamemap& m, const config& rules_cfg, int xpos, int ypos, bool border)
00363 {
00364 const config::const_child_itors &rules = rules_cfg.child_range("rule");
00365 int actual_border = (m.border_size() == border_size()) && border ? border_size() : 0;
00366
00367 const int xstart = std::max<int>(-actual_border, -xpos - actual_border);
00368 const int ystart = std::max<int>(-actual_border, -ypos - actual_border - ((xpos & 1) ? 1 : 0));
00369 const int xend = std::min<int>(m.w() + actual_border, w() + actual_border - xpos);
00370 const int yend = std::min<int>(m.h() + actual_border, h() + actual_border - ypos);
00371 for(int x1 = xstart; x1 < xend; ++x1) {
00372 for(int y1 = ystart; y1 < yend; ++y1) {
00373 const int x2 = x1 + xpos;
00374 const int y2 = y1 + ypos +
00375 ((xpos & 1) && (x1 & 1) ? 1 : 0);
00376
00377 const t_translation::t_terrain t = m[x1][y1 + m.border_size_];
00378 const t_translation::t_terrain current = (*this)[x2][y2 + border_size_];
00379
00380 if(t == t_translation::FOGGED || t == t_translation::VOID_TERRAIN) {
00381 continue;
00382 }
00383
00384
00385 config::const_child_iterator rule = rules.first;
00386 for( ; rule != rules.second; ++rule)
00387 {
00388 static const std::string src_key = "old", src_not_key = "old_not",
00389 dst_key = "new", dst_not_key = "new_not";
00390 const config &cfg = *rule;
00391 const t_translation::t_list& src = t_translation::read_list(cfg[src_key]);
00392
00393 if(!src.empty() && t_translation::terrain_matches(current, src) == false) {
00394 continue;
00395 }
00396
00397 const t_translation::t_list& src_not = t_translation::read_list(cfg[src_not_key]);
00398
00399 if(!src_not.empty() && t_translation::terrain_matches(current, src_not)) {
00400 continue;
00401 }
00402
00403 const t_translation::t_list& dst = t_translation::read_list(cfg[dst_key]);
00404
00405 if(!dst.empty() && t_translation::terrain_matches(t, dst) == false) {
00406 continue;
00407 }
00408
00409 const t_translation::t_list& dst_not = t_translation::read_list(cfg[dst_not_key]);
00410
00411 if(!dst_not.empty() && t_translation::terrain_matches(t, dst_not)) {
00412 continue;
00413 }
00414
00415 break;
00416 }
00417
00418
00419 if (rule != rules.second)
00420 {
00421 const config &cfg = *rule;
00422 const t_translation::t_list& terrain = t_translation::read_list(cfg["terrain"]);
00423
00424 tmerge_mode mode = BOTH;
00425 if (cfg["layer"] == "base") {
00426 mode = BASE;
00427 }
00428 else if (cfg["layer"] == "overlay") {
00429 mode = OVERLAY;
00430 }
00431
00432 t_translation::t_terrain new_terrain = t;
00433 if(!terrain.empty()) {
00434 new_terrain = terrain[0];
00435 }
00436
00437 if (!cfg["use_old"].to_bool()) {
00438 set_terrain(map_location(x2, y2), new_terrain, mode, cfg["replace_if_failed"].to_bool());
00439 }
00440
00441 } else {
00442 set_terrain(map_location(x2,y2),t);
00443 }
00444 }
00445 }
00446
00447 for(const map_location* pos = m.startingPositions_;
00448 pos != m.startingPositions_ + sizeof(m.startingPositions_)/sizeof(*m.startingPositions_);
00449 ++pos) {
00450
00451 if(pos->valid()) {
00452 startingPositions_[pos - m.startingPositions_] = *pos;
00453 }
00454 }
00455 }
00456
00457 t_translation::t_terrain gamemap::get_terrain(const map_location& loc) const
00458 {
00459
00460 if(on_board_with_border(loc)) {
00461 return tiles_[loc.x + border_size_][loc.y + border_size_];
00462 }
00463
00464 const std::map<map_location, t_translation::t_terrain>::const_iterator itor = borderCache_.find(loc);
00465 if(itor != borderCache_.end())
00466 return itor->second;
00467
00468
00469 t_translation::t_terrain items[6];
00470 int number_of_items = 0;
00471
00472 map_location adj[6];
00473 get_adjacent_tiles(loc,adj);
00474 for(int n = 0; n != 6; ++n) {
00475 if(on_board(adj[n])) {
00476 items[number_of_items] = tiles_[adj[n].x][adj[n].y];
00477 ++number_of_items;
00478 } else {
00479
00480
00481
00482
00483
00484
00485
00486
00487
00488 std::map<map_location, t_translation::t_terrain>::const_iterator itor =
00489 borderCache_.find(adj[n]);
00490
00491
00492 if(itor != borderCache_.end() &&
00493 itor->second != t_translation::NONE_TERRAIN) {
00494
00495 items[number_of_items] = itor->second;
00496 ++number_of_items;
00497 }
00498 }
00499
00500 }
00501
00502
00503
00504 t_translation::t_terrain used_terrain;
00505 int terrain_count = 0;
00506 for(int i = 0; i != number_of_items; ++i) {
00507 if(items[i] != used_terrain && !is_village(items[i]) && !is_keep(items[i])) {
00508 const int c = std::count(items+i+1,items+number_of_items,items[i]) + 1;
00509 if(c > terrain_count) {
00510 used_terrain = items[i];
00511 terrain_count = c;
00512 }
00513 }
00514 }
00515
00516 borderCache_.insert(std::pair<map_location, t_translation::t_terrain>(loc,used_terrain));
00517 return used_terrain;
00518
00519 }
00520
00521 const map_location& gamemap::starting_position(int n) const
00522 {
00523 if(size_t(n) < sizeof(startingPositions_)/sizeof(*startingPositions_)) {
00524 return startingPositions_[n];
00525 } else {
00526 static const map_location null_loc;
00527 return null_loc;
00528 }
00529 }
00530
00531 int gamemap::num_valid_starting_positions() const
00532 {
00533 const int res = is_starting_position(map_location());
00534 if(res == -1)
00535 return num_starting_positions()-1;
00536 else
00537 return res;
00538 }
00539
00540 int gamemap::is_starting_position(const map_location& loc) const
00541 {
00542 const map_location* const beg = startingPositions_+1;
00543 const map_location* const end = startingPositions_+num_starting_positions();
00544 const map_location* const pos = std::find(beg,end,loc);
00545
00546 return pos == end ? -1 : pos - beg;
00547 }
00548
00549 void gamemap::set_starting_position(int side, const map_location& loc)
00550 {
00551 if(side >= 0 && side < num_starting_positions()) {
00552 startingPositions_[side] = loc;
00553 }
00554 }
00555
00556 bool gamemap::on_board(const map_location& loc) const
00557 {
00558 return loc.valid() && loc.x < w_ && loc.y < h_;
00559 }
00560
00561 bool gamemap::on_board_with_border(const map_location& loc) const
00562 {
00563 if(tiles_.empty()) {
00564 return false;
00565 } else {
00566 return loc.x >= (0 - border_size_) && loc.x < (w_ + border_size_) &&
00567 loc.y >= (0 - border_size_) && loc.y < (h_ + border_size_);
00568 }
00569 }
00570
00571 const terrain_type& gamemap::get_terrain_info(const t_translation::t_terrain terrain) const
00572 {
00573 static const terrain_type default_terrain;
00574 const std::map<t_translation::t_terrain,terrain_type>::const_iterator i =
00575 tcodeToTerrain_.find(terrain);
00576
00577 if(i != tcodeToTerrain_.end())
00578 return i->second;
00579 else
00580 return default_terrain;
00581 }
00582
00583 void gamemap::set_terrain(const map_location& loc, const t_translation::t_terrain terrain, const tmerge_mode mode, bool replace_if_failed) {
00584 if(!on_board_with_border(loc)) {
00585
00586 return;
00587 }
00588
00589 t_translation::t_terrain new_terrain = merge_terrains(get_terrain(loc), terrain, mode, replace_if_failed);
00590
00591 if(new_terrain == t_translation::NONE_TERRAIN) {
00592 return;
00593 }
00594
00595 if(on_board(loc)) {
00596 const bool old_village = is_village(loc);
00597 const bool new_village = is_village(new_terrain);
00598
00599 if(old_village && !new_village) {
00600 villages_.erase(std::remove(villages_.begin(),villages_.end(),loc),villages_.end());
00601 } else if(!old_village && new_village) {
00602 villages_.push_back(loc);
00603 }
00604 }
00605
00606 tiles_[loc.x + border_size_][loc.y + border_size_] = new_terrain;
00607
00608
00609 map_location adj[6];
00610 get_adjacent_tiles(loc,adj);
00611
00612 for(int n = 0; n < 6; ++n) {
00613 remove_from_border_cache(adj[n]);
00614 }
00615 }
00616
00617 const std::map<t_translation::t_terrain, size_t>& gamemap::get_weighted_terrain_frequencies() const
00618 {
00619 if(terrainFrequencyCache_.empty() == false) {
00620 return terrainFrequencyCache_;
00621 }
00622
00623 const map_location center(w()/2,h()/2);
00624
00625 const size_t furthest_distance = distance_between(map_location(0,0),center);
00626
00627 const size_t weight_at_edge = 100;
00628 const size_t additional_weight_at_center = 200;
00629
00630 for(size_t i = 0; i != size_t(w()); ++i) {
00631 for(size_t j = 0; j != size_t(h()); ++j) {
00632 const size_t distance = distance_between(map_location(i,j),center);
00633 terrainFrequencyCache_[(*this)[i][j]] += weight_at_edge +
00634 (furthest_distance-distance)*additional_weight_at_center;
00635 }
00636 }
00637
00638 return terrainFrequencyCache_;
00639 }
00640
00641 bool gamemap::try_merge_terrains(const t_translation::t_terrain terrain) {
00642
00643 if(tcodeToTerrain_.count(terrain) == 0) {
00644 const std::map<t_translation::t_terrain, terrain_type>::const_iterator base_iter =
00645 tcodeToTerrain_.find(t_translation::t_terrain(terrain.base, t_translation::NO_LAYER));
00646 const std::map<t_translation::t_terrain, terrain_type>::const_iterator overlay_iter =
00647 tcodeToTerrain_.find(t_translation::t_terrain(t_translation::NO_LAYER, terrain.overlay));
00648
00649 if(base_iter == tcodeToTerrain_.end() || overlay_iter == tcodeToTerrain_.end()) {
00650 return false;
00651 }
00652
00653 terrain_type new_terrain(base_iter->second, overlay_iter->second);
00654 terrainList_.push_back(new_terrain.number());
00655 tcodeToTerrain_.insert(std::pair<t_translation::t_terrain, terrain_type>(
00656 new_terrain.number(), new_terrain));
00657 return true;
00658 }
00659 return true;
00660 }
00661
00662 t_translation::t_terrain gamemap::merge_terrains(const t_translation::t_terrain old_t, const t_translation::t_terrain new_t, const tmerge_mode mode, bool replace_if_failed) {
00663 t_translation::t_terrain result = t_translation::NONE_TERRAIN;
00664
00665 if(mode == OVERLAY) {
00666 const t_translation::t_terrain t = t_translation::t_terrain(old_t.base, new_t.overlay);
00667 if (try_merge_terrains(t)) {
00668 result = t;
00669 }
00670 }
00671 else if(mode == BASE) {
00672 const t_translation::t_terrain t = t_translation::t_terrain(new_t.base, old_t.overlay);
00673 if (try_merge_terrains(t)) {
00674 result = t;
00675 }
00676 }
00677 else if(mode == BOTH && new_t.base != t_translation::NO_LAYER) {
00678
00679 if (try_merge_terrains(new_t)) {
00680 result = new_t;
00681 }
00682 }
00683
00684
00685
00686
00687 if(result == t_translation::NONE_TERRAIN && replace_if_failed && tcodeToTerrain_.count(new_t) > 0) {
00688 if(new_t.base != t_translation::NO_LAYER) {
00689
00690 if (try_merge_terrains(new_t)) {
00691 result = new_t;
00692 }
00693 }
00694 else if (get_terrain_info(new_t).default_base() != t_translation::NONE_TERRAIN) {
00695 result = get_terrain_info(new_t).terrain_with_default_base();
00696 }
00697 }
00698 return result;
00699 }