/* $Id$ */ /* Copyright (C) 2006 - 2009 by Mark de Wever Part of the Battle for Wesnoth Project http://www.wesnoth.org/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 or at your option any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the COPYING file for more details. */ /** * @file terrain_translation.cpp * Routines for terrain-conversion. */ #define GETTEXT_DOMAIN "wesnoth-lib" #include "global.hpp" #include "gettext.hpp" #include "log.hpp" #include "terrain_translation.hpp" #include "serialization/string_utils.hpp" #include "wml_exception.hpp" #define ERR_G LOG_STREAM(err, general) #define WRN_G LOG_STREAM(warn, general) namespace t_translation { /***************************************************************************************/ // forward declaration of internal functions // The low level convertors, // These function are the ones which know about the internal format. // All other functions are unaware of the internal format. /** * Get the mask for a single layer. * * @param terrain 1 layer of a terrain, might have a wildcard. * * @return Mask for that layer. */ static t_layer get_layer_mask_(t_layer terrain); //inlined /** * Gets a mask for a terrain, this mask is used for wildcard matching. * * @param terrain The terrain which might have a wildcard. * * @return The mask for this terrain. */ static t_terrain get_mask_(const t_terrain& terrain); /** * Converts a string to a layer. * * @param str The terrain string to convert, but needs to be * sanitized so no spaces and only the terrain to convert. * * @return The converted layer. */ static t_layer string_to_layer_(const std::string& str); /** * Converts a terrain string to a number. * @param str The terrain string with an optional number. * @param start_position Returns the start_position, the caller should * set it on -1 and it's only changed it there is * a starting position found. * @param filler If the terrain has only 1 layer then the filler * will be used as the second layer. * * @return The terrain code found in the string if no * valid terrain is found VOID will be returned. */ static t_terrain string_to_number_(std::string str, int& start_position, const t_layer filler); static t_terrain string_to_number_(const std::string& str, const t_layer filler = NO_LAYER); /** * Converts a terrain number to a string * * @param terrain The terrain number to convert. * @param start_position The starting position, if smaller than 0 * it's ignored else it's written. * @param min_size Padds the results with spaces if required, * until the result has a length of min_size. * * @return The converted string, if no starting * position given it's padded to 4 chars else * padded to 7 chars. */ static std::string number_to_string_(t_terrain terrain, const int start_position, const size_t min_size); static std::string number_to_string_(t_terrain terrain, const int start_position = -1); /** * Converts a terrain string to a number for the builder. * The translation rules differ from the normal conversion rules * * @param str The terrain string. * * @return Number for the builder map. */ static t_terrain string_to_builder_number_(std::string str); /***************************************************************************************/ const t_terrain OFF_MAP_USER = string_to_number_("_off^_usr"); const t_terrain VOID_TERRAIN = string_to_number_("_s"); const t_terrain FOGGED = string_to_number_("_f"); const t_terrain HUMAN_CASTLE = string_to_number_("Ch"); const t_terrain HUMAN_KEEP = string_to_number_("Kh"); const t_terrain SHALLOW_WATER = string_to_number_("Ww"); const t_terrain DEEP_WATER = string_to_number_("Wo"); const t_terrain GRASS_LAND = string_to_number_("Gg"); const t_terrain FOREST = string_to_number_("Gg^Ff"); const t_terrain MOUNTAIN = string_to_number_("Mm"); const t_terrain HILL = string_to_number_("Hh"); const t_terrain CAVE_WALL = string_to_number_("Xu"); const t_terrain CAVE = string_to_number_("Uu"); const t_terrain UNDERGROUND_VILLAGE = string_to_number_("Uu^Vu"); const t_terrain DWARVEN_CASTLE = string_to_number_("Cud"); const t_terrain DWARVEN_KEEP = string_to_number_("Kud"); const t_terrain PLUS = string_to_number_("+"); const t_terrain MINUS = string_to_number_("-"); const t_terrain NOT = string_to_number_("!"); const t_terrain STAR = string_to_number_("*"); const t_terrain BASE = string_to_number_("_bas"); /***************************************************************************************/ t_terrain::t_terrain(const std::string& b) : base(string_to_layer_(b)), overlay(NO_LAYER) {} t_terrain::t_terrain(const std::string& b, const t_layer o) : base(string_to_layer_(b)), overlay(o) {} t_terrain::t_terrain(const std::string& b, const std::string& o) : base(string_to_layer_(b)), overlay(string_to_layer_(o)) {} t_match::t_match() : terrain(), mask(), masked_terrain(), has_wildcard(false), is_empty(true) {} t_match::t_match(const std::string& str, const t_layer filler) : terrain(t_translation::read_list(str, filler)), mask(), masked_terrain(), has_wildcard(t_translation::has_wildcard(terrain)), is_empty(terrain.empty()) { mask.resize(terrain.size()); masked_terrain.resize(terrain.size()); for(size_t i = 0; i < terrain.size(); i++) { mask[i] = t_translation::get_mask_(terrain[i]); masked_terrain[i] = mask[i] & terrain[i]; } } t_match::t_match(const t_terrain& tcode): terrain(t_list(1, tcode)), mask(), masked_terrain(), has_wildcard(t_translation::has_wildcard(terrain)), is_empty(terrain.empty()) { mask.resize(terrain.size()); masked_terrain.resize(terrain.size()); for(size_t i = 0; i < terrain.size(); i++) { mask[i] = t_translation::get_mask_(terrain[i]); masked_terrain[i] = mask[i] & terrain[i]; } } t_terrain read_terrain_code(const std::string& str, const t_layer filler) { return string_to_number_(str, filler); } std::string write_terrain_code(const t_terrain& tcode) { return number_to_string_(tcode); } t_list read_list(const std::string& str, const t_layer filler) { // Handle an empty string t_list result; if(str.empty()) { return result; } size_t offset = 0; while(offset < str.length()) { // Get a terrain chunk const std::string separators = ","; const size_t pos_separator = str.find_first_of(separators, offset); const std::string terrain = str.substr(offset, pos_separator - offset); // Process the chunk const t_terrain tile = string_to_number_(terrain, filler); // Add the resulting terrain number result.push_back(tile); // Evaluate the separator if(pos_separator == std::string::npos) { offset = str.length(); } else { offset = pos_separator + 1; } } return result; } std::string write_list(const t_list& list) { std::stringstream result; t_list::const_iterator itor = list.begin(); for( ; itor != list.end(); ++itor) { if(itor == list.begin()) { result << number_to_string_(*itor); } else { result << ", " << number_to_string_(*itor); } } return result.str(); } t_map read_game_map(const std::string& str, std::map& starting_positions) { t_map result; size_t offset = 0; size_t x = 0, y = 0, width = 0; // Skip the leading newlines while(offset < str.length() && utils::isnewline(str[offset])) { ++offset; } // Did we get an empty map? if((offset + 1) >= str.length()) { return result; } while(offset < str.length()) { // Get a terrain chunk const std::string separators = ",\n\r"; const size_t pos_separator = str.find_first_of(separators, offset); const std::string terrain = str.substr(offset, pos_separator - offset); // Process the chunk int starting_position = -1; // The gamemap never has a wildcard const t_terrain tile = string_to_number_(terrain, starting_position, NO_LAYER); // Add to the resulting starting position if(starting_position != -1) { if(starting_positions.find(starting_position) != starting_positions.end()) { // Redefine existion position WRN_G << "Starting position " << starting_position << " is redefined.\n"; starting_positions[starting_position].x = x; starting_positions[starting_position].y = y; } else { // Add new position const struct coordinate coord = {x, y}; starting_positions.insert(std::pair(starting_position, coord)); } } // Make space for the new item // NOTE we increase the vector every loop for every x and y. // Profiling with an increase of y with 256 items didn't show // an significant speed increase. // So didn't rework the system to allocate larger vectors at once. if(result.size() <= x) { result.resize(x + 1); } if(result[x].size() <= y) { result[x].resize(y + 1); } // Add the resulting terrain number result[x][y] = tile; // Evaluate the separator if(utils::isnewline(str[pos_separator]) || pos_separator == std::string::npos) { // the first line we set the with the other lines we check the width if(y == 0) { // x contains the offset in the map width = x + 1; } else { if((x + 1) != width ) { ERR_G << "Map not a rectangle error occured at line offset " << y << " position offset " << x << "\n"; throw error("Map not a rectangle."); } } // Prepare next iteration ++y; x = 0; // Avoid in infinite loop if the last line ends without an EOL if(pos_separator == std::string::npos) { offset = str.length(); } else { offset = pos_separator + 1; // Skip the following newlines while(offset < str.length() && utils::isnewline(str[offset])) { ++offset; } } } else { ++x; offset = pos_separator + 1; } } if(x != 0 && (x + 1) != width) { ERR_G << "Map not a rectangle error occured at the end\n"; throw error("Map not a rectangle."); } return result; } std::string write_game_map(const t_map& map, std::map starting_positions) { std::stringstream str; for(size_t y = 0; y < map[0].size(); ++y) { for(size_t x = 0; x < map.size(); ++x) { // If the current location is a starting position, // it needs to be added to the terrain. // After it's found it can't be found again, // so the location is removed from the map. std::map::iterator itor = starting_positions.begin(); int starting_position = -1; for(; itor != starting_positions.end(); ++itor) { if(itor->second.x == x && itor->second.y == y) { starting_position = itor->first; starting_positions.erase(itor); break; } } // Add the separator if(x != 0) { str << ", "; } str << number_to_string_(map[x][y], starting_position, 12); } str << "\n"; } return str.str(); } bool terrain_matches(const t_terrain& src, const t_terrain& dest) { return terrain_matches(src, t_list(1, dest)); } bool terrain_matches(const t_terrain& src, const t_list& dest) { // NOTE we impose some code duplication. // It could have been rewritten to get a match structure // and then call the version with the match structure. // IMO that's some extra overhead to this function // which is not required. Hence the two versions if(dest.empty()) { return false; } #if 0 std::cerr << std::hex << "src = " << src.base << "^" << src.overlay << "\t" << src_mask.base << "^" << src_mask.overlay << "\t" << masked_src.base << "^" << masked_src.overlay << "\t" << src_has_wildcard << "\n"; #endif bool result = true; t_list::const_iterator itor = dest.begin(); // Try to match the terrains if matched jump out of the loop. for(; itor != dest.end(); ++itor) { // Match wildcard if(*itor == STAR) { return result; } // Match inverse symbol if(*itor == NOT) { result = !result; continue; } // Full match if(src == *itor) { return result; } // Does the destination wildcard match const t_terrain dest_mask = get_mask_(*itor); const t_terrain masked_dest = (*itor & dest_mask); const bool dest_has_wildcard = has_wildcard(*itor); #if 0 std::cerr << std::hex << "dest= " << itor->base << "^" << itor->overlay << "\t" << dest_mask.base << "^" << dest_mask.overlay << "\t" << masked_dest.base << "^" << masked_dest.overlay << "\t" << dest_has_wildcard << "\n"; #endif if(dest_has_wildcard && (src.base & dest_mask.base) == masked_dest.base && (src.overlay & dest_mask.overlay) == masked_dest.overlay) { return result; } /* Test code */ /* if(src_has_wildcard && dest_has_wildcard && ( ( get_layer_mask_(itor->base) != NO_LAYER && get_layer_mask_(src.overlay) != NO_LAYER && (src.base & dest_mask.base) == masked_dest.base && (itor->overlay & src_mask.overlay) == masked_src.overlay ) || ( get_layer_mask_(itor->overlay) != NO_LAYER && get_layer_mask_(src.base) != NO_LAYER && (src.overlay & dest_mask.overlay) == masked_dest.overlay && (itor->base & src_mask.base) == masked_src.base ))) { return result; } */ } // No match, return the inverse of the result return !result; } // This routine is used for the terrain building, // so it's one of the delays while loading a map. // This routine is optimized a bit at the loss of readability. bool terrain_matches(const t_terrain& src, const t_match& dest) { if(dest.is_empty) { return false; } bool result = true; // Try to match the terrains if matched jump out of the loop. // We loop on the dest.terrain since the iterator is faster than operator[]. // The i holds the value for operator[]. // Since dest.mask and dest.masked_terrain need to be in sync, // they are less often looked up, so no iterator for them. size_t i = 0; t_list::const_iterator end = dest.terrain.end(); for(t_list::const_iterator terrain_itor = dest.terrain.begin(); terrain_itor != end; ++i, ++terrain_itor) { // Match wildcard if(*terrain_itor == STAR) { return result; } // Match inverse symbol if(*terrain_itor == NOT) { result = !result; continue; } // Full match if(*terrain_itor == src) { return result; } // Does the destination wildcard match if(dest.has_wildcard && (src.base & dest.mask[i].base) == dest.masked_terrain[i].base && (src.overlay & dest.mask[i].overlay) == dest.masked_terrain[i].overlay) { return result; } /* Test code */ /* if(src_has_wildcard && has_wildcard(*terrain_itor) && ( ( get_layer_mask_(terrain_itor->base) != NO_LAYER && get_layer_mask_(src.overlay) != NO_LAYER && (src.base & dest.mask[i].base) == dest.masked_terrain[i].base && (terrain_itor->overlay & src_mask.overlay) == masked_src.overlay ) || ( get_layer_mask_(terrain_itor->overlay) != NO_LAYER && get_layer_mask_(src.base) != NO_LAYER && (src.overlay & dest.mask[i].overlay) == dest.masked_terrain[i].overlay && (terrain_itor->base & src_mask.base) == masked_src.base ))) { return result; } */ } // No match, return the inverse of the result return !result; } bool has_wildcard(const t_terrain& tcode) { if(tcode.overlay == NO_LAYER) { return get_layer_mask_(tcode.base) != NO_LAYER; } else { return get_layer_mask_(tcode.base) != NO_LAYER || get_layer_mask_(tcode.overlay) != NO_LAYER; } } bool has_wildcard(const t_list& list) { if(list.empty()) { return false; } // Test all items for a wildcard t_list::const_iterator itor = list.begin(); for(; itor != list.end(); ++itor) { if(has_wildcard(*itor)) { return true; } } // No wildcard found return false; } t_map read_builder_map(const std::string& str) { size_t offset = 0; t_map result; // Skip the leading newlines while(offset < str.length() && utils::isnewline(str[offset])) { ++offset; } // Did we get an empty map? if((offset + 1) >= str.length()) { return result; } size_t x = 0, y = 0; while(offset < str.length()) { // Get a terrain chunk const std::string separators = ",\n\r"; const size_t pos_separator = str.find_first_of(separators, offset); std::string terrain = ""; // Make sure we didn't hit an empty chunk // which is allowed if(pos_separator != offset) { terrain = str.substr(offset, pos_separator - offset); } // Process the chunk const t_terrain tile = string_to_builder_number_(terrain); // Make space for the new item if(result.size() <= y) { result.resize(y + 1); } if(result[y].size() <= x) { result[y].resize(x + 1); } // Add the resulting terrain number, result[y][x] = tile; // evaluate the separator if(pos_separator == std::string::npos) { // Probably not required to change the value, // but be sure the case should be handled at least. // I'm not sure how it is defined in the standard, // but here it's defined at max u32 which with +1 gives 0 // and make a nice infinite loop. offset = str.length(); } else if(utils::isnewline(str[pos_separator])) { // Prepare next iteration ++y; x = 0; offset = pos_separator + 1; // Skip the following newlines while(offset < str.length() && utils::isnewline(str[offset])) { ++offset; } } else { ++x; offset = pos_separator + 1; } } return result; } /***************************************************************************************/ // Internal inline t_layer get_layer_mask_(t_layer terrain) { // Test for the star 0x2A in every position // and return the appropriate mask /* * This is what the code intents to do, but in order to gain some more * speed it's changed to the code below, which does the same but faster. * This routine is used often in the builder and the speedup is noticable. */ if((terrain & 0xFF000000) == 0x2A000000) return 0x00000000; if((terrain & 0x00FF0000) == 0x002A0000) return 0xFF000000; if((terrain & 0x0000FF00) == 0x00002A00) return 0xFFFF0000; if((terrain & 0x000000FF) == 0x0000002A) return 0xFFFFFF00; /* Uint8 *ptr = (Uint8 *) &terrain; if(ptr[3] == 0x2A) return 0x00000000; if(ptr[2] == 0x2A) return 0xFF000000; if(ptr[1] == 0x2A) return 0xFFFF0000; if(ptr[0] == 0x2A) return 0xFFFFFF00; */ // no star found return the default return 0xFFFFFFFF; } static t_terrain get_mask_(const t_terrain& terrain) { if(terrain.overlay == NO_LAYER) { return t_terrain(get_layer_mask_(terrain.base), 0xFFFFFFFF); } else { return t_terrain(get_layer_mask_(terrain.base), get_layer_mask_(terrain.overlay)); } } static t_layer string_to_layer_(const std::string& str) { if (str.size() == 0) return NO_LAYER; t_layer result = 0; // Validate the string (Note at the moment the error is caught at another // location and sending a lg::wml_error() but that can be replaced later) VALIDATE(str.size() <= 4, _("A terrain with a string with more " "than 4 characters has been found, the affected terrain is :") + str); // The conversion to int puts the first char // in the highest part of the number. // This will make the wildcard matching // later on a bit easier. for(size_t i = 0; i < 4; ++i) { const unsigned char c = (i < str.length()) ? str[i] : 0; // Clearing the lower area is a nop on i == 0 // so no need for if statement result <<= 8; // Add the result result += c; } return result; } static t_terrain string_to_number_(const std::string& str, const t_layer filler) { int dummy = -1; return string_to_number_(str, dummy, filler); } static t_terrain string_to_number_(std::string str, int& start_position, const t_layer filler) { t_terrain result; // Need to store the orginal string for the error handling. // This has been made to avoid the assertion failure // which happens often and is not too user friendly. const std::string input(str); // Strip the spaces around us const std::string& whitespace = " \t"; str.erase(0, str.find_first_not_of(whitespace)); str.erase(str.find_last_not_of(whitespace) + 1); if(str.empty()) { return result; } // Split if we have 1 space inside size_t offset = str.find(' ', 0); if(offset != std::string::npos) { try { start_position = lexical_cast(str.substr(0, offset)); } catch(bad_lexical_cast&) { return VOID_TERRAIN; } str.erase(0, offset + 1); } offset = str.find('^', 0); if(offset != std::string::npos) { // If either string is longer than 4 characters bail out if(offset > 4 || (str.size() - offset) > 5) { return VOID_TERRAIN; } const std::string base_str(str, 0, offset); const std::string overlay_str(str, offset + 1, str.size()); result = t_terrain(base_str, overlay_str); } else { // If the string is longer than 4 characters bail out if(str.size() > 4) { return VOID_TERRAIN; } result = t_terrain(str, filler); // Ugly hack if(filler == WILDCARD && (result.base == NOT.base || result.base == STAR.base)) { result.overlay = NO_LAYER; } } return result; } static std::string number_to_string_(t_terrain terrain, const int start_position) { std::string result = ""; // Insert the start position if(start_position > 0) { result = str_cast(start_position) + " "; } // Insert the terrain tcode unsigned char tcode[9]; tcode[0] = ((terrain.base & 0xFF000000) >> 24); tcode[1] = ((terrain.base & 0x00FF0000) >> 16); tcode[2] = ((terrain.base & 0x0000FF00) >> 8); tcode[3] = (terrain.base & 0x000000FF); if(terrain.overlay != NO_LAYER) { tcode[4] = '^'; //the layer separator tcode[5] = ((terrain.overlay & 0xFF000000) >> 24); tcode[6] = ((terrain.overlay & 0x00FF0000) >> 16); tcode[7] = ((terrain.overlay & 0x0000FF00) >> 8); tcode[8] = (terrain.overlay & 0x000000FF); } else { // If no second layer, the second layer won't be written, // so no need to initialize that part of the array tcode[4] = 0; } for(int i = 0; i < 9; ++i) { if(tcode[i] != 0 && tcode[i] != 0xFF) { result += tcode[i]; } if(i == 4 && tcode[i] == 0) { // no layer, stop break; } } return result; } static std::string number_to_string_(t_terrain terrain, const int start_position, const size_t min_size) { std::string result = number_to_string_(terrain, start_position); if(result.size() < min_size) { result.resize(min_size, ' '); } return result; } static t_terrain string_to_builder_number_(std::string str) { // Strip the spaces around us const std::string& whitespace = " \t"; str.erase(0, str.find_first_not_of(whitespace)); if(! str.empty()) { str.erase(str.find_last_not_of(whitespace) + 1); } // Empty string is allowed here, so handle it if(str.empty()) { return t_terrain(); } const int number = lexical_cast_default(str, -1); if(number == -1) { // At this point we have a single char // which should be interpreted by the // map builder, so return this number return t_terrain(str[0] << 24, 0); } else { return t_terrain(0, number); } } } // end namespace t_translation #if 0 // small helper rule to test the matching rules // building rule // make terrain_translation.o && g++ terrain_translation.o libwesnoth-core.a -lSDL -o terrain_translation int main(int argc, char** argv) { if(argc > 1) { if(std::string(argv[1]) == "match" && argc == 4) { t_translation::t_terrain src = t_translation::read_terrain_code(std::string(argv[2])); t_translation::t_list dest = t_translation::read_list(std::string(argv[3])); if(t_translation::terrain_matches(src, dest)) { std::cout << "Match\n" ; } else { std::cout << "No match\n"; } } } } #endif