map.cpp

Go to the documentation of this file.
00001 /* $Id: map.cpp 26778 2008-05-22 19:11:42Z mog $ */
00002 /*
00003    Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License version 2
00008    or at your option any later version.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details.
00013 */
00014 
00015 //! @file map.cpp 
00016 //! Routines related to game-maps, terrain, locations, directions. etc.
00017 
00018 #include "global.hpp"
00019 
00020 #include "config.hpp"
00021 #include "gettext.hpp"
00022 #include "log.hpp"
00023 #include "map.hpp"
00024 #include "pathfind.hpp"
00025 #include "util.hpp"
00026 #include "serialization/string_utils.hpp"
00027 #include "serialization/parser.hpp"
00028 #include "wml_exception.hpp"
00029 
00030 #include <algorithm>
00031 #include <cassert>
00032 #include <cctype>
00033 #include <cstdlib>
00034 #include <iostream>
00035 #include <sstream>
00036 
00037 #define ERR_CF LOG_STREAM(err, config)
00038 #define LOG_G LOG_STREAM(info, general)
00039 #define DBG_G LOG_STREAM(debug, general)
00040 
00041 std::ostream &operator<<(std::ostream &s, gamemap::location const &l) {
00042     s << (l.x + 1) << ',' << (l.y + 1);
00043     return s;
00044 }
00045 
00046 const gamemap::location gamemap::location::null_location;
00047 
00048 const std::string gamemap::default_map_header = "usage=map\nborder_size=1\n\n";
00049 const gamemap::tborder gamemap::default_border = gamemap::SINGLE_TILE_BORDER;
00050 
00051 const t_translation::t_list& gamemap::underlying_mvt_terrain(t_translation::t_terrain terrain) const
00052 {
00053     const std::map<t_translation::t_terrain,terrain_type>::const_iterator i =
00054         tcodeToTerrain_.find(terrain);
00055 
00056     if(i == tcodeToTerrain_.end()) {
00057         static t_translation::t_list result(1);
00058         result[0] = terrain;
00059         return result;
00060     } else {
00061         return i->second.mvt_type();
00062     }
00063 }
00064 
00065 const t_translation::t_list& gamemap::underlying_def_terrain(t_translation::t_terrain terrain) const
00066 {
00067     const std::map<t_translation::t_terrain, terrain_type>::const_iterator i =
00068         tcodeToTerrain_.find(terrain);
00069 
00070     if(i == tcodeToTerrain_.end()) {
00071         static t_translation::t_list result(1);
00072         result[0] = terrain;
00073         return result;
00074     } else {
00075         return i->second.def_type();
00076     }
00077 }
00078 
00079 const t_translation::t_list& gamemap::underlying_union_terrain(t_translation::t_terrain terrain) const
00080 {
00081     const std::map<t_translation::t_terrain,terrain_type>::const_iterator i =
00082         tcodeToTerrain_.find(terrain);
00083 
00084     if(i == tcodeToTerrain_.end()) {
00085         static t_translation::t_list result(1);
00086         result[0] = terrain;
00087         return result;
00088     } else {
00089         return i->second.union_type();
00090     }
00091 }
00092 
00093 void gamemap::write_terrain(const gamemap::location &loc, config& cfg) const
00094 {
00095     cfg["terrain"] = t_translation::write_terrain_code(get_terrain(loc));
00096 }
00097 
00098 gamemap::location::DIRECTION gamemap::location::parse_direction(const std::string& str)
00099 {
00100     if(!str.empty()) {
00101         if(str == "n") {
00102             return NORTH;
00103         } else if(str == "ne") {
00104             return NORTH_EAST;
00105         } else if(str == "se") {
00106             return SOUTH_EAST;
00107         } else if(str == "s") {
00108             return SOUTH;
00109         } else if(str == "sw") {
00110             return SOUTH_WEST;
00111         } else if(str == "nw") {
00112             return NORTH_WEST;
00113         } else if(str[0] == '-' && str.length() <= 10) {
00114             // A minus sign reverses the direction
00115             return get_opposite_dir(parse_direction(str.substr(1)));
00116         }
00117     }
00118     return NDIRECTIONS;
00119 }
00120 
00121 std::vector<gamemap::location::DIRECTION> gamemap::location::parse_directions(const std::string& str)
00122 {
00123     gamemap::location::DIRECTION temp;
00124     std::vector<gamemap::location::DIRECTION> to_return;
00125     std::vector<std::string> dir_strs = utils::split(str);
00126     std::vector<std::string>::const_iterator i, i_end=dir_strs.end();
00127     for(i = dir_strs.begin(); i != i_end; ++i) {
00128         temp = gamemap::location::parse_direction(*i);
00129         // Filter out any invalid directions
00130         if(temp != NDIRECTIONS) {
00131             to_return.push_back(temp);
00132         }
00133     }
00134     return to_return;
00135 }
00136 
00137 std::string gamemap::location::write_direction(gamemap::location::DIRECTION dir)
00138 {
00139     switch(dir) {
00140         case NORTH:
00141             return std::string("n");
00142         case NORTH_EAST:
00143             return std::string("ne");
00144         case NORTH_WEST:
00145             return std::string("nw");
00146         case SOUTH:
00147             return std::string("s");
00148         case SOUTH_EAST:
00149             return std::string("se");
00150         case SOUTH_WEST:
00151             return std::string("sw");
00152         default:
00153             return std::string();
00154 
00155     }
00156 }
00157 
00158 gamemap::location::location(const config& cfg, const variable_set *variables) :
00159         x(-1000),
00160         y(-1000)
00161 {
00162     std::string xs = cfg["x"], ys = cfg["y"];
00163     if (variables)
00164     {
00165         xs = utils::interpolate_variables_into_string( xs, *variables);
00166         ys = utils::interpolate_variables_into_string( ys, *variables);
00167     }
00168     // The co-ordinates in config files will be 1-based, 
00169     // while we want them as 0-based.
00170     if(xs.empty() == false)
00171         x = atoi(xs.c_str()) - 1;
00172 
00173     if(ys.empty() == false)
00174         y = atoi(ys.c_str()) - 1;
00175 }
00176 
00177 void gamemap::location::write(config& cfg) const
00178 {
00179     char buf[50];
00180     snprintf(buf,sizeof(buf),"%d",x+1);
00181     cfg["x"] = buf;
00182     snprintf(buf,sizeof(buf),"%d",y+1);
00183     cfg["y"] = buf;
00184 }
00185 
00186 gamemap::location gamemap::location::operator-() const
00187 {
00188     location ret;
00189     ret.x = -x;
00190     ret.y = -y;
00191 
00192     return ret;
00193 }
00194 
00195 gamemap::location gamemap::location::operator+(const gamemap::location& a) const
00196 {
00197     gamemap::location ret = *this;
00198     ret += a;
00199     return ret;
00200 }
00201 
00202 gamemap::location& gamemap::location::operator+=(const gamemap::location &a)
00203 {
00204     bool parity = (x & 1) != 0;
00205 
00206     x += a.x;
00207     y += a.y;
00208 
00209     if((a.x > 0) && (a.x % 2) && parity)
00210         y++;
00211     if((a.x < 0) && (a.x % 2) && !parity)
00212         y--;
00213 
00214     return *this;
00215 }
00216 
00217 gamemap::location gamemap::location::operator-(const gamemap::location &a) const
00218 {
00219     return operator+(-a);
00220 }
00221 
00222 gamemap::location& gamemap::location::operator-=(const gamemap::location &a)
00223 {
00224     return operator+=(-a);
00225 }
00226 
00227 gamemap::location gamemap::location::get_direction(
00228             gamemap::location::DIRECTION dir, int n) const
00229 {
00230     if (n < 0 ) {
00231         dir = get_opposite_dir(dir);
00232         n = -n;
00233     }
00234     switch(dir) {
00235         case NORTH:      return gamemap::location(x, y - n);
00236         case SOUTH:      return gamemap::location(x, y + n);
00237         case SOUTH_EAST: return gamemap::location(x + n, y + (n+is_odd(x))/2 );
00238         case SOUTH_WEST: return gamemap::location(x - n, y + (n+is_odd(x))/2 );
00239         case NORTH_EAST: return gamemap::location(x + n, y - (n+is_even(x))/2 );
00240         case NORTH_WEST: return gamemap::location(x - n, y - (n+is_even(x))/2 );
00241         default:
00242             assert(false);
00243             return gamemap::location();
00244     }
00245 }
00246 
00247 gamemap::location::DIRECTION gamemap::location::get_relative_dir(gamemap::location loc) const {
00248     location diff = loc -*this;
00249     if(diff == location(0,0)) return NDIRECTIONS;
00250     if( diff.y < 0 && diff.x >= 0 && abs(diff.x) >= abs(diff.y)) return NORTH_EAST;
00251     if( diff.y < 0 && diff.x <  0 && abs(diff.x) >= abs(diff.y)) return NORTH_WEST;
00252     if( diff.y < 0 && abs(diff.x) < abs(diff.y)) return NORTH;
00253 
00254     if( diff.y >= 0 && diff.x >= 0 && abs(diff.x) >= abs(diff.y)) return SOUTH_EAST;
00255     if( diff.y >= 0 && diff.x <  0 && abs(diff.x) >= abs(diff.y)) return SOUTH_WEST;
00256     if( diff.y >= 0 && abs(diff.x) < abs(diff.y)) return SOUTH;
00257 
00258     // Impossible
00259     assert(false);
00260     return NDIRECTIONS;
00261 
00262 
00263 }
00264 gamemap::location::DIRECTION gamemap::location::get_opposite_dir(gamemap::location::DIRECTION d) {
00265     switch (d) {
00266         case NORTH:
00267             return SOUTH;
00268         case NORTH_EAST:
00269             return SOUTH_WEST;
00270         case SOUTH_EAST:
00271             return NORTH_WEST;
00272         case SOUTH:
00273             return NORTH;
00274         case SOUTH_WEST:
00275             return NORTH_EAST;
00276         case NORTH_WEST:
00277             return SOUTH_EAST;
00278         case NDIRECTIONS:
00279         default:
00280             return NDIRECTIONS;
00281     }
00282 }
00283 
00284 //! gamemap constructor
00285 //! 
00286 //! @param cfg          the game config
00287 //! @param data         the mapdata to load
00288 gamemap::gamemap(const config& cfg, const std::string& data):
00289         tiles_(1), 
00290         terrainList_(),
00291         tcodeToTerrain_(),
00292         villages_(),
00293         borderCache_(),
00294         terrainFrequencyCache_(),
00295         w_(-1), 
00296         h_(-1),
00297         total_width_(0),
00298         total_height_(0),
00299         border_size_(NO_BORDER),
00300         usage_(IS_MAP) 
00301 {
00302     DBG_G << "loading map: '" << data << "'\n";
00303     const config::child_list& terrains = cfg.get_children("terrain");
00304     create_terrain_maps(terrains,terrainList_,tcodeToTerrain_);
00305 
00306     read(data);
00307 }
00308 
00309 //! Reads a map 
00310 //!
00311 //! @param data         the mapdata to load
00312 void gamemap::read(const std::string& data)
00313 {
00314     // Initial stuff
00315     tiles_.clear();
00316     villages_.clear();
00317     std::fill(startingPositions_, startingPositions_ + 
00318         sizeof(startingPositions_) / sizeof(*startingPositions_), location());
00319     std::map<int, t_translation::coordinate> starting_positions;
00320 
00321     if(data.empty()) {
00322         w_ = 0;
00323         h_ = 0;
00324         return;
00325     }
00326 
00327     // Test whether there is a header section
00328     size_t header_offset = data.find("\n\n");
00329     if(header_offset == std::string::npos) {
00330         // For some reason Windows will fail to load a file with \r\n 
00331         // lineending properly no problems on Linux with those files. 
00332         // This workaround fixes the problem the copy later will copy 
00333         // the second \r\n to the map, but that's no problem.
00334         header_offset = data.find("\r\n\r\n");
00335     }
00336     const size_t comma_offset = data.find(",");
00337     // The header shouldn't contain commas, so if the comma is found 
00338     // before the header, we hit a \n\n inside or after a map. 
00339     // This is no header, so don't parse it as it would be.
00340     VALIDATE(
00341         !(header_offset == std::string::npos || comma_offset < header_offset), 
00342         _("A map without a header is not supported"));
00343 
00344     std::string header_str(std::string(data, 0, header_offset + 1));
00345     config header;
00346 	::read(header, header_str);
00347 
00348     border_size_ = lexical_cast_default<int>(header["border_size"], 0);
00349     const std::string usage = header["usage"];
00350 
00351     utils::string_map symbols;
00352     symbols["border_size_key"] = "border_size";
00353     symbols["usage_key"] = "usage";
00354     symbols["usage_val"] = usage;
00355     const std::string msg = "'$border_size_key|' should be "
00356         "'$border_size_val|' when '$usage_key| = $usage_val|'";
00357     
00358     if(usage == "map") {
00359         usage_ = IS_MAP;
00360         symbols["border_size_val"] = "1";
00361         VALIDATE(border_size_ == 1, vgettext(msg.c_str(), symbols));
00362     } else if(usage == "mask") {
00363         usage_ = IS_MASK;
00364         symbols["border_size_val"] = "0";
00365         VALIDATE(border_size_ == 0, vgettext(msg.c_str(), symbols));
00366     } else if(usage == "") {
00367         throw incorrect_format_exception("Map has a header but no usage");
00368     } else {
00369         std::string msg = "Map has a header but an unknown usage:" + usage;
00370         throw incorrect_format_exception(msg.c_str());
00371     }
00372 
00373     /* The third parameter is required for MSVC++ 6.0 */
00374     const std::string& map = std::string(data, header_offset + 2, std::string::npos);
00375 
00376     try {
00377         tiles_ = t_translation::read_game_map(map, starting_positions);
00378 
00379     } catch(t_translation::error& e) {
00380         // We re-throw the error but as map error. 
00381         // Since all codepaths test for this, it's the least work.
00382         throw incorrect_format_exception(e.message.c_str());
00383     }
00384 
00385     // Convert the starting positions to the array
00386     std::map<int, t_translation::coordinate>::const_iterator itor =
00387         starting_positions.begin();
00388 
00389     for(; itor != starting_positions.end(); ++itor) {
00390 
00391         // Check for valid position, 
00392         // the first valid position is 1,
00393         // so the offset 0 in the array is never used.
00394         if(itor->first < 1 || itor->first >= MAX_PLAYERS+1) {
00395             ERR_CF << "Starting position " << itor->first << " out of range\n";
00396             throw incorrect_format_exception("Illegal starting position found"
00397                 " in map. The scenario cannot be loaded.");
00398         }
00399 
00400         // Add to the starting position array
00401         startingPositions_[itor->first] = location(itor->second.x - 1, itor->second.y - 1);
00402     }
00403 
00404     // Post processing on the map
00405     total_width_ = tiles_.size();
00406     total_height_ = total_width_ > 0 ? tiles_[0].size() : 0;
00407     w_ = total_width_ - 2 * border_size_;
00408     h_ = total_height_ - 2 * border_size_;
00409 
00410     for(int x = 0; x < total_width_; ++x) {
00411         for(int y = 0; y < total_height_; ++y) {
00412             
00413             // Is the terrain valid? 
00414             if(tcodeToTerrain_.count(tiles_[x][y]) == 0) {
00415                 if(!try_merge_terrains(tiles_[x][y])) {
00416                     ERR_CF << "Illegal character in map: (" << t_translation::write_terrain_code(tiles_[x][y])
00417                            << ") '" << tiles_[x][y] << "'\n";
00418                     throw incorrect_format_exception("Illegal character found in map. The scenario cannot be loaded.");
00419                 }
00420             }
00421 
00422             // Is it a village?
00423             if(x >= border_size_ && y >= border_size_
00424                     && x < total_width_-border_size_  && y < total_height_-border_size_
00425                     && is_village(tiles_[x][y])) {
00426                 villages_.push_back(location(x-border_size_, y-border_size_));
00427             }
00428         }
00429     }
00430 }
00431 
00432 std::string gamemap::write() const
00433 {
00434     std::map<int, t_translation::coordinate> starting_positions = std::map<int, t_translation::coordinate>();
00435 
00436     // Convert the starting positions to a map
00437     for(int i = 0; i < MAX_PLAYERS+1; ++i) {
00438     if(on_board(startingPositions_[i])) {
00439             const struct t_translation::coordinate position =
00440                 {startingPositions_[i].x + border_size_, startingPositions_[i].y + border_size_};
00441 
00442              starting_positions.insert(std::pair<int, t_translation::coordinate>(i, position));
00443         }
00444     }
00445 
00446     // Let the low level convertor do the conversion
00447     const std::string& data = t_translation::write_game_map(tiles_, starting_positions);
00448     const std::string& header = "border_size=" + lexical_cast<std::string>(border_size_) 
00449         + "\nusage=" + (usage_ == IS_MAP ? "map" : "mask");
00450     return header + "\n\n" + data;
00451 }
00452 
00453 void gamemap::overlay(const gamemap& m, const config& rules_cfg, const int xpos, const int ypos)
00454 {
00455     const config::child_list& rules = rules_cfg.get_children("rule");
00456 
00457     const int xstart = maximum<int>(0, -xpos);
00458     const int ystart = maximum<int>(0, -ypos-((xpos & 1) ? 1 : 0));
00459     const int xend = minimum<int>(m.w(),w()-xpos);
00460     const int yend = minimum<int>(m.h(),h()-ypos);
00461     for(int x1 = xstart; x1 < xend; ++x1) {
00462         for(int y1 = ystart; y1 < yend; ++y1) {
00463             const int x2 = x1 + xpos;
00464             const int y2 = y1 + ypos +
00465                 ((xpos & 1) && (x1 & 1) ? 1 : 0);
00466             if (y2 < 0 || y2 >= h()) {
00467                 continue;
00468             }
00469             const t_translation::t_terrain t = m[x1][y1 + m.border_size_];
00470             const t_translation::t_terrain current = (*this)[x2][y2 + border_size_];
00471 
00472             if(t == t_translation::FOGGED || t == t_translation::VOID_TERRAIN) {
00473                 continue;
00474             }
00475 
00476             // See if there is a matching rule
00477             config::child_list::const_iterator rule = rules.begin();
00478             for( ; rule != rules.end(); ++rule) {
00479                 static const std::string src_key = "old", src_not_key = "old_not",
00480                                          dst_key = "new", dst_not_key = "new_not";
00481                 const config& cfg = **rule;
00482                 const t_translation::t_list& src = t_translation::read_list(cfg[src_key]);
00483 
00484                 if(!src.empty() && t_translation::terrain_matches(current, src) == false) {
00485                     continue;
00486                 }
00487 
00488                 const t_translation::t_list& src_not = t_translation::read_list(cfg[src_not_key]);
00489 
00490                 if(!src_not.empty() && t_translation::terrain_matches(current, src_not)) {
00491                     continue;
00492                 }
00493 
00494                 const t_translation::t_list& dst = t_translation::read_list(cfg[dst_key]);
00495 
00496                 if(!dst.empty() && t_translation::terrain_matches(t, dst) == false) {
00497                     continue;
00498                 }
00499 
00500                 const t_translation::t_list& dst_not = t_translation::read_list(cfg[dst_not_key]);
00501 
00502                 if(!dst_not.empty() && t_translation::terrain_matches(t, dst_not)) {
00503                     continue;
00504                 }
00505 
00506                 break;
00507             }
00508 
00509 
00510             if(rule != rules.end()) {
00511                 const config& cfg = **rule;
00512                 const t_translation::t_list& terrain = t_translation::read_list(cfg["terrain"]);
00513 
00514                 tmerge_mode mode = BOTH;
00515                 if (cfg["layer"] == "base") {
00516                     mode = BASE;
00517                 }
00518                 else if (cfg["layer"] == "overlay") {
00519                     mode = OVERLAY;
00520                 }
00521 
00522                 t_translation::t_terrain new_terrain = t;
00523                 if(!terrain.empty()) {
00524                     new_terrain = terrain[0];
00525                 }
00526                 
00527                 if(!utils::string_bool(cfg["use_old"])) {
00528                     set_terrain(location(x2,y2), new_terrain, mode, utils::string_bool(cfg["replace_if_failed"]));
00529                 }
00530 
00531             } else {
00532                 set_terrain(location(x2,y2),t);
00533             }
00534         }
00535     }
00536 
00537     for(const location* pos = m.startingPositions_;
00538             pos != m.startingPositions_ + sizeof(m.startingPositions_)/sizeof(*m.startingPositions_);
00539             ++pos) {
00540 
00541         if(pos->valid()) {
00542             startingPositions_[pos - m.startingPositions_] = *pos;
00543         }
00544     }
00545 }
00546 
00547 t_translation::t_terrain gamemap::get_terrain(const gamemap::location& loc) const
00548 {
00549 
00550     if(on_board(loc, true)) {
00551         return tiles_[loc.x + border_size_][loc.y + border_size_];
00552     }
00553 
00554     const std::map<location, t_translation::t_terrain>::const_iterator itor = borderCache_.find(loc);
00555     if(itor != borderCache_.end())
00556         return itor->second;
00557 
00558     // If not on the board, decide based on what surrounding terrain is
00559     t_translation::t_terrain items[6];
00560     int nitems = 0;
00561 
00562     location adj[6];
00563     get_adjacent_tiles(loc,adj);
00564     for(int n = 0; n != 6; ++n) {
00565         if(on_board(adj[n])) {
00566             items[nitems] = tiles_[adj[n].x][adj[n].y];
00567             ++nitems;
00568         } else {
00569             // If the terrain is off map but already in the border cache, 
00570             // this will be used to determine the terrain. 
00571             // This avoids glitches
00572             // * on map with an even width in the top right corner
00573             // * on map with an odd height in the bottom left corner.
00574             // It might also change the result on other map and become random,
00575             // but the border tiles will be determined in the future, so then
00576             // this will no longer be used in the game 
00577             // (The editor will use this feature to expand maps in a better way).
00578             std::map<location, t_translation::t_terrain>::const_iterator itor =
00579                 borderCache_.find(adj[n]);
00580 
00581             // Only add if it is in the cache and a valid terrain
00582             if(itor != borderCache_.end() &&
00583                     itor->second != t_translation::NONE_TERRAIN)  {
00584 
00585                 items[nitems] = itor->second;
00586                 ++nitems;
00587             }
00588         }
00589 
00590     }
00591 
00592     // Count all the terrain types found, 
00593     // and see which one is the most common, and use it.
00594     t_translation::t_terrain used_terrain;
00595     int terrain_count = 0;
00596     for(int i = 0; i != nitems; ++i) {
00597         if(items[i] != used_terrain && !is_village(items[i]) && !is_keep(items[i])) {
00598             const int c = std::count(items+i+1,items+nitems,items[i]) + 1;
00599             if(c > terrain_count) {
00600                 used_terrain = items[i];
00601                 terrain_count = c;
00602             }
00603         }
00604     }
00605 
00606     borderCache_.insert(std::pair<location, t_translation::t_terrain>(loc,used_terrain));
00607     return used_terrain;
00608 
00609 }
00610 
00611 const gamemap::location& gamemap::starting_position(int n) const
00612 {
00613     if(size_t(n) < sizeof(startingPositions_)/sizeof(*startingPositions_)) {
00614         return startingPositions_[n];
00615     } else {
00616         static const gamemap::location null_loc;
00617         return null_loc;
00618     }
00619 }
00620 
00621 int gamemap::num_valid_starting_positions() const
00622 {
00623     const int res = is_starting_position(gamemap::location());
00624     if(res == -1)
00625         return num_starting_positions()-1;
00626     else
00627         return res;
00628 }
00629 
00630 int gamemap::is_starting_position(const gamemap::location& loc) const
00631 {
00632     const gamemap::location* const beg = startingPositions_+1;
00633     const gamemap::location* const end = startingPositions_+num_starting_positions();
00634     const gamemap::location* const pos = std::find(beg,end,loc);
00635 
00636     return pos == end ? -1 : pos - beg;
00637 }
00638 
00639 void gamemap::set_starting_position(int side, const gamemap::location& loc)
00640 {
00641     if(side >= 0 && side < num_starting_positions()) {
00642         startingPositions_[side] = loc;
00643     }
00644 }
00645 
00646 bool gamemap::on_board(const location& loc, const bool include_border) const
00647 {
00648     if(!include_border) {
00649         return loc.valid() && loc.x < w_ && loc.y < h_;
00650     } else if(tiles_.empty()) {
00651         return false;
00652     } else {
00653         return loc.x >= (0 - border_size_) && loc.x < (w_ + border_size_) && 
00654             loc.y >= (0 - border_size_) && loc.y < (h_ + border_size_);
00655     }
00656 }
00657 
00658 const terrain_type& gamemap::get_terrain_info(const t_translation::t_terrain terrain) const
00659 {
00660     static const terrain_type default_terrain;
00661     const std::map<t_translation::t_terrain,terrain_type>::const_iterator i =
00662         tcodeToTerrain_.find(terrain);
00663 
00664     if(i != tcodeToTerrain_.end())
00665         return i->second;
00666     else
00667         return default_terrain;
00668 }
00669 
00670 bool gamemap::location::matches_range(const std::string& xloc, const std::string &yloc) const
00671 {
00672     if(std::find(xloc.begin(),xloc.end(),',') != xloc.end()
00673     || std::find(yloc.begin(),yloc.end(),',') != yloc.end()) {
00674         std::vector<std::string> xlocs = utils::split(xloc);
00675         std::vector<std::string> ylocs = utils::split(yloc);
00676 
00677         size_t size;
00678         for(size = xlocs.size(); size < ylocs.size(); ++size) {
00679             xlocs.push_back("");
00680         }
00681         while(size > ylocs.size()) {
00682             ylocs.push_back("");
00683         }
00684         for(size_t i = 0; i != size; ++i) {
00685             if(matches_range(xlocs[i],ylocs[i]))
00686                 return true;
00687         }
00688         return false;
00689     }
00690     if(!xloc.empty()) {
00691         const std::string::const_iterator dash =
00692                      std::find(xloc.begin(),xloc.end(),'-');
00693         if(dash != xloc.end()) {
00694             const std::string beg(xloc.begin(),dash);
00695             const std::string end(dash+1,xloc.end());
00696 
00697             const int bot = atoi(beg.c_str()) - 1;
00698             const int top = atoi(end.c_str()) - 1;
00699 
00700             if(x < bot || x > top)
00701                 return false;
00702         } else {
00703             const int xval = atoi(xloc.c_str()) - 1;
00704             if(xval != x)
00705                 return false;
00706         }
00707     }
00708     if(!yloc.empty()) {
00709         const std::string::const_iterator dash =
00710                      std::find(yloc.begin(),yloc.end(),'-');
00711 
00712         if(dash != yloc.end()) {
00713             const std::string beg(yloc.begin(),dash);
00714             const std::string end(dash+1,yloc.end());
00715 
00716             const int bot = atoi(beg.c_str()) - 1;
00717             const int top = atoi(end.c_str()) - 1;
00718 
00719             if(y < bot || y > top)
00720                 return false;
00721         } else {
00722             const int yval = atoi(yloc.c_str()) - 1;
00723             if(yval != y)
00724                 return false;
00725         }
00726     }
00727     return true;
00728 }
00729 
00730 void gamemap::set_terrain(const gamemap::location& loc, const t_translation::t_terrain terrain, const tmerge_mode mode, bool replace_if_failed) {
00731     if(!on_board(loc, true)) {
00732         // off the map: ignore request
00733         return;
00734     }
00735 
00736     t_translation::t_terrain new_terrain = merge_terrains(get_terrain(loc), terrain, mode, replace_if_failed);
00737 
00738     if(new_terrain == t_translation::NONE_TERRAIN) {
00739         return;
00740     }
00741 
00742     if(on_board(loc, false)) {
00743         const bool old_village = is_village(loc);
00744         const bool new_village = is_village(new_terrain);
00745 
00746         if(old_village && !new_village) {
00747             villages_.erase(std::remove(villages_.begin(),villages_.end(),loc),villages_.end());
00748         } else if(!old_village && new_village) {
00749             villages_.push_back(loc);
00750         }
00751     }
00752 
00753     tiles_[loc.x + border_size_][loc.y + border_size_] = new_terrain;
00754 
00755     // Update the off-map autogenerated tiles
00756     location adj[6];
00757     get_adjacent_tiles(loc,adj);
00758 
00759     for(int n = 0; n < 6; ++n) {
00760         remove_from_border_cache(adj[n]);
00761     }
00762 }
00763 
00764 std::vector<gamemap::location> parse_location_range(const std::string& x, const std::string& y,
00765                                                     const gamemap *const map)
00766 {
00767     std::vector<gamemap::location> res;
00768     const std::vector<std::string> xvals = utils::split(x);
00769     const std::vector<std::string> yvals = utils::split(y);
00770 
00771     for(unsigned int i = 0; i < xvals.size() || i < yvals.size(); ++i) {
00772         std::pair<int,int> xrange, yrange;
00773 
00774         // x
00775         if(i < xvals.size()) {
00776             xrange = utils::parse_range(xvals[i]);
00777         } else if (map != NULL) {
00778             xrange.first = 1;
00779             xrange.second = map->w();
00780         } else {
00781             break;
00782         }
00783 
00784         // y
00785         if(i < yvals.size()) {
00786             yrange = utils::parse_range(yvals[i]);
00787         } else if (map != NULL) {
00788             yrange.first = 1;
00789             yrange.second = map->h();
00790         } else {
00791             break;
00792         }
00793 
00794         for(int x = xrange.first; x <= xrange.second; ++x) {
00795             for(int y = yrange.first; y <= yrange.second; ++y) {
00796                 res.push_back(gamemap::location(x-1,y-1));
00797             }
00798         }
00799     }
00800     return res;
00801 }
00802 
00803 const std::map<t_translation::t_terrain, size_t>& gamemap::get_weighted_terrain_frequencies() const
00804 {
00805     if(terrainFrequencyCache_.empty() == false) {
00806         return terrainFrequencyCache_;
00807     }
00808 
00809     const location center(w()/2,h()/2);
00810 
00811     const size_t furthest_distance = distance_between(location(0,0),center);
00812 
00813     const size_t weight_at_edge = 100;
00814     const size_t additional_weight_at_center = 200;
00815 
00816     for(size_t i = 0; i != size_t(w()); ++i) {
00817         for(size_t j = 0; j != size_t(h()); ++j) {
00818             const size_t distance = distance_between(location(i,j),center);
00819             terrainFrequencyCache_[(*this)[i][j]] += weight_at_edge + 
00820                 (furthest_distance-distance)*additional_weight_at_center;
00821         }
00822     }
00823 
00824     return terrainFrequencyCache_;
00825 }
00826 
00827 bool gamemap::try_merge_terrains(const t_translation::t_terrain terrain) {
00828 
00829     if(tcodeToTerrain_.count(terrain) == 0) {
00830         const std::map<t_translation::t_terrain, terrain_type>::const_iterator base_iter = 
00831             tcodeToTerrain_.find(t_translation::t_terrain(terrain.base, t_translation::NO_LAYER));
00832         const std::map<t_translation::t_terrain, terrain_type>::const_iterator overlay_iter = 
00833             tcodeToTerrain_.find(t_translation::t_terrain(t_translation::NO_LAYER, terrain.overlay));
00834         
00835         if(base_iter == tcodeToTerrain_.end() || overlay_iter == tcodeToTerrain_.end()) {
00836             return false;
00837         }
00838         
00839         terrain_type new_terrain(base_iter->second, overlay_iter->second); 
00840         terrainList_.push_back(new_terrain.number());
00841         tcodeToTerrain_.insert(std::pair<t_translation::t_terrain, terrain_type>(
00842                                    new_terrain.number(), new_terrain));
00843         return true;
00844     }
00845     return true; // Terrain already exists, nothing to do
00846 }
00847 
00848 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) {
00849     t_translation::t_terrain result = t_translation::NONE_TERRAIN;
00850 
00851     if(mode == OVERLAY) {
00852         const t_translation::t_terrain t = t_translation::t_terrain(old_t.base, new_t.overlay);
00853         if (try_merge_terrains(t)) {
00854             result = t;
00855         }
00856     }
00857     else if(mode == BASE) {
00858         const t_translation::t_terrain t = t_translation::t_terrain(new_t.base, old_t.overlay);
00859         if (try_merge_terrains(t)) {
00860             result = t;
00861         }
00862     }
00863     else if(mode == BOTH && new_t.base != t_translation::NO_LAYER) {
00864         // We need to merge here, too, because the dest terrain might be a combined one.
00865         if (try_merge_terrains(new_t)) {
00866             result = new_t;
00867         }
00868     }
00869     
00870     // if merging of overlay and base failed, and replace_if_failed is set,
00871     // replace the terrain with the complete new terrain (if given)
00872     // or with (default base)^(new overlay)
00873     if(result == t_translation::NONE_TERRAIN && replace_if_failed && tcodeToTerrain_.count(new_t) > 0) {
00874         if(new_t.base != t_translation::NO_LAYER) {
00875             // Same as above
00876             if (try_merge_terrains(new_t)) {
00877                 result = new_t;
00878             }
00879         }   
00880         else if (get_terrain_info(new_t).default_base() != t_translation::NONE_TERRAIN) {
00881             result = get_terrain_info(new_t).terrain_with_default_base();
00882         }
00883     }
00884     return result;
00885 }

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs