1 /*
2 Copyright (C) 2006 - 2018 by Mark de Wever <koraq@xs4all.nl>
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13 */
14
15 /**
16 * @file
17 * Routines for terrain-conversion.
18 */
19
20 #define GETTEXT_DOMAIN "wesnoth-lib"
21
22 #include "gettext.hpp"
23 #include "lexical_cast.hpp"
24 #include "log.hpp"
25 #include "terrain/translation.hpp"
26 #include "serialization/string_utils.hpp"
27 #include "wml_exception.hpp"
28
29
30 #define ERR_G LOG_STREAM(err, lg::general())
31 #define WRN_G LOG_STREAM(warn, lg::general())
32
33 namespace t_translation {
34
max_map_size()35 int max_map_size() {
36 return 1000; //TODO make this overridable by the user without having to rebuild
37 }
38
39 /***************************************************************************************/
40 // forward declaration of internal functions
41
42 // The low level convertors,
43 // These function are the ones which know about the internal format.
44 // All other functions are unaware of the internal format.
45
46 /**
47 * Get the mask for a single layer.
48 *
49 * @param terrain 1 layer of a terrain, might have a wildcard.
50 *
51 * @return Mask for that layer.
52 */
53 static ter_layer get_layer_mask_(ter_layer terrain); //inlined
54
55 /**
56 * Gets a mask for a terrain, this mask is used for wildcard matching.
57 *
58 * @param terrain The terrain which might have a wildcard.
59 *
60 * @return The mask for this terrain.
61 */
62 static terrain_code get_mask_(const terrain_code& terrain);
63
64 static ter_layer string_to_layer_(const char* begin, const char* end);
65
66 /**
67 * Converts a string to a layer.
68 *
69 * @param str The terrain string to convert, but needs to be
70 * sanitized so no spaces and only the terrain to convert.
71 *
72 * @return The converted layer.
73 */
string_to_layer_(const std::string & str)74 static ter_layer string_to_layer_(const std::string& str)
75 {
76 return string_to_layer_(str.c_str(), str.c_str() + str.size());
77 }
78
79 /**
80 * Converts a terrain string to a number.
81 * @param str The terrain string with an optional number.
82 * @param start_position Returns the start_position, the caller should
83 * set it on -1 and it's only changed it there is
84 * a starting position found.
85 * @param filler If the terrain has only 1 layer then the filler
86 * will be used as the second layer.
87 *
88 * @return The terrain code found in the string if no
89 * valid terrain is found VOID will be returned.
90 */
91 static terrain_code string_to_number_(std::string str, std::string& start_position, const ter_layer filler);
92 static terrain_code string_to_number_(const std::string& str, const ter_layer filler = NO_LAYER);
93
94 /**
95 * Converts a terrain number to a string
96 *
97 * @param terrain The terrain number to convert.
98 * @param start_position The starting position, if smaller than 0
99 * it's ignored else it's written.
100 *
101 * @return The converted string, if no starting
102 * position given it's padded to 4 chars else
103 * padded to 7 chars.
104 */
105 static std::string number_to_string_(terrain_code terrain, const std::string& start_position = "");
106
107 /**
108 * Converts a terrain string to a number for the builder.
109 * The translation rules differ from the normal conversion rules
110 *
111 * @param str The terrain string.
112 *
113 * @return Number for the builder map.
114 */
115 static terrain_code string_to_builder_number_(std::string str);
116
117 /***************************************************************************************/
118
119 const terrain_code OFF_MAP_USER = string_to_number_("_off^_usr");
120
121 const terrain_code VOID_TERRAIN = string_to_number_("_s");
122 const terrain_code FOGGED = string_to_number_("_f");
123
124 const terrain_code HUMAN_CASTLE = string_to_number_("Ch");
125 const terrain_code HUMAN_KEEP = string_to_number_("Kh");
126 const terrain_code SHALLOW_WATER = string_to_number_("Ww");
127 const terrain_code DEEP_WATER = string_to_number_("Wo");
128 const terrain_code GRASS_LAND = string_to_number_("Gg");
129 const terrain_code FOREST = string_to_number_("Gg^Ff");
130 const terrain_code MOUNTAIN = string_to_number_("Mm");
131 const terrain_code HILL = string_to_number_("Hh");
132
133 const terrain_code CAVE_WALL = string_to_number_("Xu");
134 const terrain_code CAVE = string_to_number_("Uu");
135 const terrain_code UNDERGROUND_VILLAGE = string_to_number_("Uu^Vu");
136 const terrain_code DWARVEN_CASTLE = string_to_number_("Cud");
137 const terrain_code DWARVEN_KEEP = string_to_number_("Kud");
138
139 const terrain_code PLUS = string_to_number_("+");
140 const terrain_code MINUS = string_to_number_("-");
141 const terrain_code NOT = string_to_number_("!");
142 const terrain_code STAR = string_to_number_("*");
143 const terrain_code BASE = string_to_number_("_bas");
144
145 const ter_match ALL_OFF_MAP("_off^_usr,*^_fme");
146 const ter_match ALL_FORESTS("F*,*^F*");
147 const ter_match ALL_HILLS("!,*^V*,!,H*");
148 const ter_match ALL_MOUNTAINS("!,*^V*,!,M*"); //excluding impassable mountains
149 const ter_match ALL_SWAMPS("!,*^V*,*^B*,!,S*"); //excluding swamp villages and bridges
150
151 /***************************************************************************************/
152
terrain_code(const std::string & b,ter_layer o)153 terrain_code::terrain_code(const std::string& b, ter_layer o) :
154 base(string_to_layer_(b)), overlay(o)
155 {}
156
terrain_code(const std::string & b,const std::string & o)157 terrain_code::terrain_code(const std::string& b, const std::string& o) :
158 base(string_to_layer_(b)), overlay(string_to_layer_(o))
159 {}
160
ter_match()161 ter_match::ter_match() :
162 terrain(),
163 mask(),
164 masked_terrain(),
165 has_wildcard(false),
166 is_empty(true)
167 {}
168
ter_match(const std::string & str,const ter_layer filler)169 ter_match::ter_match(const std::string& str, const ter_layer filler) :
170 terrain(t_translation::read_list(str, filler)),
171 mask(),
172 masked_terrain(),
173 has_wildcard(t_translation::has_wildcard(terrain)),
174 is_empty(terrain.empty())
175
176 {
177 mask.resize(terrain.size());
178 masked_terrain.resize(terrain.size());
179
180 for(size_t i = 0; i < terrain.size(); i++) {
181 mask[i] = t_translation::get_mask_(terrain[i]);
182 masked_terrain[i] = mask[i] & terrain[i];
183 }
184 }
185
ter_match(const terrain_code & tcode)186 ter_match::ter_match(const terrain_code& tcode):
187 terrain(ter_list(1, tcode)),
188 mask(),
189 masked_terrain(),
190 has_wildcard(t_translation::has_wildcard(terrain)),
191 is_empty(terrain.empty())
192 {
193 mask.resize(terrain.size());
194 masked_terrain.resize(terrain.size());
195
196 for(size_t i = 0; i < terrain.size(); i++) {
197 mask[i] = t_translation::get_mask_(terrain[i]);
198 masked_terrain[i] = mask[i] & terrain[i];
199 }
200 }
201
read_terrain_code(const std::string & str,const ter_layer filler)202 terrain_code read_terrain_code(const std::string& str, const ter_layer filler)
203 {
204 return string_to_number_(str, filler);
205 }
206
write_terrain_code(const terrain_code & tcode)207 std::string write_terrain_code(const terrain_code& tcode)
208 {
209 return number_to_string_(tcode);
210 }
211
read_list(utils::string_view str,const ter_layer filler)212 ter_list read_list(utils::string_view str, const ter_layer filler)
213 {
214 // Handle an empty string
215 ter_list result;
216
217 if(str.empty()) {
218 return result;
219 }
220
221 size_t offset = 0;
222 while(offset < str.length()) {
223
224 // Get a terrain chunk
225 const std::string separators = ",";
226 const size_t pos_separator = str.find_first_of(separators, offset);
227 const std::string terrain = str.substr(offset, pos_separator - offset).to_string();
228
229 // Process the chunk
230 const terrain_code tile = string_to_number_(terrain, filler);
231
232 // Add the resulting terrain number
233 result.push_back(tile);
234
235 // Evaluate the separator
236 if(pos_separator == std::string::npos) {
237 offset = str.length();
238 } else {
239 offset = pos_separator + 1;
240 }
241 }
242
243 return result;
244 }
245
write_list(const ter_list & list)246 std::string write_list(const ter_list& list)
247 {
248 std::stringstream result;
249
250 ter_list::const_iterator itor = list.begin();
251 for( ; itor != list.end(); ++itor) {
252 if(itor == list.begin()) {
253 result << number_to_string_(*itor);
254 } else {
255 result << ", " << number_to_string_(*itor);
256 }
257 }
258
259 return result.str();
260 }
261
get_map_size(const char * begin,const char * end)262 static std::pair<int, int> get_map_size(const char* begin, const char* end)
263 {
264 int w = 1;
265 int h = 0;
266 for (const char* it = begin; it != end;) {
267 int cur_w = 1;
268 ++h;
269
270
271 for (;it != end && (*it != '\n' && *it != '\r'); ++it) {
272 if (*it == ',') {
273 ++cur_w;
274 }
275 }
276 w = std::max(w, cur_w);
277
278 while (it != end && (*it == '\n' || *it == '\r')) {
279 ++it;
280 }
281
282 }
283 return{ w, h };
284 }
285
read_game_map(const std::string & str,starting_positions & starting_positions,coordinate border_offset)286 ter_map read_game_map(const std::string& str, starting_positions& starting_positions, coordinate border_offset)
287 {
288 size_t offset = 0;
289 int x = 0, y = 0, width = 0;
290
291 // Skip the leading newlines
292 while(offset < str.length() && utils::isnewline(str[offset])) {
293 ++offset;
294 }
295
296 // Did we get an empty map?
297 if((offset + 1) >= str.length()) {
298 return ter_map();
299 }
300
301 auto map_size = get_map_size(&str[offset], str.c_str() + str.size());
302 ter_map result(map_size.first, map_size.second);
303
304 while(offset < str.length()) {
305
306 // Get a terrain chunk
307 const std::string separators = ",\n\r";
308 const size_t pos_separator = str.find_first_of(separators, offset);
309 const std::string terrain = str.substr(offset, pos_separator - offset);
310
311 // Process the chunk
312 std::string starting_position;
313 // The gamemap never has a wildcard
314 const terrain_code tile = string_to_number_(terrain, starting_position, NO_LAYER);
315
316 // Add to the resulting starting position
317 if(!starting_position.empty()) {
318 if (starting_positions.left.find(starting_position) != starting_positions.left.end()) {
319 WRN_G << "Starting position " << starting_position << " is redefined." << std::endl;
320 }
321 starting_positions.insert(starting_positions::value_type(starting_position, coordinate(x - border_offset.x, y - border_offset.y)));
322 }
323
324 if(result.w <= x || result.h <= y) {
325 throw error("Map not a rectangle.");
326 }
327
328 // Add the resulting terrain number
329 result.get(x, y) = tile;
330
331 // Evaluate the separator
332 if(pos_separator == std::string::npos || utils::isnewline(str[pos_separator])) {
333 // the first line we set the with the other lines we check the width
334 if(y == 0) {
335 // x contains the offset in the map
336 width = x + 1;
337 } else {
338 if((x + 1) != width ) {
339 ERR_G << "Map not a rectangle error occurred at line offset " << y << " position offset " << x << std::endl;
340 throw error("Map not a rectangle.");
341 }
342 if (y > max_map_size()) {
343 ERR_G << "Map size exceeds limit (y > " << max_map_size() << ")" << std::endl;
344 throw error("Map height limit exceeded.");
345 }
346 }
347
348 // Prepare next iteration
349 ++y;
350 x = 0;
351
352 // Avoid in infinite loop if the last line ends without an EOL
353 if(pos_separator == std::string::npos) {
354 offset = str.length();
355
356 } else {
357
358 offset = pos_separator + 1;
359 // Skip the following newlines
360 while(offset < str.length() && utils::isnewline(str[offset])) {
361 ++offset;
362 }
363 }
364
365 } else {
366 ++x;
367 offset = pos_separator + 1;
368 if (x > max_map_size()) {
369 ERR_G << "Map size exceeds limit (x > " << max_map_size() << ")" << std::endl;
370 throw error("Map width limit exceeded.");
371 }
372 }
373
374 }
375
376 if(x != 0 && (x + 1) != width) {
377 ERR_G << "Map not a rectangle error occurred at the end" << std::endl;
378 throw error("Map not a rectangle.");
379 }
380
381 return result;
382 }
383
write_game_map(const ter_map & map,const starting_positions & starting_positions,coordinate border_offset)384 std::string write_game_map(const ter_map& map, const starting_positions& starting_positions, coordinate border_offset)
385 {
386 std::stringstream str;
387
388 for(int y = 0; y < map.h; ++y) {
389 for(int x = 0; x < map.w; ++x) {
390
391 // If the current location is a starting position,
392 // it needs to be added to the terrain.
393 // After it's found it can't be found again,
394 // so the location is removed from the map.
395 auto itor = starting_positions.right.find(coordinate(x - border_offset.x, y - border_offset.y));
396 std::string starting_position;
397 if (itor != starting_positions.right.end()) {
398 starting_position = itor->second;
399 }
400 // Add the separator
401 if(x != 0) {
402 str << ", ";
403 }
404 str << number_to_string_(map[x][y], starting_position);
405 }
406
407 if (y < map.h -1)
408 str << "\n";
409 }
410
411 return str.str();
412 }
413
terrain_matches(const terrain_code & src,const terrain_code & dest)414 bool terrain_matches(const terrain_code& src, const terrain_code& dest)
415 {
416 return terrain_matches(src, ter_list(1, dest));
417 }
418
terrain_matches(const terrain_code & src,const ter_list & dest)419 bool terrain_matches(const terrain_code& src, const ter_list& dest)
420 {
421 // NOTE we impose some code duplication.
422 // It could have been rewritten to get a match structure
423 // and then call the version with the match structure.
424 // IMO that's some extra overhead to this function
425 // which is not required. Hence the two versions
426 if(dest.empty()) {
427 return false;
428 }
429
430 #if 0
431 std::cerr << std::hex << "src = " << src.base << "^" << src.overlay << "\t"
432 << src_mask.base << "^" << src_mask.overlay << "\t"
433 << masked_src.base << "^" << masked_src.overlay << "\t"
434 << src_has_wildcard << "\n";
435 #endif
436
437 bool result = true;
438 ter_list::const_iterator itor = dest.begin();
439
440 // Try to match the terrains if matched jump out of the loop.
441 for(; itor != dest.end(); ++itor) {
442
443 // Match wildcard
444 if(*itor == STAR) {
445 return result;
446 }
447
448 // Match inverse symbol
449 if(*itor == NOT) {
450 result = !result;
451 continue;
452 }
453
454 // Full match
455 if(src == *itor) {
456 return result;
457 }
458
459 // Does the destination wildcard match
460 const terrain_code dest_mask = get_mask_(*itor);
461 const terrain_code masked_dest = (*itor & dest_mask);
462 const bool dest_has_wildcard = has_wildcard(*itor);
463 #if 0
464 std::cerr << std::hex << "dest= "
465 << itor->base << "^" << itor->overlay << "\t"
466 << dest_mask.base << "^" << dest_mask.overlay << "\t"
467 << masked_dest.base << "^" << masked_dest.overlay << "\t"
468 << dest_has_wildcard << "\n";
469 #endif
470 if(dest_has_wildcard &&
471 (src.base & dest_mask.base) == masked_dest.base &&
472 (src.overlay & dest_mask.overlay) == masked_dest.overlay) {
473 return result;
474 }
475
476 /* Test code */ /*
477 if(src_has_wildcard && dest_has_wildcard && (
478 (
479 get_layer_mask_(itor->base) != NO_LAYER &&
480 get_layer_mask_(src.overlay) != NO_LAYER &&
481 (src.base & dest_mask.base) == masked_dest.base &&
482 (itor->overlay & src_mask.overlay) == masked_src.overlay
483 ) || (
484 get_layer_mask_(itor->overlay) != NO_LAYER &&
485 get_layer_mask_(src.base) != NO_LAYER &&
486 (src.overlay & dest_mask.overlay) == masked_dest.overlay &&
487 (itor->base & src_mask.base) == masked_src.base
488 ))) {
489
490 return result;
491 }
492 */
493 }
494
495 // No match, return the inverse of the result
496 return !result;
497 }
498
499 // This routine is used for the terrain building,
500 // so it's one of the delays while loading a map.
501 // This routine is optimized a bit at the loss of readability.
terrain_matches(const terrain_code & src,const ter_match & dest)502 bool terrain_matches(const terrain_code& src, const ter_match& dest)
503 {
504 if(dest.is_empty) {
505 return false;
506 }
507
508 bool result = true;
509
510 // Try to match the terrains if matched jump out of the loop.
511 // We loop on the dest.terrain since the iterator is faster than operator[].
512 // The i holds the value for operator[].
513 // Since dest.mask and dest.masked_terrain need to be in sync,
514 // they are less often looked up, so no iterator for them.
515 size_t i = 0;
516 ter_list::const_iterator end = dest.terrain.end();
517 for(ter_list::const_iterator terrain_itor = dest.terrain.begin();
518 terrain_itor != end;
519 ++i, ++terrain_itor) {
520
521 // Match wildcard
522 if(*terrain_itor == STAR) {
523 return result;
524 }
525
526 // Match inverse symbol
527 if(*terrain_itor == NOT) {
528 result = !result;
529 continue;
530 }
531
532 // Full match
533 if(*terrain_itor == src) {
534 return result;
535 }
536
537 // Does the destination wildcard match
538 if(dest.has_wildcard &&
539 (src.base & dest.mask[i].base) == dest.masked_terrain[i].base &&
540 (src.overlay & dest.mask[i].overlay) == dest.masked_terrain[i].overlay) {
541 return result;
542 }
543
544 /* Test code */ /*
545 if(src_has_wildcard && has_wildcard(*terrain_itor) && (
546 (
547 get_layer_mask_(terrain_itor->base) != NO_LAYER &&
548 get_layer_mask_(src.overlay) != NO_LAYER &&
549 (src.base & dest.mask[i].base) == dest.masked_terrain[i].base &&
550 (terrain_itor->overlay & src_mask.overlay) == masked_src.overlay
551 ) || (
552 get_layer_mask_(terrain_itor->overlay) != NO_LAYER &&
553 get_layer_mask_(src.base) != NO_LAYER &&
554 (src.overlay & dest.mask[i].overlay) == dest.masked_terrain[i].overlay &&
555 (terrain_itor->base & src_mask.base) == masked_src.base
556 ))) {
557
558 return result;
559 }
560 */
561 }
562
563 // No match, return the inverse of the result
564 return !result;
565 }
566
has_wildcard(const terrain_code & tcode)567 bool has_wildcard(const terrain_code& tcode)
568 {
569 if(tcode.overlay == NO_LAYER) {
570 return get_layer_mask_(tcode.base) != NO_LAYER;
571 } else {
572 return get_layer_mask_(tcode.base) != NO_LAYER || get_layer_mask_(tcode.overlay) != NO_LAYER;
573 }
574 }
575
has_wildcard(const ter_list & list)576 bool has_wildcard(const ter_list& list)
577 {
578 if(list.empty()) {
579 return false;
580 }
581
582 // Test all items for a wildcard
583 ter_list::const_iterator itor = list.begin();
584 for(; itor != list.end(); ++itor) {
585 if(has_wildcard(*itor)) {
586 return true;
587 }
588 }
589
590 // No wildcard found
591 return false;
592 }
593
read_builder_map(const std::string & str)594 ter_map read_builder_map(const std::string& str)
595 {
596 boost::multi_array<int, sizeof(ter_map)> a;
597
598 size_t offset = 0;
599 // Skip the leading newlines
600 while(offset < str.length() && utils::isnewline(str[offset])) {
601 ++offset;
602 }
603 // Did we get an empty map?
604 if((offset + 1) >= str.length()) {
605 return ter_map();
606 }
607
608 auto map_size = get_map_size(&str[offset], str.c_str() + str.size());
609 ter_map result(map_size.second, map_size.first, terrain_code(t_translation::TB_DOT, ter_layer()));
610
611 int x = 0, y = 0;
612 while(offset < str.length()) {
613
614 // Get a terrain chunk
615 const std::string separators = ",\n\r";
616 const size_t pos_separator = str.find_first_of(separators, offset);
617 std::string terrain = "";
618 // Make sure we didn't hit an empty chunk
619 // which is allowed
620 if(pos_separator != offset) {
621 terrain = str.substr(offset, pos_separator - offset);
622 }
623
624 // Process the chunk
625 const terrain_code tile = string_to_builder_number_(terrain);
626
627 // Make space for the new item
628 if (result.h <= x || result.w <= y) {
629 throw error("Map not a rectangle.");
630 }
631
632 // Add the resulting terrain number,
633 result.get(y, x) = tile;
634
635 // evaluate the separator
636 if(pos_separator == std::string::npos) {
637 // Probably not required to change the value,
638 // but be sure the case should be handled at least.
639 // I'm not sure how it is defined in the standard,
640 // but here it's defined at max u32 which with +1 gives 0
641 // and make a nice infinite loop.
642 offset = str.length();
643 } else if(utils::isnewline(str[pos_separator])) {
644 // Prepare next iteration
645 ++y;
646 x = 0;
647
648 offset = pos_separator + 1;
649 // Skip the following newlines
650 while(offset < str.length() && utils::isnewline(str[offset])) {
651 ++offset;
652 }
653
654 } else {
655 ++x;
656 offset = pos_separator + 1;
657 }
658
659 }
660
661 return result;
662 }
663
664 /***************************************************************************************/
665 // Internal
666
get_layer_mask_(ter_layer terrain)667 inline ter_layer get_layer_mask_(ter_layer terrain)
668 {
669 // Test for the star 0x2A in every position
670 // and return the appropriate mask
671 /*
672 * This is what the code intents to do, but in order to gain some more
673 * speed it's changed to the code below, which does the same but faster.
674 * This routine is used often in the builder and the speedup is noticeable. */
675 if((terrain & 0xFF000000) == 0x2A000000) return 0x00000000;
676 if((terrain & 0x00FF0000) == 0x002A0000) return 0xFF000000;
677 if((terrain & 0x0000FF00) == 0x00002A00) return 0xFFFF0000;
678 if((terrain & 0x000000FF) == 0x0000002A) return 0xFFFFFF00;
679
680 /*
681 uint8_t *ptr = (uint8_t *) &terrain;
682
683 if(ptr[3] == 0x2A) return 0x00000000;
684 if(ptr[2] == 0x2A) return 0xFF000000;
685 if(ptr[1] == 0x2A) return 0xFFFF0000;
686 if(ptr[0] == 0x2A) return 0xFFFFFF00;
687 */
688 // no star found return the default
689 return 0xFFFFFFFF;
690 }
691
get_mask_(const terrain_code & terrain)692 static terrain_code get_mask_(const terrain_code& terrain)
693 {
694 if(terrain.overlay == NO_LAYER) {
695 return terrain_code(get_layer_mask_(terrain.base), 0xFFFFFFFF);
696 } else {
697 return terrain_code(get_layer_mask_(terrain.base), get_layer_mask_(terrain.overlay));
698 }
699 }
700
string_to_layer_(const char * begin,const char * end)701 static ter_layer string_to_layer_(const char* begin, const char* end)
702 {
703 size_t size = end - begin;
704 if (begin == end) {
705 return NO_LAYER;
706 }
707 ter_layer result = 0;
708
709 // Validate the string
710 VALIDATE(size <= 4, _("A terrain with a string with more "
711 "than 4 characters has been found, the affected terrain is :") + std::string(begin, end));
712
713 // The conversion to int puts the first char
714 // in the highest part of the number.
715 // This will make the wildcard matching
716 // later on a bit easier.
717 for(size_t i = 0; i < 4; ++i) {
718 const unsigned char c = (i < size) ? begin[i] : 0;
719
720 // Clearing the lower area is a nop on i == 0
721 // so no need for if statement
722 result <<= 8;
723
724 // Add the result
725 result += c;
726 }
727
728 return result;
729 }
730
string_to_number_(const std::string & str,const ter_layer filler)731 static terrain_code string_to_number_(const std::string& str, const ter_layer filler) {
732 std::string dummy;
733 return string_to_number_(str, dummy, filler);
734 }
735
string_to_number_(std::string str,std::string & start_position,const ter_layer filler)736 static terrain_code string_to_number_(std::string str, std::string& start_position, const ter_layer filler)
737 {
738 const char* c_str = str.c_str();
739 terrain_code result;
740
741 // Strip the spaces around us
742 const std::string& whitespace = " \t";
743 size_t begin = str.find_first_not_of(whitespace);
744 size_t end = str.find_last_not_of(whitespace) + 1;
745 if(begin == std::string::npos) {
746 return result;
747 }
748
749 // Split if we have 1 space inside
750 size_t offset = str.find(' ', begin);
751 if(offset < end) {
752 start_position = str.substr(begin, offset - begin);
753 begin = offset + 1;
754 }
755
756 offset = str.find('^', 0);
757 if(offset != std::string::npos) {
758 result = terrain_code { string_to_layer_(c_str + begin, c_str + offset), string_to_layer_(c_str + offset + 1, c_str + end) };
759 } else {
760 result = terrain_code { string_to_layer_(c_str + begin, c_str + end), filler };
761
762 // Ugly hack
763 if(filler == WILDCARD && (result.base == NOT.base ||
764 result.base == STAR.base)) {
765
766 result.overlay = NO_LAYER;
767 }
768 }
769
770 return result;
771 }
772
number_to_string_(terrain_code terrain,const std::string & start_position)773 static std::string number_to_string_(terrain_code terrain, const std::string& start_position)
774 {
775 std::string result = "";
776
777 // Insert the start position
778 if(!start_position.empty()) {
779 result = start_position + " ";
780 }
781
782 /*
783 * The initialization of tcode is done to make gcc-4.7 happy. Otherwise it
784 * some uninitialized fields might be used. Its analysis are wrong, but
785 * Initialize to keep it happy.
786 */
787 unsigned char tcode[9] {0};
788 // Insert the terrain tcode
789 tcode[0] = ((terrain.base & 0xFF000000) >> 24);
790 tcode[1] = ((terrain.base & 0x00FF0000) >> 16);
791 tcode[2] = ((terrain.base & 0x0000FF00) >> 8);
792 tcode[3] = (terrain.base & 0x000000FF);
793
794 if(terrain.overlay != NO_LAYER) {
795 tcode[4] = '^'; //the layer separator
796 tcode[5] = ((terrain.overlay & 0xFF000000) >> 24);
797 tcode[6] = ((terrain.overlay & 0x00FF0000) >> 16);
798 tcode[7] = ((terrain.overlay & 0x0000FF00) >> 8);
799 tcode[8] = (terrain.overlay & 0x000000FF);
800 } else {
801 // If no second layer, the second layer won't be written,
802 // so no need to initialize that part of the array
803 tcode[4] = 0;
804 }
805
806 for(int i = 0; i < 9; ++i) {
807 if(tcode[i] != 0 && tcode[i] != 0xFF) {
808 result += tcode[i];
809 }
810 if(i == 4 && tcode[i] == 0) {
811 // no layer, stop
812 break;
813 }
814 }
815
816 return result;
817 }
818
string_to_builder_number_(std::string str)819 static terrain_code string_to_builder_number_(std::string str)
820 {
821 // Strip the spaces around us
822 const std::string& whitespace = " \t";
823 str.erase(0, str.find_first_not_of(whitespace));
824 if(! str.empty()) {
825 str.erase(str.find_last_not_of(whitespace) + 1);
826 }
827
828 // Empty string is allowed here, so handle it
829 if(str.empty()) {
830 return terrain_code();
831 }
832
833 const int number = lexical_cast_default(str, -1);
834 if(number == -1) {
835 // At this point we have a single char
836 // which should be interpreted by the
837 // map builder, so return this number
838 return terrain_code(str[0] << 24, 0);
839 } else {
840 return terrain_code(0, number);
841 }
842 }
843
844 } // end namespace t_translation
845
846 #if 0
847 // small helper rule to test the matching rules
848 // building rule
849 // make terrain_translation.o && g++ terrain_translation.o libwesnoth-core.a -lSDL -o terrain_translation
850 int main(int argc, char** argv)
851 {
852 if(argc > 1) {
853
854 if(std::string(argv[1]) == "match" && argc == 4) {
855 t_translation::terrain_code src = t_translation::read_terrain_code(std::string(argv[2]));
856
857 t_translation::ter_list dest = t_translation::read_list(std::string(argv[3]));
858
859 if(t_translation::terrain_matches(src, dest)) {
860 std::cout << "Match\n" ;
861 } else {
862 std::cout << "No match\n";
863 }
864 }
865 }
866 }
867
868 #endif
869