void gamemap::set_terrain(const map_location& loc, const t_translation::t_terrain terrain, const tmerge_mode mode, bool replace_if_failed) { if(!on_board_with_border(loc)) { // off the map: ignore request return; } t_translation::t_terrain new_terrain = merge_terrains(get_terrain(loc), terrain, mode, replace_if_failed); if(new_terrain == t_translation::NONE_TERRAIN) { return; } if(on_board(loc)) { const bool old_village = is_village(loc); const bool new_village = is_village(new_terrain); if(old_village && !new_village) { villages_.erase(std::remove(villages_.begin(),villages_.end(),loc),villages_.end()); } else if(!old_village && new_village) { villages_.push_back(loc); } } tiles_[loc.x + border_size_][loc.y + border_size_] = new_terrain; // Update the off-map autogenerated tiles map_location adj[6]; get_adjacent_tiles(loc,adj); for(int n = 0; n < 6; ++n) { remove_from_border_cache(adj[n]); } }
void gamemap::set_terrain(const map_location& loc, const t_translation::terrain_code & terrain, const terrain_type_data::merge_mode mode, bool replace_if_failed) { if(!on_board_with_border(loc)) { DBG_G << "set_terrain: " << loc << " is not on the map.\n"; // off the map: ignore request return; } t_translation::terrain_code new_terrain = tdata_->merge_terrains(get_terrain(loc), terrain, mode, replace_if_failed); if(new_terrain == t_translation::NONE_TERRAIN) { return; } if(on_board(loc)) { const bool old_village = is_village(loc); const bool new_village = tdata_->is_village(new_terrain); if(old_village && !new_village) { villages_.erase(std::remove(villages_.begin(),villages_.end(),loc),villages_.end()); } else if(!old_village && new_village) { villages_.push_back(loc); } } (*this)[loc] = new_terrain; }
bool is_village(const map_location& loc) const { return on_board(loc) && is_village(get_terrain(loc)); }
t_translation::t_terrain gamemap::get_terrain(const map_location& loc) const { if(on_board_with_border(loc)) { return tiles_[loc.x + border_size_][loc.y + border_size_]; } const std::map<map_location, t_translation::t_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 t_translation::t_terrain items[6]; int nitems = 0; map_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; } else { // If the terrain is off map but already in the border cache, // this will be used to determine the terrain. // This avoids glitches // * on map with an even width in the top right corner // * on map with an odd height in the bottom left corner. // It might also change the result on other map and become random, // but the border tiles will be determined in the future, so then // this will no longer be used in the game // (The editor will use this feature to expand maps in a better way). std::map<map_location, t_translation::t_terrain>::const_iterator itor = borderCache_.find(adj[n]); // Only add if it is in the cache and a valid terrain if(itor != borderCache_.end() && itor->second != t_translation::NONE_TERRAIN) { items[nitems] = itor->second; ++nitems; } } } // Count all the terrain types found, // and see which one is the most common, and use it. t_translation::t_terrain used_terrain; 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<map_location, t_translation::t_terrain>(loc,used_terrain)); return used_terrain; }
void gamemap::read(const std::string& data) { // Initial stuff tiles_.clear(); villages_.clear(); std::fill(startingPositions_, startingPositions_ + sizeof(startingPositions_) / sizeof(*startingPositions_), map_location()); std::map<int, t_translation::coordinate> starting_positions; if(data.empty()) { w_ = 0; h_ = 0; return; } // Test whether there is a header section size_t header_offset = data.find("\n\n"); if(header_offset == std::string::npos) { // For some reason Windows will fail to load a file with \r\n // lineending properly no problems on Linux with those files. // This workaround fixes the problem the copy later will copy // the second \r\n to the map, but that's no problem. header_offset = data.find("\r\n\r\n"); } const size_t comma_offset = data.find(","); // The header shouldn't contain commas, so if the comma is found // before the header, we hit a \n\n inside or after a map. // This is no header, so don't parse it as it would be. VALIDATE( !(header_offset == std::string::npos || comma_offset < header_offset), _("A map without a header is not supported")); std::string header_str(std::string(data, 0, header_offset + 1)); config header; ::read(header, header_str); border_size_ = lexical_cast_default<int>(header["border_size"], 0); const std::string usage = header["usage"]; utils::string_map symbols; symbols["border_size_key"] = "border_size"; symbols["usage_key"] = "usage"; symbols["usage_val"] = usage; const std::string msg = "'$border_size_key|' should be " "'$border_size_val|' when '$usage_key| = $usage_val|'"; if(usage == "map") { usage_ = IS_MAP; symbols["border_size_val"] = "1"; VALIDATE(border_size_ == 1, vgettext(msg.c_str(), symbols)); } else if(usage == "mask") { usage_ = IS_MASK; symbols["border_size_val"] = "0"; VALIDATE(border_size_ == 0, vgettext(msg.c_str(), symbols)); } else if(usage == "") { throw incorrect_map_format_exception("Map has a header but no usage"); } else { std::string msg = "Map has a header but an unknown usage:" + usage; throw incorrect_map_format_exception(msg.c_str()); } /* The third parameter is required for MSVC++ 6.0 */ const std::string& map = std::string(data, header_offset + 2, std::string::npos); try { tiles_ = t_translation::read_game_map(map, starting_positions); } catch(t_translation::error& e) { // We re-throw the error but as map error. // Since all codepaths test for this, it's the least work. throw incorrect_map_format_exception(e.message); } // Convert the starting positions to the array std::map<int, t_translation::coordinate>::const_iterator itor = starting_positions.begin(); for(; itor != starting_positions.end(); ++itor) { // Check for valid position, // the first valid position is 1, // so the offset 0 in the array is never used. if(itor->first < 1 || itor->first >= MAX_PLAYERS+1) { std::stringstream ss; ss << "Starting position " << itor->first << " out of range\n"; ERR_CF << ss.str(); ss << "The map cannot be loaded."; throw incorrect_map_format_exception(ss.str().c_str()); } // Add to the starting position array startingPositions_[itor->first] = map_location(itor->second.x - 1, itor->second.y - 1); } // Post processing on the map total_width_ = tiles_.size(); total_height_ = total_width_ > 0 ? tiles_[0].size() : 0; w_ = total_width_ - 2 * border_size_; h_ = total_height_ - 2 * border_size_; for(int x = 0; x < total_width_; ++x) { for(int y = 0; y < total_height_; ++y) { // Is the terrain valid? if(tcodeToTerrain_.count(tiles_[x][y]) == 0) { if(!try_merge_terrains(tiles_[x][y])) { std::stringstream ss; ss << "Illegal character in map: (" << t_translation::write_terrain_code(tiles_[x][y]) << ") '" << tiles_[x][y] << "'\n"; ERR_CF << ss.str(); ss << "The map cannot be loaded."; throw incorrect_map_format_exception(ss.str().c_str()); } } // Is it a village? if(x >= border_size_ && y >= border_size_ && x < total_width_-border_size_ && y < total_height_-border_size_ && is_village(tiles_[x][y])) { villages_.push_back(map_location(x-border_size_, y-border_size_)); } } } }
void gamemap::read(const std::string& data, const bool allow_invalid, int border_size, std::string usage) { // Initial stuff border_size_ = border_size; set_usage(usage); tiles_.clear(); villages_.clear(); std::fill(startingPositions_, startingPositions_ + sizeof(startingPositions_) / sizeof(*startingPositions_), map_location()); std::map<int, t_translation::coordinate> starting_positions; if(data.empty()) { w_ = 0; h_ = 0; total_width_ = 0; total_height_ = 0; if(allow_invalid) return; } int offset = read_header(data); const std::string& data_only = std::string(data, offset); try { tiles_ = t_translation::read_game_map(data_only, starting_positions); } catch(t_translation::error& e) { // We re-throw the error but as map error. // Since all codepaths test for this, it's the least work. throw incorrect_map_format_error(e.message); } // Convert the starting positions to the array std::map<int, t_translation::coordinate>::const_iterator itor = starting_positions.begin(); for(; itor != starting_positions.end(); ++itor) { // Check for valid position, // the first valid position is 1, // so the offset 0 in the array is never used. if(itor->first < 1 || itor->first >= MAX_PLAYERS+1) { std::stringstream ss; ss << "Starting position " << itor->first << " out of range\n"; ERR_CF << ss.str(); ss << "The map cannot be loaded."; throw incorrect_map_format_error(ss.str().c_str()); } // Add to the starting position array startingPositions_[itor->first] = map_location(itor->second.x - 1, itor->second.y - 1); } // Post processing on the map total_width_ = tiles_.size(); total_height_ = total_width_ > 0 ? tiles_[0].size() : 0; w_ = total_width_ - 2 * border_size_; h_ = total_height_ - 2 * border_size_; //Disabled since there are callcases which pass along a valid map header but empty //map data. Still, loading (and actually applying) an empty map causes problems later on. //Other callcases which need to load a dummy map use completely empty data :(. //VALIDATE((w_ >= 1 && h_ >= 1), "A map needs at least 1 tile, the map cannot be loaded."); for(int x = 0; x < total_width_; ++x) { for(int y = 0; y < total_height_; ++y) { // Is the terrain valid? if(tcodeToTerrain_.count(tiles_[x][y]) == 0) { if(!try_merge_terrains(tiles_[x][y])) { std::stringstream ss; ss << "Illegal tile in map: (" << t_translation::write_terrain_code(tiles_[x][y]) << ") '" << tiles_[x][y] << "'\n"; ERR_CF << ss.str(); ss << "The map cannot be loaded."; throw incorrect_map_format_error(ss.str().c_str()); } } // Is it a village? if(x >= border_size_ && y >= border_size_ && x < total_width_-border_size_ && y < total_height_-border_size_ && is_village(tiles_[x][y])) { villages_.push_back(map_location(x-border_size_, y-border_size_)); } } } }