The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
builder.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2004 - 2025
3 by Philippe Plantier <ayin@anathas.org>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
16/**
17 * @file
18 * Terrain builder.
19 */
20
21#include "terrain/builder.hpp"
22
23#include "game_config_view.hpp"
24#include "global.hpp"
26#include "log.hpp"
27#include "map/map.hpp"
28#include "picture.hpp"
31
32static lg::log_domain log_engine("engine");
33#define ERR_NG LOG_STREAM(err, log_engine)
34#define WRN_NG LOG_STREAM(warn, log_engine)
35
36using namespace std::chrono_literals;
37
38/**
39 *
40 * These legacy map_location functions moved here from map_location.?pp.
41 * We have refactored them out of everything but this class. Hopefully
42 * the end is near...
43 *
44 // Adds an absolute location to a "delta" location
45 // This is not the mathematically correct behavior, it is neither
46 // commutative nor associative. Negative coordinates may give strange
47 // results. It is retained because terrain builder code relies in this
48 // broken behavior. Best avoid.
49 map_location legacy_negation() const;
50 map_location legacy_sum(const map_location &a) const;
51 map_location& legacy_sum_assign(const map_location &a);
52 map_location legacy_difference(const map_location &a) const;
53 *
54 */
55
57{
58 return map_location(-me.x, -me.y);
59}
60
62{
63 bool parity = (me.x & 1) != 0;
64 me.x += a.x;
65 me.y += a.y;
66 if((a.x > 0) && (a.x % 2) && parity)
67 me.y++;
68 if((a.x < 0) && (a.x % 2) && !parity)
69 me.y--;
70
71 return me;
72}
73
75{
76 map_location ret(me);
77 legacy_sum_assign(ret, a);
78 return ret;
79}
80
82{
83 return legacy_sum(me, legacy_negation(a));
84}
85
86/**
87 *
88 * This file holds the terrain_builder implementation.
89 *
90 */
91
92void terrain_builder::tile::rebuild_cache(const std::string& tod, logs* log)
93{
94 images_background.clear();
95 images_foreground.clear();
96
97 if(!sorted_images) {
98 // sort images by their layer (and basey)
99 // but use stable to keep the insertion order in equal cases
100 std::stable_sort(images.begin(), images.end());
101 sorted_images = true;
102 }
103
104 for(const rule_image_rand& ri : images) {
105 bool is_background = ri->is_background();
106 bool animate = (!ri.ri->is_water || prefs::get().animate_water());
107
109
110 for(const rule_image_variant& variant : ri->variants) {
111 if(!variant.has_flag.empty()) {
112 bool has_flag_match = true;
113 for(const std::string& s : variant.has_flag) {
114 // If a flag listed in "has_flag" is not present, this variant does not match
115 if(flags.find(s) == flags.end()) {
116 has_flag_match = false;
117 break;
118 }
119 }
120
121 if(!has_flag_match) {
122 continue;
123 }
124 }
125
126 if(!variant.tods.empty() && variant.tods.find(tod) == variant.tods.end())
127 continue;
128
129 // need to break parity pattern in RNG
130 /** @todo improve this */
131 unsigned int rnd = ri.rand / 7919; // just the 1000th prime
132 const animated<image::locator>& anim = variant.images[rnd % variant.images.size()];
133
134 bool is_empty = true;
135 for(std::size_t i = 0; i < anim.get_frames_count(); ++i) {
136 if(!image::is_empty_hex(anim.get_frame(i))) {
137 is_empty = false;
138 break;
139 }
140 }
141
142 if(is_empty)
143 continue;
144
145 img_list.push_back(anim);
146
147 assert(anim.get_animation_duration() != 0ms);
148
149 if(variant.random_start < 0ms) {
150 img_list.back().apply_time_offset(std::chrono::milliseconds{ri.rand} % img_list.back().get_animation_duration());
151 } else if(variant.random_start > 0ms) {
152 img_list.back().apply_time_offset(std::chrono::milliseconds{ri.rand} % variant.random_start);
153 }
154
155 if(!animate) {
156 img_list.back().pause_animation();
157 }
158
159 if(log) {
160 log->emplace_back(&ri, &variant);
161 }
162
163 break; // found a matching variant
164 }
165 }
166}
167
169{
170 flags.clear();
171 images.clear();
172 sorted_images = false;
173 images_foreground.clear();
174 images_background.clear();
175 last_tod = "invalid_tod";
176}
177
178static unsigned int get_noise(const map_location& loc, unsigned int index)
179{
180 unsigned int a = (loc.x + 92872973) ^ 918273;
181 unsigned int b = (loc.y + 1672517) ^ 128123;
182 unsigned int c = (index + 127390) ^ 13923787;
183 unsigned int abc = a * b * c + a * b + b * c + a * c + a + b + c;
184 return abc * abc;
185}
186
188 : tiles_((x + 4) * (y + 4))
189 , x_(x)
190 , y_(y)
191{
192 reset();
193}
194
196{
197 for(std::vector<tile>::iterator it = tiles_.begin(); it != tiles_.end(); ++it)
198 it->clear();
199}
200
202{
203 x_ = x;
204 y_ = y;
205 std::vector<terrain_builder::tile> new_tiles(static_cast<std::size_t>(x + 4) * (y + 4));
206 tiles_.swap(new_tiles);
207 reset();
208}
209
211{
212 if(loc.x < -2 || loc.y < -2 || loc.x > (x_ + 1) || loc.y > (y_ + 1)) {
213 return false;
214 }
215
216 return true;
217}
218
220{
221 assert(on_map(loc));
222
223 return tiles_[(loc.x + 2) + (loc.y + 2) * (x_ + 4)];
224}
225
227{
228 assert(on_map(loc));
229
230 return tiles_[(loc.x + 2) + (loc.y + 2) * (x_ + 4)];
231}
232
233terrain_builder::terrain_builder(const config& level, const gamemap* m, const std::string& offmap_image, bool draw_border)
234 : tilewidth_(game_config::tile_size)
235 , map_(m)
236 , tile_map_(m ? map().w() : 0, m ? map().h() : 0)
239{
241
242 if(building_rules_.empty() && rules_cfg_) {
243 // off_map first to prevent some default rule seems to block it
245 // parse global terrain rules
247 } else {
248 // use cached global rules but clear local rules
250 }
251
252 // parse local rules
253 parse_config(level);
254
255 if(m)
257}
258
260{
261 for(int x = -2; x <= map().w(); ++x) {
262 for(int y = -2; y <= map().h(); ++y) {
263 tile_map_[map_location(x, y)].rebuild_cache("");
264 }
265 }
266}
267
269{
270 building_ruleset::iterator i = building_rules_.begin();
271 for(; i != building_rules_.end();) {
272 if(i->local)
273 building_rules_.erase(i++);
274 else
275 ++i;
276 }
277}
278
284
286{
287 tile_map_.reload(map().w(), map().h());
288 terrain_by_type_.clear();
290}
291
293{
294 map_ = m;
295 reload_map();
296}
297
299 const map_location& loc, const std::string& tod, const TERRAIN_TYPE terrain_type)
300{
301 if(!tile_map_.on_map(loc))
302 return nullptr;
303
305
306 if(tod != tile_at.last_tod) {
307 tile_at.rebuild_cache(tod);
308 tile_at.last_tod = tod;
309 }
310
311 const imagelist& img_list = (terrain_type == BACKGROUND) ? tile_at.images_background : tile_at.images_foreground;
312
313 if(!img_list.empty()) {
314 return &img_list;
315 }
316
317 return nullptr;
318}
319
321{
322 if(!tile_map_.on_map(loc))
323 return false;
324
325 bool changed = false;
326
328
329 for(animated<image::locator>& a : btile.images_background) {
330 if(a.need_update()) {
331 changed = true;
332 a.advance_to_current_frame();
333 }
334 }
335 for(animated<image::locator>& a : btile.images_foreground) {
336 if(a.need_update()) {
337 changed = true;
338 a.advance_to_current_frame();
339 }
340 }
341
342 return changed;
343}
344
345/** @todo TODO: rename this function */
347{
348 if(tile_map_.on_map(loc)) {
350 // btile.images.clear();
351 btile.images_foreground.clear();
352 btile.images_background.clear();
353 const std::string filename = map().get_terrain_info(loc).minimap_image();
354
355 if(!filename.empty()) {
357 img_loc.add_frame(100ms, image::locator("terrain/" + filename + ".png"));
358 img_loc.start_animation(0ms, true);
359 btile.images_background.push_back(img_loc);
360 }
361
362 // Combine base and overlay image if necessary
363 if(map().get_terrain_info(loc).is_combined()) {
365
366 if(!filename_ovl.empty()) {
368 img_loc_ovl.add_frame(100ms, image::locator("terrain/" + filename_ovl + ".png"));
369 img_loc_ovl.start_animation(0ms, true);
370 btile.images_background.push_back(img_loc_ovl);
371 }
372 }
373 }
374}
375
382
383static bool image_exists(const std::string& name)
384{
385 bool precached = name.find("..") == std::string::npos;
386
387 if(precached && image::precached_file_exists(name)) {
388 return true;
389 } else if(image::exists(name)) {
390 return true;
391 }
392
393 return false;
394}
395
396static std::vector<std::string> get_variations(const std::string& base, const std::string& variations)
397{
398 /** @todo optimize this function */
399 std::vector<std::string> res;
400 if(variations.empty()) {
401 res.push_back(base);
402 return res;
403 }
404 std::string::size_type pos = base.find("@V", 0);
405 if(pos == std::string::npos) {
406 res.push_back(base);
407 return res;
408 }
409 std::vector<std::string> vars = utils::split(variations, ';', 0);
410
411 for(const std::string& v : vars) {
412 res.push_back(base);
413 pos = 0;
414 while((pos = res.back().find("@V", pos)) != std::string::npos) {
415 res.back().replace(pos, 2, v);
416 pos += v.size();
417 }
418 }
419 return res;
420}
421
423{
424 // If the rule has no constraints, it is invalid
425 if(rule.constraints.empty())
426 return false;
427
428 // Parse images and animations data
429 // If one is not valid, return false.
430 for(terrain_constraint& constraint : rule.constraints) {
431 for(rule_image& ri : constraint.images) {
432 for(rule_image_variant& variant : ri.variants) {
433 std::vector<std::string> var_strings = get_variations(variant.image_string, variant.variations);
434 for(const std::string& var : var_strings) {
435 /** @todo improve this, 99% of terrains are not animated. */
436 std::vector<std::string> frames = utils::square_parenthetical_split(var, ',');
438
439 for(const std::string& frame : frames) {
440 const std::vector<std::string> items = utils::split(frame, ':');
441 const std::string& str = items.front();
442
443 const std::size_t tilde = str.find('~');
444 bool has_tilde = tilde != std::string::npos;
445 const std::string filename = "terrain/" + (has_tilde ? str.substr(0, tilde) : str);
446
447 if(!image_exists(filename)) {
448 continue; // ignore missing frames
449 }
450
451 const std::string modif = (has_tilde ? str.substr(tilde + 1) : "");
452
453 auto time = 100ms;
454 if(items.size() > 1) {
455 try {
456 time = std::chrono::milliseconds{std::stoi(items.back())};
457 } catch(const std::invalid_argument&) {
458 ERR_NG << "Invalid 'time' value in terrain image builder: " << items.back();
459 }
460 }
461 if(ri.global_image) {
462 res.add_frame(time, image::locator(filename, constraint.loc, ri.center_x, ri.center_y, modif));
463 } else {
464 res.add_frame(time, image::locator(filename, modif));
465 }
466 }
467 if(res.get_frames_count() == 0)
468 break; // no valid images, don't register it
469
470 res.start_animation(0ms, true);
471 variant.images.push_back(std::move(res));
472 }
473 if(variant.images.empty())
474 return false; // no valid images, rule is invalid
475 }
476 }
477 }
478
479 return true;
480}
481
483{
484 static const struct
485 {
486 int ii;
487 int ij;
488 int ji;
489 int jj;
490 } rotations[6] {{1, 0, 0, 1}, {1, 1, -1, 0}, {0, 1, -1, -1}, {-1, 0, 0, -1}, {-1, -1, 1, 0}, {0, -1, 1, 1}};
491
492 // The following array of matrices is intended to rotate the (x,y)
493 // coordinates of a point in a wesnoth hex (and wesnoth hexes are not
494 // regular hexes :) ).
495 // The base matrix for a 1-step rotation with the wesnoth tile shape
496 // is:
497 //
498 // r = s^-1 * t * s
499 //
500 // with s = [[ 1 0 ]
501 // [ 0 -sqrt(3)/2 ]]
502 //
503 // and t = [[ -1/2 sqrt(3)/2 ]
504 // [ -sqrt(3)/2 1/2 ]]
505 //
506 // With t being the rotation matrix (pi/3 rotation), and s a matrix
507 // that transforms the coordinates of the wesnoth hex to make them
508 // those of a regular hex.
509 //
510 // (demonstration left as an exercise for the reader)
511 //
512 // So we have
513 //
514 // r = [[ 1/2 -3/4 ]
515 // [ 1 1/2 ]]
516 //
517 // And the following array contains I(2), r, r^2, r^3, r^4, r^5
518 // (with r^3 == -I(2)), which are the successive rotations.
519 static const struct
520 {
521 double xx;
522 double xy;
523 double yx;
524 double yy;
525 } xyrotations[6] {
526 { 1., 0., 0., 1. },
527 { 1./2. , -3./4., 1., 1./2. },
528 { -1./2., -3./4., 1, -1./2.},
529 { -1. , 0., 0., -1. },
530 { -1./2., 3./4., -1., -1./2.},
531 { 1./2. , 3./4., -1., 1./2. },
532 };
533
534 assert(angle >= 0);
535
536 angle %= 6;
537
538 // Vector i is going from n to s, vector j is going from ne to sw.
539 int vi = ret.loc.y - ret.loc.x / 2;
540 int vj = ret.loc.x;
541
542 int ri = rotations[angle].ii * vi + rotations[angle].ij * vj;
543 int rj = rotations[angle].ji * vi + rotations[angle].jj * vj;
544
545 ret.loc.x = rj;
546 ret.loc.y = ri + (rj >= 0 ? rj / 2 : (rj - 1) / 2);
547
548 for(rule_imagelist::iterator itor = ret.images.begin(); itor != ret.images.end(); ++itor) {
549 double vx, vy, rx, ry;
550
551 vx = static_cast<double>(itor->basex) - static_cast<double>(tilewidth_) / 2;
552 vy = static_cast<double>(itor->basey) - static_cast<double>(tilewidth_) / 2;
553
554 rx = xyrotations[angle].xx * vx + xyrotations[angle].xy * vy;
555 ry = xyrotations[angle].yx * vx + xyrotations[angle].yy * vy;
556
557 itor->basex = static_cast<int>(rx + tilewidth_ / 2);
558 itor->basey = static_cast<int>(ry + tilewidth_ / 2);
559 }
560}
561
562void terrain_builder::replace_rotate_tokens(std::string& s, int angle, const std::vector<std::string>& replacement)
563{
564 std::string::size_type pos = 0;
565 while((pos = s.find("@R", pos)) != std::string::npos) {
566 if(pos + 2 >= s.size())
567 return;
568 unsigned i = s[pos + 2] - '0' + angle;
569 if(i >= 6)
570 i -= 6;
571 if(i >= 6) {
572 pos += 2;
573 continue;
574 }
575 const std::string& r = replacement[i];
576 s.replace(pos, 3, r);
577 pos += r.size();
578 }
579}
580
581void terrain_builder::replace_rotate_tokens(rule_image& image, int angle, const std::vector<std::string>& replacement)
582{
583 for(rule_image_variant& variant : image.variants) {
585 }
586}
587
589 rule_imagelist& list, int angle, const std::vector<std::string>& replacement)
590{
591 for(rule_image& img : list) {
593 }
594}
595
596void terrain_builder::replace_rotate_tokens(building_rule& rule, int angle, const std::vector<std::string>& replacement)
597{
598 for(terrain_constraint& cons : rule.constraints) {
599 // Transforms attributes
600 for(std::string& flag : cons.set_flag) {
602 }
603 for(std::string& flag : cons.no_flag) {
605 }
606 for(std::string& flag : cons.has_flag) {
608 }
610 }
611
612 // replace_rotate_tokens(rule.images, angle, replacement);
613}
614
615void terrain_builder::rotate_rule(building_rule& ret, int angle, const std::vector<std::string>& rot)
616{
617 if(rot.size() != 6) {
618 ERR_NG << "invalid rotations";
619 return;
620 }
621
622 for(terrain_constraint& cons : ret.constraints) {
623 rotate(cons, angle);
624 }
625
626 // Normalize the rotation, so that it starts on a positive location
627 int minx = std::numeric_limits<int>::max();
628 int miny = std::numeric_limits<int>::max();
629
630 for(const terrain_constraint& cons : ret.constraints) {
631 minx = std::min<int>(cons.loc.x, minx);
632 miny = std::min<int>(2 * cons.loc.y + (cons.loc.x & 1), miny);
633 }
634
635 if((miny & 1) && (minx & 1) && (minx < 0))
636 miny += 2;
637 if(!(miny & 1) && (minx & 1) && (minx > 0))
638 miny -= 2;
639
640 for(terrain_constraint& cons : ret.constraints) {
641 legacy_sum_assign(cons.loc, map_location(-minx, -((miny - 1) / 2)));
642 }
643
645}
646
648 const std::string& variations,
649 const std::chrono::milliseconds& random_start)
650 : image_string(image_string)
651 , variations(variations)
652 , images()
653 , tods()
654 , has_flag()
655 , random_start(random_start)
656{
657}
658
660 const std::string& variations,
661 const std::string& tod,
662 const std::string& has_flag,
663 const std::chrono::milliseconds& random_start)
664 : image_string(image_string)
665 , variations(variations)
666 , images()
667 , tods()
668 , has_flag()
669 , random_start(random_start)
670{
671 if(!has_flag.empty()) {
672 this->has_flag = utils::split(has_flag);
673 }
674 if(!tod.empty()) {
675 const std::vector<std::string> tod_list = utils::split(tod);
676 tods.insert(tod_list.begin(), tod_list.end());
677 }
678}
679
681{
682 for(const config& img : cfg.child_range("image")) {
683 int layer = img["layer"].to_int();
684
685 int basex = tilewidth_ / 2 + dx, basey = tilewidth_ / 2 + dy;
686 if(const config::attribute_value* base_ = img.get("base")) {
687 std::vector<std::string> base = utils::split(*base_);
688 if(base.size() >= 2) {
689 try {
690 basex = std::stoi(base[0]);
691 basey = std::stoi(base[1]);
692 } catch(const std::invalid_argument&) {
693 ERR_NG << "Invalid 'base' value in terrain image builder: " << base[0] << ", " << base[1];
694 }
695 }
696 }
697
698 int center_x = -1, center_y = -1;
699 if(const config::attribute_value* center_ = img.get("center")) {
700 std::vector<std::string> center = utils::split(*center_);
701 if(center.size() >= 2) {
702 try {
703 center_x = std::stoi(center[0]);
704 center_y = std::stoi(center[1]);
705 } catch(const std::invalid_argument&) {
706 ERR_NG << "Invalid 'center' value in terrain image builder: " << center[0] << ", " << center[1];
707 }
708 }
709 }
710
711 bool is_water = img["is_water"].to_bool();
712
713 images.AGGREGATE_EMPLACE(layer, basex - dx, basey - dy, global, center_x, center_y, is_water);
714
715 // Adds the other variants of the image
716 for(const config& variant : img.child_range("variant")) {
717 const std::string& name = variant["name"];
718 const std::string& variations = img["variations"];
719 const std::string& tod = variant["tod"];
720 const std::string& has_flag = variant["has_flag"];
721
722 // If an integer is given then assign that, but if a bool is given, then assign -1 if true and 0 if false
723 int random_start = variant["random_start"].to_bool(true) ? variant["random_start"].to_int(-1) : 0;
724
725 images.back().variants.emplace_back(name, variations, tod, has_flag, std::chrono::milliseconds{random_start});
726 }
727
728 // Adds the main (default) variant of the image at the end,
729 // (will be used only if previous variants don't match)
730 const std::string& name = img["name"];
731 const std::string& variations = img["variations"];
732
733 int random_start = img["random_start"].to_bool(true) ? img["random_start"].to_int(-1) : 0;
734
735 images.back().variants.emplace_back(name, variations, std::chrono::milliseconds{random_start});
736 }
737}
738
740 const map_location& loc,
741 const t_translation::ter_match& type,
742 const config& global_images)
743{
744 terrain_constraint* cons = nullptr;
745 for(terrain_constraint& c : constraints) {
746 if(c.loc == loc) {
747 cons = &c;
748 break;
749 }
750 }
751
752 if(!cons) {
753 // The terrain at the current location did not exist, so create it
754 constraints.AGGREGATE_EMPLACE(loc);
755 cons = &constraints.back();
756 }
757
758 if(!type.terrain.empty()) {
759 cons->terrain_types_match = type;
760 }
761
762 int x = loc.x * tilewidth_ * 3 / 4;
763 int y = loc.y * tilewidth_ + (loc.x % 2) * tilewidth_ / 2;
764 add_images_from_config(cons->images, global_images, true, x, y);
765
766 return *cons;
767}
768
770 const map_location& loc,
771 const config& cfg,
772 const config& global_images)
773
774{
775 // default to WILDCARD overlay in [terrain_graphics] [tile] type=
777
778 std::vector<std::string> item_string = utils::square_parenthetical_split(cfg["set_flag"], ',', "[", "]");
779 constraint.set_flag.insert(constraint.set_flag.end(), item_string.begin(), item_string.end());
780
781 item_string = utils::square_parenthetical_split(cfg["has_flag"], ',', "[", "]");
782 constraint.has_flag.insert(constraint.has_flag.end(), item_string.begin(), item_string.end());
783
784 item_string = utils::square_parenthetical_split(cfg["no_flag"], ',', "[", "]");
785 constraint.no_flag.insert(constraint.no_flag.end(), item_string.begin(), item_string.end());
786
787 item_string = utils::square_parenthetical_split(cfg["set_no_flag"], ',', "[", "]");
788 constraint.set_flag.insert(constraint.set_flag.end(), item_string.begin(), item_string.end());
789 constraint.no_flag.insert(constraint.no_flag.end(), item_string.begin(), item_string.end());
790
791 constraint.no_draw = cfg["no_draw"].to_bool(false);
792
793 add_images_from_config(constraint.images, cfg, false);
794}
795
797 const std::string& mapstring, struct building_rule& br, anchormap& anchors, const config& global_images)
798{
800
801 // If there is an empty map leave directly.
802 // Determine after conversion, since a
803 // non-empty string can return an empty map.
804 if(map.data.empty()) {
805 return;
806 }
807
808 int lineno = (map.get(0, 0) == t_translation::NONE_TERRAIN) ? 1 : 0;
809 int x = lineno;
810 int y = 0;
811 for(int y_off = 0; y_off < map.w; ++y_off) {
812 for(int x_off = x; x_off < map.h; ++x_off) {
813 const t_translation::terrain_code terrain = map.get(y_off, x_off);
814
815 if(terrain.base == t_translation::TB_DOT) {
816 // Dots are simple placeholders,
817 // which do not represent actual terrains.
818 } else if(terrain.overlay != 0) {
819 anchors.emplace(terrain.overlay, map_location(x, y));
820 } else if(terrain.base == t_translation::TB_STAR) {
822 } else {
823 std::string error = "Invalid terrain (" + t_translation::write_terrain_code(terrain) + ") in [terrain_graphics] map=";
824 ERR_NG << error;
825 throw game::error(error);
826 }
827 x += 2;
828 }
829
830 if(lineno % 2 == 1) {
831 ++y;
832 x = 0;
833 } else {
834 x = 1;
835 }
836 ++lineno;
837 }
838}
839
846
848{
849 if(rotations.empty()) {
850 // Adds the parsed built terrain to the list
851
853 } else {
854 const std::vector<std::string>& rot = utils::split(rotations, ',');
855
856 for(std::size_t angle = 0; angle < rot.size(); ++angle) {
857 /* Only 5% of the rules have valid images, so most of
858 them will be discarded. If the ratio was higher,
859 it would be more efficient to insert a copy of the
860 template rule into the ruleset, modify it in place,
861 and remove it if invalid. But since the ratio is so
862 low, the speedup is not worth the extra multiset
863 manipulations. */
864
865 if(rot.at(angle) == "skip") {
866 continue;
867 }
868
872 }
873 }
874}
875
877{
879}
880
882{
883 log_scope("terrain_builder::parse_config");
884 int n = 0;
885
886 // Parses the list of building rules (BRs)
887 for(const config& br : cfg.child_range("terrain_graphics")) {
888 building_rule pbr; // Parsed Building rule
889 pbr.local = local;
890
891 // add_images_from_config(pbr.images, **br);
892
893 pbr.location_constraints = map_location(br["x"].to_int() - 1, br["y"].to_int() - 1);
894
895 pbr.modulo_constraints = map_location(br["mod_x"].to_int(), br["mod_y"].to_int());
896
897 pbr.probability = br["probability"].to_int(100);
898
899 // Mapping anchor indices to anchor locations.
901
902 // Parse the map= , if there is one (and fill the anchors list)
903 parse_mapstring(br["map"], pbr, anchors, br);
904
905 // Parses the terrain constraints (TCs)
906 for(const config& tc : br.child_range("tile")) {
907 // Adds the terrain constraint to the current built terrain's list
908 // of terrain constraints, if it does not exist.
910 if(const config::attribute_value* v = tc.get("x")) {
911 loc.x = v->to_int();
912 }
913 if(const config::attribute_value* v = tc.get("y")) {
914 loc.y = v->to_int();
915 }
916 if(loc.valid()) {
917 add_constraints(pbr.constraints, loc, tc, br);
918 }
919 if(const config::attribute_value* v = tc.get("pos")) {
920 int pos = v->to_int();
921 if(anchors.find(pos) == anchors.end()) {
922 WRN_NG << "Invalid anchor!";
923 continue;
924 }
925
926 auto range = anchors.equal_range(pos);
927
928 for(; range.first != range.second; ++range.first) {
929 loc = range.first->second;
930 add_constraints(pbr.constraints, loc, tc, br);
931 }
932 }
933 }
934
935 const std::vector<std::string> global_set_flag = utils::split(br["set_flag"]);
936 const std::vector<std::string> global_no_flag = utils::split(br["no_flag"]);
937 const std::vector<std::string> global_has_flag = utils::split(br["has_flag"]);
938 const std::vector<std::string> global_set_no_flag = utils::split(br["set_no_flag"]);
939
940 for(terrain_constraint& constraint : pbr.constraints) {
941 constraint.set_flag.insert(constraint.set_flag.end(), global_set_flag.begin(), global_set_flag.end());
942 constraint.no_flag.insert(constraint.no_flag.end(), global_no_flag.begin(), global_no_flag.end());
943 constraint.has_flag.insert(constraint.has_flag.end(), global_has_flag.begin(), global_has_flag.end());
944 constraint.set_flag.insert(constraint.set_flag.end(), global_set_no_flag.begin(), global_set_no_flag.end());
945 constraint.no_flag.insert(constraint.no_flag.end(), global_set_no_flag.begin(), global_set_no_flag.end());
946 }
947
948 // Handles rotations
949 const std::string& rotations = br["rotations"];
950
951 pbr.precedence = br["precedence"].to_int();
952
954
955 n++;
956 if(n % 10 == 0) {
958 }
959 }
960
961// Debug output for the terrain rules
962#if 0
963 PLAIN_LOG << "Built terrain rules: ";
964
965 building_ruleset::const_iterator rule;
966 for(rule = building_rules_.begin(); rule != building_rules_.end(); ++rule) {
967 PLAIN_LOG << ">> New rule: image_background = "
968 << "\n>> Location " << rule->second.location_constraints
969 << "\n>> Probability " << rule->second.probability
970
971 for(constraint_set::const_iterator constraint = rule->second.constraints.begin();
972 constraint != rule->second.constraints.end(); ++constraint) {
973
974 PLAIN_LOG << ">>>> New constraint: location = (" << constraint->second.loc
975 << "), terrain types = '" << t_translation::write_list(constraint->second.terrain_types_match.terrain) << "'";
976
977 std::vector<std::string>::const_iterator flag;
978
979 for(flag = constraint->second.set_flag.begin(); flag != constraint->second.set_flag.end(); ++flag) {
980 PLAIN_LOG << ">>>>>> Set_flag: " << *flag;
981 }
982
983 for(flag = constraint->second.no_flag.begin(); flag != constraint->second.no_flag.end(); ++flag) {
984 PLAIN_LOG << ">>>>>> No_flag: " << *flag;
985 }
986 }
987
988 }
989#endif
990}
991
993{
994 // Build a config object
995 config cfg;
996
997 config& item = cfg.add_child("terrain_graphics");
998
999 config& tile = item.add_child("tile");
1000 tile["x"] = 0;
1001 tile["y"] = 0;
1003
1004 config& tile_image = tile.add_child("image");
1005 tile_image["layer"] = -1000;
1006 tile_image["name"] = image;
1007
1008 item["probability"] = 100;
1009 item["no_flag"] = "base";
1010 item["set_flag"] = "base";
1011
1012 // Parse the object
1014}
1015
1017 const map_location& loc,
1018 const terrain_constraint* type_checked) const
1019{
1020 // Don't match if the location isn't a multiple of mod_x and mod_y
1021 if(rule.modulo_constraints.x > 0 && (loc.x % rule.modulo_constraints.x != 0)) {
1022 return false;
1023 }
1024 if(rule.modulo_constraints.y > 0 && (loc.y % rule.modulo_constraints.y != 0)) {
1025 return false;
1026 }
1027
1028 if(rule.location_constraints.valid() && rule.location_constraints != loc) {
1029 return false;
1030 }
1031
1032 if(rule.probability != 100) {
1033 unsigned int random = get_noise(loc, rule.get_hash()) % 100;
1034 if(random > static_cast<unsigned int>(rule.probability)) {
1035 return false;
1036 }
1037 }
1038
1039 for(const terrain_constraint& cons : rule.constraints) {
1040 // Translated location
1041 const map_location tloc = legacy_sum(loc, cons.loc);
1042
1043 if(!tile_map_.on_map(tloc)) {
1044 return false;
1045 }
1046
1047 // std::cout << "testing..." << builder_letter(map().get_terrain(tloc))
1048
1049 // check if terrain matches except if we already know that it does
1050 if(&cons != type_checked && !terrain_matches(map().get_terrain(tloc), cons.terrain_types_match)) {
1051 return false;
1052 }
1053
1054 const std::set<std::string>& flags = tile_map_[tloc].flags;
1055
1056 for(const std::string& s : cons.no_flag) {
1057 // If a flag listed in "no_flag" is present, the rule does not match
1058 if(flags.find(s) != flags.end()) {
1059 return false;
1060 }
1061 }
1062 for(const std::string& s : cons.has_flag) {
1063 // If a flag listed in "has_flag" is not present, this rule does not match
1064 if(flags.find(s) == flags.end()) {
1065 return false;
1066 }
1067 }
1068 }
1069
1070 return true;
1071}
1072
1074{
1075 unsigned int rand_seed = get_noise(loc, rule.get_hash());
1076
1077 for(const terrain_constraint& constraint : rule.constraints) {
1079 if(!tile_map_.on_map(tloc)) {
1080 return;
1081 }
1082
1084
1085 if(!constraint.no_draw) {
1086 for(const rule_image& img : constraint.images) {
1087 btile.images.AGGREGATE_EMPLACE(&img, rand_seed);
1088 }
1089 }
1090
1091 // Sets flags
1092 for(const std::string& flag : constraint.set_flag) {
1093 btile.flags.insert(flag);
1094 }
1095 }
1096}
1097
1098// copied from text_surface::hash()
1099// but keep it separated because the needs are different
1100// and changing it will modify the map random variations
1101static unsigned int hash_str(const std::string& str)
1102{
1103 unsigned int h = 0;
1104 for(std::string::const_iterator it = str.begin(), it_end = str.end(); it != it_end; ++it)
1105 h = ((h << 9) | (h >> (sizeof(int) * 8 - 9))) ^ (*it);
1106 return h;
1107}
1108
1110{
1111 if(hash_ != DUMMY_HASH)
1112 return hash_;
1113
1114 for(const terrain_constraint& constraint : constraints) {
1115 for(const rule_image& ri : constraint.images) {
1116 for(const rule_image_variant& variant : ri.variants) {
1117 // we will often hash the same string, but that seems fast enough
1118 hash_ += hash_str(variant.image_string);
1119 }
1120 }
1121 }
1122
1123 // don't use the reserved dummy hash
1124 if(hash_ == DUMMY_HASH)
1125 hash_ = 105533; // just a random big prime number
1126
1127 return hash_;
1128}
1129
1131{
1132 log_scope("terrain_builder::build_terrains");
1133
1134 // Builds the terrain_by_type_ cache
1135 for(int x = -2; x <= map().w(); ++x) {
1136 for(int y = -2; y <= map().h(); ++y) {
1137 const map_location loc(x, y);
1139
1140 terrain_by_type_[t].push_back(loc);
1141
1142 // Flag all hexes according to whether they're on the border or not,
1143 // to make it easier for WML to draw the borders
1144 if(draw_border_&& !map().on_board(loc)) {
1145 tile_map_[loc].flags.insert("_border");
1146 } else {
1147 tile_map_[loc].flags.insert("_board");
1148 }
1149 }
1150 }
1151
1152 for(const building_rule& rule : building_rules_) {
1153 // Find the constraint that contains the less terrain of all terrain rules.
1154 // We will keep a track of the matching terrains of this constraint
1155 // and later try to apply the rule only on them
1156 std::size_t min_size = std::numeric_limits<int>::max();
1157 t_translation::ter_list min_types = t_translation::ter_list(); // <-- This must be explicitly initialized, just
1158 // as min_constraint is, at start of loop, or we
1159 // get a null pointer dereference when we go
1160 // through on later times.
1161 const terrain_constraint* min_constraint = nullptr;
1162
1163 for(const terrain_constraint& constraint : rule.constraints) {
1164 const t_translation::ter_match& match = constraint.terrain_types_match;
1166 std::size_t constraint_size = 0;
1167
1168 for(terrain_by_type_map::iterator type_it = terrain_by_type_.begin(); type_it != terrain_by_type_.end();
1169 ++type_it) {
1170 const t_translation::terrain_code t = type_it->first;
1171 if(terrain_matches(t, match)) {
1172 const std::size_t match_size = type_it->second.size();
1174 if(constraint_size >= min_size) {
1175 break; // not a minimum, bail out
1176 }
1177 matching_types.push_back(t);
1178 }
1179 }
1180
1185 if(min_size == 0) {
1186 // a constraint is never matched on this map
1187 // we break with a empty type list
1188 break;
1189 }
1190 }
1191 }
1192
1193 assert(min_constraint != nullptr);
1194
1195 // NOTE: if min_types is not empty, we have found a valid min_constraint;
1196 for(const auto& t : min_types) {
1199
1202 }
1203 }
1204 }
1205 }
1206}
1207
1209{
1210 if(tile_map_.on_map(loc))
1211 return &(tile_map_[loc]);
1212 return nullptr;
1213}
map_location loc
Definition move.cpp:172
#define WRN_NG
#define ERR_NG
double t
static map_location legacy_negation(const map_location &me)
These legacy map_location functions moved here from map_location.
Definition builder.cpp:56
static map_location & legacy_sum_assign(map_location &me, const map_location &a)
Definition builder.cpp:61
static lg::log_domain log_engine("engine")
static unsigned int hash_str(const std::string &str)
Definition builder.cpp:1101
static unsigned int get_noise(const map_location &loc, unsigned int index)
Definition builder.cpp:178
static std::vector< std::string > get_variations(const std::string &base, const std::string &variations)
Definition builder.cpp:396
static map_location legacy_sum(const map_location &me, const map_location &a)
Definition builder.cpp:74
static bool image_exists(const std::string &name)
Definition builder.cpp:383
static map_location legacy_difference(const map_location &me, const map_location &a)
Definition builder.cpp:81
Definitions for the terrain builder.
Variant for storing WML attributes.
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
config & add_child(std::string_view key)
Definition config.cpp:436
child_itors child_range(std::string_view key)
Definition config.cpp:268
void clear()
Definition config.cpp:802
A class grating read only view to a vector of config objects, viewed as one config with all children ...
static game_config_view wrap(const config &cfg)
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition map.cpp:265
int w() const
Effective map width.
Definition map.hpp:50
int h() const
Effective map height.
Definition map.hpp:53
bool empty() const
Tell if the map is of 0 size.
Definition map.hpp:68
Encapsulates the map of the game.
Definition map.hpp:176
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition map.cpp:78
static void progress(loading_stage stage=loading_stage::none)
Report what is being loaded to the loading screen.
Generic locator abstracting the location of an image.
Definition picture.hpp:59
static prefs & get()
tile & operator[](const map_location &loc)
Returns a reference to the tile which is at the position pointed by loc.
Definition builder.cpp:219
bool on_map(const map_location &loc) const
Tests if a location is on the map.
Definition builder.cpp:210
void reset()
Resets the whole tile map.
Definition builder.cpp:195
tilemap(int x, int y)
Constructs a tilemap of dimensions x * y.
Definition builder.cpp:187
void reload(int x, int y)
Rebuilds the map to a new set of dimensions.
Definition builder.cpp:201
void parse_mapstring(const std::string &mapstring, struct building_rule &br, anchormap &anchors, const config &global_images)
Parses a map string (the map= element of a [terrain_graphics] rule, and adds constraints from this ma...
Definition builder.cpp:796
std::vector< animated< image::locator > > imagelist
A shorthand typedef for a list of animated image locators, the base data type returned by the get_ter...
Definition builder.hpp:74
std::multiset< building_rule > building_ruleset
A set of building rules.
Definition builder.hpp:468
void parse_config(const config &cfg, bool local=true)
Parses a configuration object containing [terrain_graphics] rules, and fills the building_rules_ memb...
Definition builder.cpp:876
tile * get_tile(const map_location &loc)
Definition builder.cpp:1208
void add_rule(building_ruleset &rules, building_rule &rule)
Adds a rule to a ruleset.
Definition builder.cpp:840
terrain_constraint & add_constraints(constraint_set &constraints, const map_location &loc, const t_translation::ter_match &type, const config &global_images)
Creates a rule constraint object which matches a given list of terrains, and adds it to the list of c...
Definition builder.cpp:739
void rotate_rule(building_rule &rule, int angle, const std::vector< std::string > &angle_name)
Rotates a template rule to a given angle.
Definition builder.cpp:615
bool draw_border_
Whether the map border should be drawn.
Definition builder.hpp:797
void add_off_map_rule(const std::string &image)
Adds a builder rule for the _off^_usr tile, this tile only has 1 image.
Definition builder.cpp:992
void build_terrains()
Calculates the list of terrains, and fills the tile_map_ member, from the gamemap and the building_ru...
Definition builder.cpp:1130
void add_images_from_config(rule_imagelist &images, const config &cfg, bool global, int dx=0, int dy=0)
Parses a "config" object, which should contains [image] children, and adds the corresponding parsed r...
Definition builder.cpp:680
void rebuild_terrain(const map_location &loc)
Performs a "quick-rebuild" of the terrain in a given location.
Definition builder.cpp:346
bool update_animation(const map_location &loc)
Updates the animation at a given tile.
Definition builder.cpp:320
static building_ruleset building_rules_
Parsed terrain rules.
Definition builder.hpp:800
static const unsigned int DUMMY_HASH
Definition builder.hpp:69
const gamemap & map() const
Definition builder.hpp:99
bool rule_matches(const building_rule &rule, const map_location &loc, const terrain_constraint *type_checked) const
Checks whether a rule matches a given location in the map.
Definition builder.cpp:1016
void change_map(const gamemap *m)
Definition builder.cpp:292
static void set_terrain_rules_cfg(const game_config_view &cfg)
Set the config where we will parse the global terrain rules.
Definition builder.cpp:279
std::vector< terrain_constraint > constraint_set
The list of constraints attached to a terrain_graphics WML rule.
Definition builder.hpp:356
const int tilewidth_
The tile width used when using basex and basey.
Definition builder.hpp:351
const gamemap * map_
A pointer to the gamemap class used in the current level.
Definition builder.hpp:777
bool terrain_matches(const t_translation::terrain_code &tcode, const t_translation::ter_list &terrains) const
Checks whether a terrain code matches a given list of terrain codes.
Definition builder.hpp:727
const imagelist * get_terrain_at(const map_location &loc, const std::string &tod, TERRAIN_TYPE const terrain_type)
Returns a vector of strings representing the images to load & blit together to get the built content ...
Definition builder.cpp:298
bool load_images(building_rule &rule)
Load images and tests for validity of a rule.
Definition builder.cpp:422
tilemap tile_map_
The tile_map_ for the current level, which is filled by the build_terrains_ method to contain "tiles"...
Definition builder.hpp:784
void apply_rule(const building_rule &rule, const map_location &loc)
Applies a rule at a given location: applies the result of a matching rule at a given location: attach...
Definition builder.cpp:1073
void flush_local_rules()
Definition builder.cpp:268
void rotate(terrain_constraint &constraint, int angle)
"Rotates" a constraint from a rule.
Definition builder.cpp:482
void rebuild_all()
Performs a complete rebuild of the list of terrain graphics attached to a map.
Definition builder.cpp:376
std::multimap< int, map_location > anchormap
Definition builder.hpp:654
void add_rotated_rules(building_ruleset &rules, building_rule &tpl, const std::string &rotations)
Adds a set of rules to a ruleset, from a template rule which spans 6 rotations (or less if some of th...
Definition builder.cpp:847
TERRAIN_TYPE
Used as a parameter for the get_terrain_at function.
Definition builder.hpp:51
@ BACKGROUND
Represents terrains which are to be drawn behind unit sprites.
Definition builder.hpp:52
std::vector< rule_image > rule_imagelist
A shorthand notation for a vector of rule_images.
Definition builder.hpp:258
terrain_by_type_map terrain_by_type_
A map representing all locations whose terrain is of a given type.
Definition builder.hpp:794
static const game_config_view * rules_cfg_
Config used to parse global terrain rules.
Definition builder.hpp:803
void parse_global_config(const game_config_view &cfg)
Definition builder.hpp:701
void reload_map()
Updates internals that cache map size.
Definition builder.cpp:285
void replace_rotate_tokens(std::string &s, int angle, const std::vector< std::string > &replacement)
Replaces, in a given string, rotation tokens with their values.
Definition builder.cpp:562
void rebuild_cache_all()
Definition builder.cpp:259
terrain_builder(const config &level, const gamemap *map, const std::string &offmap_image, bool draw_border)
Constructor for the terrain_builder class.
Definition builder.cpp:233
const std::string & minimap_image_overlay() const
Definition terrain.hpp:46
const std::string & minimap_image() const
Definition terrain.hpp:45
const config * cfg
Standard logging facilities (interface).
#define PLAIN_LOG
Definition log.hpp:296
#define log_scope(description)
Definition log.hpp:275
Game configuration data as global variables.
Functions to load and save images from/to disk.
bool is_empty_hex(const locator &i_locator)
Checks if an image is empty after hex masking.
Definition picture.cpp:835
bool precached_file_exists(const std::string &file)
Definition picture.cpp:911
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition picture.cpp:855
void precache_file_existence(const std::string &subdir)
Precache the existence of files in a binary path subdirectory (e.g.
Definition picture.cpp:904
constexpr terrain_code NONE_TERRAIN
const ter_layer TB_STAR
constexpr ter_layer WILDCARD
const terrain_code STAR
std::vector< terrain_code > ter_list
ter_map read_builder_map(const std::string &str)
Reads a builder map.
const ter_layer TB_DOT
std::string write_terrain_code(const terrain_code &tcode)
Writes a single terrain code to a string.
const terrain_code OFF_MAP_USER
std::string write_list(const ter_list &list)
Writes a list of terrains to a string, only writes the new format.
std::vector< std::string > square_parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Similar to parenthetical_split, but also expands embedded square brackets.
std::vector< std::string > split(const config_attribute_value &val)
int w
Definition pathfind.cpp:188
std::string filename
Filename.
Base class for all the errors encountered by the engine.
Encapsulates the map of the game.
Definition location.hpp:46
bool valid() const
Definition location.hpp:111
This structure can be used for matching terrain strings.
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
The in-memory representation of a [terrain_graphics] WML rule.
Definition builder.hpp:362
unsigned int get_hash() const
Definition builder.cpp:1109
std::vector< std::string > has_flag
Definition builder.hpp:212
rule_image_variant(const std::string &image_string, const std::string &variations, const std::chrono::milliseconds &random_start=std::chrono::milliseconds{-1})
Constructor for the normal default case.
Definition builder.cpp:647
std::set< std::string > tods
The Time of Day associated to this variant (if any)
Definition builder.hpp:210
Each terrain_graphics rule is associated a set of images, which are applied on the terrain if the rul...
Definition builder.hpp:227
The in-memory representation of a [tile] WML rule inside of a [terrain_graphics] WML rule.
Definition builder.hpp:265
Represent a rule_image applied with a random seed.
Definition builder.hpp:306
Represents a tile of the game map, with all associated builder-specific parameters: flags,...
Definition builder.hpp:285
std::vector< rule_image_rand > images
The list of rule_images and random seeds associated to this tile.
Definition builder.hpp:323
std::vector< log_details > logs
Definition builder.hpp:288
void clear()
Clears all data in this tile, and resets the cache.
Definition builder.cpp:168
void rebuild_cache(const std::string &tod, logs *log=nullptr)
Rebuilds the whole image cache, for a given time-of-day.
Definition builder.cpp:92
imagelist images_background
The list of images which are behind the unit sprites, attached to this tile.
Definition builder.hpp:334
imagelist images_foreground
The list of images which are in front of the unit sprites, attached to this tile.
Definition builder.hpp:329
bool sorted_images
Indicates if 'images' is sorted.
Definition builder.hpp:341
std::set< std::string > flags
The list of flags present in this tile.
Definition builder.hpp:302
static map_location::direction n
static map_location::direction s
#define i
#define h
#define c
#define b