1 /*
2    Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
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 to manage units.
18  */
19 
20 #include "units/unit.hpp"
21 
22 #include "color.hpp"
23 #include "deprecation.hpp"
24 #include "display_context.hpp"
25 #include "formatter.hpp"
26 #include "formula/string_utils.hpp" // for VGETTEXT
27 #include "game_board.hpp"			// for game_board
28 #include "game_config.hpp"			// for add_color_info, etc
29 #include "game_data.hpp"
30 #include "game_errors.hpp"		   // for game_error
31 #include "game_events/manager.hpp" // for add_events
32 #include "preferences/game.hpp"	// for encountered_units
33 #include "gettext.hpp"			   // for N_
34 #include "lexical_cast.hpp"
35 #include "log.hpp"						 // for LOG_STREAM, logger, etc
36 #include "map/map.hpp"					 // for gamemap
37 #include "random.hpp"				 // for generator, rng
38 #include "resources.hpp"				 // for units, gameboard, teams, etc
39 #include "scripting/game_lua_kernel.hpp" // for game_lua_kernel
40 #include "side_filter.hpp"				 // for side_filter
41 #include "synced_context.hpp"
42 #include "team.hpp"						 // for team, get_teams, etc
43 #include "terrain/filter.hpp"			 // for terrain_filter
44 #include "units/abilities.hpp"			 // for effect, filter_base_matches
45 #include "units/animation.hpp"			 // for unit_animation
46 #include "units/animation_component.hpp" // for unit_animation_component
47 #include "units/filter.hpp"
48 #include "units/formula_manager.hpp" // for unit_formula_manager
49 #include "units/id.hpp"
50 #include "units/map.hpp"	   // for unit_map, etc
51 #include "variable.hpp"		   // for vconfig, etc
52 #include "game_version.hpp"
53 
54 #include "utils/functional.hpp"
55 #include <boost/dynamic_bitset.hpp>
56 #include <boost/function_output_iterator.hpp>
57 
58 #ifdef _MSC_VER
59 #pragma warning (push)
60 #pragma warning (disable: 4510 4610)
61 #endif
62 #include <boost/range/algorithm.hpp>
63 #ifdef _MSC_VER
64 #pragma warning (pop)
65 #endif
66 
67 #include <cassert>                     // for assert
68 #include <cstdlib>                     // for rand
69 #include <exception>                    // for exception
70 #include <iterator>                     // for back_insert_iterator, etc
71 #include <new>                          // for operator new
72 #include <ostream>                      // for operator<<, basic_ostream, etc
73 
74 namespace t_translation { struct terrain_code; }
75 
76 static lg::log_domain log_unit("unit");
77 #define DBG_UT LOG_STREAM(debug, log_unit)
78 #define LOG_UT LOG_STREAM(info, log_unit)
79 #define WRN_UT LOG_STREAM(warn, log_unit)
80 #define ERR_UT LOG_STREAM(err, log_unit)
81 
82 namespace
83 {
84 	// "advance" only kept around for backwards compatibility; only "advancement" should be used
85 	const std::array<std::string, 4> ModificationTypes {{ "advancement", "advance", "trait", "object" }};
86 
87 	/**
88 	 * Pointers to units which have data in their internal caches. The
89 	 * destructor of an unit removes itself from the cache, so the pointers are
90 	 * always valid.
91 	 */
92 	static std::vector<const unit*> units_with_cache;
93 
94 	static const std::string leader_crown_path = "misc/leader-crown.png";
95 	static std::string internalized_attrs[] {
96 		"type",
97 		"id",
98 		"name",
99 		"gender",
100 		"random_gender",
101 		"variation",
102 		"role",
103 		"ai_special",
104 		"side",
105 		"underlying_id",
106 		"overlays",
107 		"facing",
108 		"race",
109 		"level",
110 		"recall_cost",
111 		"undead_variation",
112 		"max_attacks",
113 		"attacks_left",
114 		"alpha",
115 		"zoc",
116 		"flying",
117 		"cost",
118 		"max_hitpoints",
119 		"max_moves",
120 		"vision",
121 		"jamming",
122 		"max_experience",
123 		"advances_to",
124 		"hitpoints",
125 		"goto_x",
126 		"goto_y",
127 		"moves",
128 		"experience",
129 		"resting",
130 		"unrenamable",
131 		"alignment",
132 		"canrecruit",
133 		"extra_recruit",
134 		"x",
135 		"y",
136 		"placement",
137 		"parent_type",
138 		"description",
139 		"usage",
140 		"halo",
141 		"ellipse",
142 		"upkeep",
143 		"random_traits",
144 		"generate_name",
145 		"profile",
146 		"small_profile",
147 		"fire_event",
148 		"passable",
149 		"overwrite",
150 		"location_id",
151 		"hidden",
152 		// Useless attributes created when saving units to WML:
153 		"flag_rgb",
154 		"language_name",
155 		"image",
156 		"image_icon"
157 	};
158 
159 	struct internalized_attrs_sorter
160 	{
internalized_attrs_sorter__anonf047bb650111::internalized_attrs_sorter161 		internalized_attrs_sorter()
162 		{
163 			std::sort(std::begin(internalized_attrs), std::end(internalized_attrs));
164 		}
165 	};
166 
167 	// Sort the array to make set_difference below work.
168 	internalized_attrs_sorter sorter;
169 
warn_unknown_attribute(const config::const_attr_itors & cfg)170 	void warn_unknown_attribute(const config::const_attr_itors& cfg)
171 	{
172 		config::const_attribute_iterator cur = cfg.begin();
173 		config::const_attribute_iterator end = cfg.end();
174 
175 		const std::string* cur_known = std::begin(internalized_attrs);
176 		const std::string* end_known = std::end(internalized_attrs);
177 
178 		while(cur_known != end_known) {
179 			if(cur == end) {
180 				return;
181 			}
182 			int comp = cur->first.compare(*cur_known);
183 			if(comp < 0) {
184 				WRN_UT << "Unknown attribute '" << cur->first << "' discarded." << std::endl;
185 				++cur;
186 			}
187 			else if(comp == 0) {
188 				++cur;
189 				++cur_known;
190 			}
191 			else {
192 				++cur_known;
193 			}
194 		}
195 
196 		while(cur != end) {
197 			WRN_UT << "Unknown attribute '" << cur->first << "' discarded." << std::endl;
198 			++cur;
199 		}
200 	}
201 
202 	template<typename T>
copy_or_null(const std::unique_ptr<T> & ptr)203 	T* copy_or_null(const std::unique_ptr<T>& ptr)
204 	{
205 		return ptr ? new T(*ptr) : nullptr;
206 	}
207 } // end anon namespace
208 
209 /**
210  * Intrusive Pointer interface
211  *
212  **/
213 
intrusive_ptr_add_ref(const unit * u)214 void intrusive_ptr_add_ref(const unit* u)
215 {
216 	assert(u->ref_count_ >= 0);
217 	// the next code line is to notice possible wrongly initialized units.
218 	// The 100000 is picked rather randomly. If you are in the situation
219 	// that you can actually have more then 100000 intrusive_ptr to one unit
220 	// or if you are sure that the refcounting system works
221 	// then feel free to remove the next line
222 	assert(u->ref_count_ < 100000);
223 	if(u->ref_count_ == 0) {
224 		LOG_UT << "Freshly constructed" << std::endl;
225 	}
226 	++(u->ref_count_);
227 }
228 
intrusive_ptr_release(const unit * u)229 void intrusive_ptr_release(const unit* u)
230 {
231 	assert(u->ref_count_ >= 1);
232 	assert(u->ref_count_ < 100000); //See comment in intrusive_ptr_add_ref
233 	if(--(u->ref_count_) == 0)
234 	{
235 		DBG_UT << "Deleting a unit: id = " << u->id() << ", uid = " << u->underlying_id() << std::endl;
236 		delete u;
237 	}
238 }
239 
240 /**
241  * Converts a string ID to a unit_type.
242  * Throws a game_error exception if the string does not correspond to a type.
243  */
get_unit_type(const std::string & type_id)244 static const unit_type& get_unit_type(const std::string& type_id)
245 {
246 	if(type_id.empty()) {
247 		throw unit_type::error("creating unit with an empty type field");
248 	}
249 	std::string new_id = type_id;
250 	unit_type::check_id(new_id);
251 	const unit_type* i = unit_types.find(new_id);
252 	if(!i) throw unit_type::error("unknown unit type: " + type_id);
253 	return *i;
254 }
255 
generate_gender(const unit_type & type,bool random_gender)256 static unit_race::GENDER generate_gender(const unit_type& type, bool random_gender)
257 {
258 	const std::vector<unit_race::GENDER>& genders = type.genders();
259 	assert(genders.size() > 0);
260 
261 	if(random_gender == false  ||  genders.size() == 1) {
262 		return genders.front();
263 	} else {
264 		return genders[randomness::generator->get_random_int(0,genders.size()-1)];
265 		// Note: genders is guaranteed to be non-empty, so this is not a
266 		// potential division by zero.
267 		// Note: Whoever wrote this code, you should have used an assertion, to save others hours of work...
268 		// If the assertion size>0 is failing for you, one possible cause is that you are constructing a unit
269 		// from a unit type which has not been ``built'' using the unit_type_data methods.
270 	}
271 }
272 
generate_gender(const unit_type & u_type,const config & cfg)273 static unit_race::GENDER generate_gender(const unit_type& u_type, const config& cfg)
274 {
275 	const std::string& gender = cfg["gender"];
276 	if(!gender.empty()) {
277 		return string_gender(gender);
278 	}
279 
280 	return generate_gender(u_type, cfg["random_gender"].to_bool());
281 }
282 
283 struct ptr_vector_pushback
284 {
ptr_vector_pushbackptr_vector_pushback285 	ptr_vector_pushback(boost::ptr_vector<config>& vec) : vec_(&vec) {}
286 
operator ()ptr_vector_pushback287 	void operator()(const config& cfg)
288 	{
289 		vec_->push_back(new config(cfg));
290 	}
291 
292 	//Don't use reference to be copyable.
293 	boost::ptr_vector<config>* vec_;
294 };
295 
296 // Copy constructor
unit(const unit & o)297 unit::unit(const unit& o)
298 	: ref_count_(0)
299 	, loc_(o.loc_)
300 	, advances_to_(o.advances_to_)
301 	, type_(o.type_)
302 	, type_name_(o.type_name_)
303 	, race_(o.race_)
304 	, id_(o.id_)
305 	, name_(o.name_)
306 	, underlying_id_(o.underlying_id_)
307 	, undead_variation_(o.undead_variation_)
308 	, variation_(o.variation_)
309 	, hit_points_(o.hit_points_)
310 	, max_hit_points_(o.max_hit_points_)
311 	, experience_(o.experience_)
312 	, max_experience_(o.max_experience_)
313 	, level_(o.level_)
314 	, recall_cost_(o.recall_cost_)
315 	, canrecruit_(o.canrecruit_)
316 	, recruit_list_(o.recruit_list_)
317 	, alignment_(o.alignment_)
318 	, flag_rgb_(o.flag_rgb_)
319 	, image_mods_(o.image_mods_)
320 	, unrenamable_(o.unrenamable_)
321 	, side_(o.side_)
322 	, gender_(o.gender_)
323 	, formula_man_(new unit_formula_manager(o.formula_manager()))
324 	, movement_(o.movement_)
325 	, max_movement_(o.max_movement_)
326 	, vision_(o.vision_)
327 	, jamming_(o.jamming_)
328 	, movement_type_(o.movement_type_)
329 	, hold_position_(o.hold_position_)
330 	, end_turn_(o.end_turn_)
331 	, resting_(o.resting_)
332 	, attacks_left_(o.attacks_left_)
333 	, max_attacks_(o.max_attacks_)
334 	, states_(o.states_)
335 	, known_boolean_states_(o.known_boolean_states_)
336 	, variables_(o.variables_)
337 	, events_(o.events_)
338 	, filter_recall_(o.filter_recall_)
339 	, emit_zoc_(o.emit_zoc_)
340 	, overlays_(o.overlays_)
341 	, role_(o.role_)
342 	, attacks_(o.attacks_)
343 	, facing_(o.facing_)
344 	, trait_names_(o.trait_names_)
345 	, trait_descriptions_(o.trait_descriptions_)
346 	, unit_value_(o.unit_value_)
347 	, goto_(o.goto_)
348 	, interrupted_move_(o.interrupted_move_)
349 	, is_fearless_(o.is_fearless_)
350 	, is_healthy_(o.is_healthy_)
351 	, modification_descriptions_(o.modification_descriptions_)
352 	, anim_comp_(new unit_animation_component(*this, *o.anim_comp_))
353 	, hidden_(o.hidden_)
354 	, hp_bar_scaling_(o.hp_bar_scaling_)
355 	, xp_bar_scaling_(o.xp_bar_scaling_)
356 	, modifications_(o.modifications_)
357 	, abilities_(o.abilities_)
358 	, advancements_(o.advancements_)
359 	, description_(o.description_)
360 	, usage_(copy_or_null(o.usage_))
361 	, halo_(copy_or_null(o.halo_))
362 	, ellipse_(copy_or_null(o.ellipse_))
363 	, random_traits_(o.random_traits_)
364 	, generate_name_(o.generate_name_)
365 	, upkeep_(o.upkeep_)
366 	, profile_(o.profile_)
367 	, small_profile_(o.small_profile_)
368 	, invisibility_cache_()
369 {
370 	// Copy the attacks rather than just copying references
371 	for(auto& a : attacks_) {
372 		a.reset(new attack_type(*a));
373 	}
374 }
375 
unit()376 unit::unit()
377 	: ref_count_(0)
378 	, loc_()
379 	, advances_to_()
380 	, type_(nullptr)
381 	, type_name_()
382 	, race_(&unit_race::null_race)
383 	, id_()
384 	, name_()
385 	, underlying_id_(0)
386 	, undead_variation_()
387 	, variation_()
388 	, hit_points_(1)
389 	, max_hit_points_(1)
390 	, experience_(0)
391 	, max_experience_(1)
392 	, level_(0)
393 	, recall_cost_(-1)
394 	, canrecruit_(false)
395 	, recruit_list_()
396 	, alignment_()
397 	, flag_rgb_()
398 	, image_mods_()
399 	, unrenamable_(false)
400 	, side_(0)
401 	, gender_(unit_race::NUM_GENDERS)
402 	, formula_man_(new unit_formula_manager())
403 	, movement_(0)
404 	, max_movement_(0)
405 	, vision_(-1)
406 	, jamming_(0)
407 	, movement_type_()
408 	, hold_position_(false)
409 	, end_turn_(false)
410 	, resting_(false)
411 	, attacks_left_(0)
412 	, max_attacks_(0)
413 	, states_()
414 	, known_boolean_states_()
415 	, variables_()
416 	, events_()
417 	, filter_recall_()
418 	, emit_zoc_(0)
419 	, overlays_()
420 	, role_()
421 	, attacks_()
422 	, facing_(map_location::NDIRECTIONS)
423 	, trait_names_()
424 	, trait_descriptions_()
425 	, unit_value_()
426 	, goto_()
427 	, interrupted_move_()
428 	, is_fearless_(false)
429 	, is_healthy_(false)
430 	, modification_descriptions_()
431 	, anim_comp_(new unit_animation_component(*this))
432 	, hidden_(false)
433 	, hp_bar_scaling_(0)
434 	, xp_bar_scaling_(0)
435 	, modifications_()
436 	, abilities_()
437 	, advancements_()
438 	, description_()
439 	, usage_()
440 	, halo_()
441 	, ellipse_()
442 	, random_traits_(true)
443 	, generate_name_(true)
444 	, upkeep_(upkeep_full())
445 	, invisibility_cache_()
446 {
447 
448 }
449 
init(const config & cfg,bool use_traits,const vconfig * vcfg)450 void unit::init(const config& cfg, bool use_traits, const vconfig* vcfg)
451 {
452 	loc_ = map_location(cfg["x"], cfg["y"], wml_loc());
453 	type_ = &get_unit_type(cfg["parent_type"].blank() ? cfg["type"].str() : cfg["parent_type"].str());
454 	race_ = &unit_race::null_race;
455 	id_ = cfg["id"].str();
456 	name_ = cfg["name"].t_str();
457 	variation_ = cfg["variation"].empty() ? type_->default_variation() : cfg["variation"].str();
458 	canrecruit_ = cfg["canrecruit"].to_bool();
459 	gender_ = generate_gender(*type_, cfg);
460 	role_ = cfg["role"].str();
461 	//, facing_(map_location::NDIRECTIONS)
462 	//, anim_comp_(new unit_animation_component(*this))
463 	hidden_ = cfg["hidden"].to_bool(false);
464 	hp_bar_scaling_ = cfg["hp_bar_scaling"].blank() ? type_->hp_bar_scaling() : cfg["hp_bar_scaling"];
465 	xp_bar_scaling_ = cfg["xp_bar_scaling"].blank() ? type_->xp_bar_scaling() : cfg["xp_bar_scaling"];
466 	random_traits_ = true;
467 	generate_name_ = true;
468 	side_ = cfg["side"].to_int();
469 
470 	if(side_ <= 0) {
471 		side_ = 1;
472 	}
473 
474 	validate_side(side_);
475 	underlying_id_ = n_unit::unit_id(cfg["underlying_id"].to_size_t());
476 	set_underlying_id(resources::gameboard ? resources::gameboard->unit_id_manager() : n_unit::id_manager::global_instance());
477 
478 	overlays_ = utils::parenthetical_split(cfg["overlays"], ',');
479 	if(overlays_.size() == 1 && overlays_.front().empty()) {
480 		overlays_.clear();
481 	}
482 
483 	if(vcfg) {
484 		const vconfig& filter_recall = vcfg->child("filter_recall");
485 		if(!filter_recall.null())
486 			filter_recall_ = filter_recall.get_config();
487 
488 		const vconfig::child_list& events = vcfg->get_children("event");
489 		for(const vconfig& e : events) {
490 			events_.add_child("event", e.get_config());
491 		}
492 	} else {
493 		filter_recall_ = cfg.child_or_empty("filter_recall");
494 
495 		for(const config& unit_event : cfg.child_range("event")) {
496 			events_.add_child("event", unit_event);
497 		}
498 	}
499 
500 	if(resources::game_events) {
501 		resources::game_events->add_events(events_.child_range("event"));
502 	}
503 
504 	random_traits_ = cfg["random_traits"].to_bool(true);
505 	facing_ = map_location::parse_direction(cfg["facing"]);
506 	if(facing_ == map_location::NDIRECTIONS) facing_ = static_cast<map_location::DIRECTION>(randomness::rng::default_instance().get_random_int(0, map_location::NDIRECTIONS-1));
507 
508 	if(const config& mods = cfg.child("modifications")) {
509 		modifications_ = mods;
510 	}
511 
512 	generate_name_ = cfg["generate_name"].to_bool(true);
513 
514 	// Apply the unit type's data to this unit.
515 	advance_to(*type_, use_traits);
516 
517 	if(const config& variables = cfg.child("variables")) {
518 		variables_ = variables;
519 	}
520 
521 	if(const config::attribute_value* v = cfg.get("race")) {
522 		if(const unit_race *r = unit_types.find_race(*v)) {
523 			race_ = r;
524 		} else {
525 			race_ = &unit_race::null_race;
526 		}
527 	}
528 
529 	level_ = cfg["level"].to_int(level_);
530 
531 	if(const config::attribute_value* v = cfg.get("undead_variation")) {
532 		undead_variation_ = v->str();
533 	}
534 
535 	if(const config::attribute_value* v = cfg.get("max_attacks")) {
536 		max_attacks_ = std::max(0, v->to_int(1));
537 	}
538 
539 	attacks_left_ = std::max(0, cfg["attacks_left"].to_int(max_attacks_));
540 
541 	if(const config::attribute_value* v = cfg.get("zoc")) {
542 		emit_zoc_ = v->to_bool(level_ > 0);
543 	}
544 
545 	if(const config::attribute_value* v = cfg.get("description")) {
546 		description_ = *v;
547 	}
548 
549 	if(const config::attribute_value* v = cfg.get("cost")) {
550 		unit_value_ = *v;
551 	}
552 
553 	if(const config::attribute_value* v = cfg.get("ellipse")) {
554 		set_image_ellipse(*v);
555 	}
556 
557 	if(const config::attribute_value* v = cfg.get("halo")) {
558 		set_image_halo(*v);
559 	}
560 
561 	if(const config::attribute_value* v = cfg.get("usage")) {
562 		set_usage(*v);
563 	}
564 
565 	if(const config::attribute_value* v = cfg.get("profile")) {
566 		std::string profile = (*v).str();
567 		adjust_profile(profile);
568 		profile_ = profile;
569 	}
570 
571 	if(const config::attribute_value* v = cfg.get("small_profile")) {
572 		small_profile_ = (*v).str();
573 	}
574 
575 	max_hit_points_ = std::max(1, cfg["max_hitpoints"].to_int(max_hit_points_));
576 	max_movement_ = std::max(0, cfg["max_moves"].to_int(max_movement_));
577 	max_experience_ = std::max(1, cfg["max_experience"].to_int(max_experience_));
578 
579 	vision_ = cfg["vision"].to_int(vision_);
580 
581 	std::vector<std::string> temp_advances = utils::split(cfg["advances_to"]);
582 	if(temp_advances.size() == 1 && temp_advances.front() == "null") {
583 		advances_to_.clear();
584 	} else if(temp_advances.size() >= 1 && !temp_advances.front().empty()) {
585 		advances_to_ = temp_advances;
586 	}
587 
588 	if(const config& ai = cfg.child("ai")) {
589 		formula_man_->read(ai);
590 	}
591 
592 	// Don't use the unit_type's attacks if this config has its own defined
593 	if(config::const_child_itors cfg_range = cfg.child_range("attack")) {
594 		attacks_.clear();
595 		for(const config& c : cfg_range) {
596 			attacks_.emplace_back(new attack_type(c));
597 		}
598 	}
599 
600 	// If cfg specifies [advancement]s, replace this [advancement]s with them.
601 	if(cfg.has_child("advancement")) {
602 		this->advancements_.clear();
603 		boost::copy(cfg.child_range("advancement"), boost::make_function_output_iterator(ptr_vector_pushback(advancements_)));
604 	}
605 
606 	// Don't use the unit_type's abilities if this config has its own defined
607 	// Why do we allow multiple [abilities] tags?
608 	if(config::const_child_itors cfg_range = cfg.child_range("abilities")) {
609 		abilities_.clear();
610 		for(const config& abilities : cfg_range) {
611 			this->abilities_.append(abilities);
612 		}
613 	}
614 
615 	// Adjust the unit's defense, movement, vision, jamming, resistances, and
616 	// flying status if this config has its own defined.
617 	movement_type_.merge(cfg);
618 
619 	if(const config& status_flags = cfg.child("status")) {
620 		for(const config::attribute &st : status_flags.attribute_range()) {
621 			if(st.second.to_bool()) {
622 				set_state(st.first, true);
623 			}
624 		}
625 	}
626 
627 	if(cfg["ai_special"] == "guardian") {
628 		set_state(STATE_GUARDIAN, true);
629 	}
630 
631 	if(const config::attribute_value* v = cfg.get("hitpoints")) {
632 		hit_points_ = *v;
633 	} else {
634 		hit_points_ = max_hit_points_;
635 	}
636 
637 	if(const config::attribute_value* v = cfg.get("invulnerable")) {
638 		set_state("invulnerable", v->to_bool());
639 	}
640 
641 	goto_.set_wml_x(cfg["goto_x"].to_int());
642 	goto_.set_wml_y(cfg["goto_y"].to_int());
643 
644 	if(const config::attribute_value* v = cfg.get("moves")) {
645 		movement_ = *v;
646 		if(movement_ < 0) {
647 			attacks_left_ = 0;
648 			movement_ = 0;
649 		}
650 	} else {
651 		movement_ = max_movement_;
652 	}
653 
654 	experience_ = cfg["experience"];
655 	resting_ = cfg["resting"].to_bool();
656 	unrenamable_ = cfg["unrenamable"].to_bool();
657 
658 	// We need to check to make sure that the cfg is not blank and if it
659 	// isn't pull that value otherwise it goes with the default of -1.
660 	if(!cfg["recall_cost"].blank()) {
661 		recall_cost_ = cfg["recall_cost"].to_int(recall_cost_);
662 	}
663 
664 	alignment_.parse(cfg["alignment"].str());
665 
666 	generate_name();
667 
668 	parse_upkeep(cfg["upkeep"]);
669 
670 	set_recruits(utils::split(cfg["extra_recruit"]));
671 
672 	game_config::add_color_info(cfg);
673 	warn_unknown_attribute(cfg.attribute_range());
674 
675 #if 0
676 	// Debug unit animations for units as they appear in game
677 	for(const auto& anim : anim_comp_->animations_) {
678 		std::cout << anim.debug() << std::endl;
679 	}
680 #endif
681 }
682 
clear_status_caches()683 void unit::clear_status_caches()
684 {
685 	for(auto& u : units_with_cache) {
686 		u->clear_visibility_cache();
687 	}
688 
689 	units_with_cache.clear();
690 }
691 
init(const unit_type & u_type,int side,bool real_unit,unit_race::GENDER gender)692 void unit::init(const unit_type& u_type, int side, bool real_unit, unit_race::GENDER gender)
693 {
694 	type_ = &u_type;
695 	race_ = &unit_race::null_race;
696 	variation_ = type_->default_variation();
697 	side_ = side;
698 	gender_ = gender != unit_race::NUM_GENDERS ? gender : generate_gender(u_type, real_unit);
699 	facing_ = static_cast<map_location::DIRECTION>(randomness::rng::default_instance().get_random_int(0, map_location::NDIRECTIONS-1));
700 	upkeep_ = upkeep_full();
701 
702 	// Apply the unit type's data to this unit.
703 	advance_to(u_type, real_unit);
704 
705 	if(real_unit) {
706 		generate_name();
707 	}
708 
709 	set_underlying_id(resources::gameboard ? resources::gameboard->unit_id_manager() : n_unit::id_manager::global_instance());
710 
711 	// Set these after traits and modifications have set the maximums.
712 	movement_ = max_movement_;
713 	hit_points_ = max_hit_points_;
714 	attacks_left_ = max_attacks_;
715 }
716 
~unit()717 unit::~unit()
718 {
719 	try {
720 		anim_comp_->clear_haloes();
721 
722 		// Remove us from the status cache
723 		auto itor = std::find(units_with_cache.begin(), units_with_cache.end(), this);
724 
725 		if(itor != units_with_cache.end()) {
726 			units_with_cache.erase(itor);
727 		}
728 	} catch(const std::exception & e) {
729 		ERR_UT << "Caught exception when destroying unit: " << e.what() << std::endl;
730 	} catch(...) {}
731 }
732 
733 /**
734  * Swap, for copy and swap idiom
735  */
swap(unit & o)736 void unit::swap(unit & o)
737 {
738 	using std::swap;
739 
740 	// Don't swap reference count, or it will be incorrect...
741 	swap(loc_, o.loc_);
742 	swap(advances_to_, o.advances_to_);
743 	swap(type_, o.type_);
744 	swap(type_name_, o.type_name_);
745 	swap(race_, o.race_);
746 	swap(id_, o.id_);
747 	swap(name_, o.name_);
748 	swap(underlying_id_, o.underlying_id_);
749 	swap(undead_variation_, o.undead_variation_);
750 	swap(variation_, o.variation_);
751 	swap(hit_points_, o.hit_points_);
752 	swap(max_hit_points_, o.max_hit_points_);
753 	swap(experience_, o.experience_);
754 	swap(max_experience_, o.max_experience_);
755 	swap(level_, o.level_);
756 	swap(recall_cost_, o.recall_cost_);
757 	swap(canrecruit_, o.canrecruit_);
758 	swap(recruit_list_, o.recruit_list_);
759 	swap(alignment_, o.alignment_);
760 	swap(flag_rgb_, o.flag_rgb_);
761 	swap(image_mods_, o.image_mods_);
762 	swap(unrenamable_, o.unrenamable_);
763 	swap(side_, o.side_);
764 	swap(gender_, o.gender_);
765 	swap(formula_man_, o.formula_man_);
766 	swap(movement_, o.movement_);
767 	swap(max_movement_, o.max_movement_);
768 	swap(vision_, o.vision_);
769 	swap(jamming_, o.jamming_);
770 	swap(movement_type_, o.movement_type_);
771 	swap(hold_position_, o.hold_position_);
772 	swap(end_turn_, o.end_turn_);
773 	swap(resting_, o.resting_);
774 	swap(attacks_left_, o.attacks_left_);
775 	swap(max_attacks_, o.max_attacks_);
776 	swap(states_, o.states_);
777 	swap(known_boolean_states_, o.known_boolean_states_);
778 	swap(variables_, o.variables_);
779 	swap(events_, o.events_);
780 	swap(filter_recall_, o.filter_recall_);
781 	swap(emit_zoc_, o.emit_zoc_);
782 	swap(overlays_, o.overlays_);
783 	swap(role_, o.role_);
784 	swap(attacks_, o.attacks_);
785 	swap(facing_, o.facing_);
786 	swap(trait_names_, o.trait_names_);
787 	swap(trait_descriptions_, o.trait_descriptions_);
788 	swap(unit_value_, o.unit_value_);
789 	swap(goto_, o.goto_);
790 	swap(interrupted_move_, o.interrupted_move_);
791 	swap(is_fearless_, o.is_fearless_);
792 	swap(is_healthy_, o.is_healthy_);
793 	swap(modification_descriptions_, o.modification_descriptions_);
794 	swap(anim_comp_, o.anim_comp_);
795 	swap(hidden_, o.hidden_);
796 	swap(modifications_, o.modifications_);
797 	swap(invisibility_cache_, o.invisibility_cache_);
798 }
799 
generate_name()800 void unit::generate_name()
801 {
802 	if(!name_.empty() || !generate_name_) {
803 		return;
804 	}
805 	name_ = race_->generate_name(gender_);
806 	generate_name_ = false;
807 }
808 
generate_traits(bool must_have_only)809 void unit::generate_traits(bool must_have_only)
810 {
811 	LOG_UT << "Generating a trait for unit type " << type().log_id() << " with must_have_only " << must_have_only  << std::endl;
812 	const unit_type& u_type = type();
813 
814 	// Calculate the unit's traits
815 	config::const_child_itors current_traits = modifications_.child_range("trait");
816 	std::vector<const config*> candidate_traits;
817 
818 	for(const config& t : u_type.possible_traits()) {
819 		// Skip the trait if the unit already has it.
820 		const std::string& tid = t["id"];
821 		bool already = false;
822 		for(const config& mod : current_traits) {
823 			if(mod["id"] == tid) {
824 				already = true;
825 				break;
826 			}
827 		}
828 
829 		if(already) {
830 			continue;
831 		}
832 
833 		// Add the trait if it is mandatory.
834 		const std::string& avl = t["availability"];
835 		if(avl == "musthave") {
836 			modifications_.add_child("trait", t);
837 			current_traits = modifications_.child_range("trait");
838 			continue;
839 		}
840 
841 		// The trait is still available, mark it as a candidate for randomizing.
842 		// For leaders, only traits with availability "any" are considered.
843 		if(!must_have_only && (!can_recruit() || avl == "any")) {
844 			candidate_traits.push_back(&t);
845 		}
846 	}
847 
848 	if(must_have_only) return;
849 
850 	// Now randomly fill out to the number of traits required or until
851 	// there aren't any more traits.
852 	int nb_traits = current_traits.size();
853 	int max_traits = u_type.num_traits();
854 	for(; nb_traits < max_traits && !candidate_traits.empty(); ++nb_traits)
855 	{
856 		int num = randomness::generator->get_random_int(0,candidate_traits.size()-1);
857 		modifications_.add_child("trait", *candidate_traits[num]);
858 		candidate_traits.erase(candidate_traits.begin() + num);
859 	}
860 
861 	// Once random traits are added, don't do it again.
862 	// Such as when restoring a saved character.
863 	random_traits_ = false;
864 }
865 
get_traits_list() const866 std::vector<std::string> unit::get_traits_list() const
867 {
868 	std::vector<std::string> res;
869 
870 	for(const config& mod : modifications_.child_range("trait"))
871 	{
872 			// Make sure to return empty id trait strings as otherwise
873 			// names will not match in length (Bug #21967)
874 			res.push_back(mod["id"]);
875 	}
876 	return res;
877 }
878 
879 
880 /**
881  * Advances this unit to the specified type.
882  * Experience is left unchanged.
883  * Current hit point total is left unchanged unless it would violate max HP.
884  * Assumes gender_ and variation_ are set to their correct values.
885  */
advance_to(const unit_type & u_type,bool use_traits)886 void unit::advance_to(const unit_type& u_type, bool use_traits)
887 {
888 	appearance_changed_ = true;
889 	// For reference, the type before this advancement.
890 	const unit_type& old_type = type();
891 	// Adjust the new type for gender and variation.
892 	const unit_type& new_type = u_type.get_gender_unit_type(gender_).get_variation(variation_);
893 
894 	// Reset the scalar values first
895 	trait_names_.clear();
896 	trait_descriptions_.clear(),
897 	is_fearless_ = false;
898 	is_healthy_ = false;
899 
900 	// Clear modification-related caches
901 	modification_descriptions_.clear();
902 
903 	// build unit type ready to create units. Not sure if needed.
904 	new_type.get_cfg_for_units();
905 
906 	if(!new_type.usage().empty()) {
907 		 set_usage(new_type.usage());
908 	}
909 
910 	set_image_halo(new_type.halo());
911 	if(!new_type.ellipse().empty()) {
912 		set_image_ellipse(new_type.ellipse());
913 	}
914 
915 	generate_name_ &= new_type.generate_name();
916 	abilities_ = new_type.abilities_cfg();
917 	advancements_.clear();
918 
919 	for(const config& advancement : new_type.advancements()) {
920 		advancements_.push_back(new config(advancement));
921 	}
922 
923 	// If unit has specific profile, remember it and keep it after advancing
924 	if(small_profile_.empty() || small_profile_ == old_type.small_profile()) {
925 		small_profile_ = new_type.small_profile();
926 	}
927 
928 	if(profile_.empty() || profile_ == old_type.big_profile()) {
929 		profile_ = new_type.big_profile();
930 	}
931 	// NOTE: There should be no need to access old_cfg (or new_cfg) after this
932 	//       line. Particularly since the swap might have affected old_cfg.
933 
934 	advances_to_ = new_type.advances_to();
935 
936 	race_ = new_type.race();
937 	type_ = &new_type;
938 	type_name_ = new_type.type_name();
939 	description_ = new_type.unit_description();
940 	undead_variation_ = new_type.undead_variation();
941 	max_experience_ = new_type.experience_needed(false);
942 	level_ = new_type.level();
943 	recall_cost_ = new_type.recall_cost();
944 	alignment_ = new_type.alignment();
945 	max_hit_points_ = new_type.hitpoints();
946 	hp_bar_scaling_ = new_type.hp_bar_scaling();
947 	xp_bar_scaling_ = new_type.xp_bar_scaling();
948 	max_movement_ = new_type.movement();
949 	vision_ = new_type.vision(true);
950 	jamming_ = new_type.jamming();
951 	movement_type_ = new_type.movement_type();
952 	emit_zoc_ = new_type.has_zoc();
953 	attacks_.clear();
954 	std::transform(new_type.attacks().begin(), new_type.attacks().end(), std::back_inserter(attacks_), [](const attack_type& atk) {
955 		return std::make_shared<attack_type>(atk);
956 	});
957 	unit_value_ = new_type.cost();
958 
959 	max_attacks_ = new_type.max_attacks();
960 
961 	flag_rgb_ = new_type.flag_rgb();
962 
963 	upkeep_ = upkeep_full();
964 	parse_upkeep(new_type.get_cfg()["upkeep"]);
965 
966 	anim_comp_->reset_after_advance(&new_type);
967 
968 	if(random_traits_) {
969 		generate_traits(!use_traits);
970 	} else {
971 		// This will add any "musthave" traits to the new unit that it doesn't already have.
972 		// This covers the Dark Sorcerer advancing to Lich and gaining the "undead" trait,
973 		// but random and/or optional traits are not added,
974 		// and neither are inappropriate traits removed.
975 		generate_traits(true);
976 	}
977 
978 	// Apply modifications etc, refresh the unit.
979 	// This needs to be after type and gender are fixed,
980 	// since there can be filters on the modifications
981 	// that may result in different effects after the advancement.
982 	apply_modifications();
983 
984 	// Now that modifications are done modifying traits, check if poison should
985 	// be cleared.
986 	if(get_state("unpoisonable")) {
987 		set_state(STATE_POISONED, false);
988 	}
989 
990 	// Now that modifications are done modifying the maximum hit points,
991 	// enforce this maximum.
992 	if(hit_points_ > max_hit_points_) {
993 		hit_points_ = max_hit_points_;
994 	}
995 
996 	// In case the unit carries EventWML, apply it now
997 	if(resources::game_events) {
998 		resources::game_events->add_events(new_type.events(), new_type.id());
999 	}
1000 }
1001 
big_profile() const1002 std::string unit::big_profile() const
1003 {
1004 	if(!profile_.empty() && profile_ != "unit_image") {
1005 		return profile_;
1006 	}
1007 
1008 	return absolute_image();
1009 }
1010 
small_profile() const1011 std::string unit::small_profile() const
1012 {
1013 	if(!small_profile_.empty() && small_profile_ != "unit_image") {
1014 		return small_profile_;
1015 	}
1016 
1017 	if(!profile_.empty() && small_profile_ != "unit_image" && profile_ != "unit_image") {
1018 		return profile_;
1019 	}
1020 
1021 	return absolute_image();
1022 }
1023 
leader_crown()1024 const std::string& unit::leader_crown()
1025 {
1026 	return leader_crown_path;
1027 }
1028 
flag_rgb() const1029 const std::string& unit::flag_rgb() const
1030 {
1031 	return flag_rgb_.empty() ? game_config::unit_rgb : flag_rgb_;
1032 }
1033 
hp_color_impl(int hitpoints,int max_hitpoints)1034 static color_t hp_color_impl(int hitpoints, int max_hitpoints)
1035 {
1036 	double unit_energy = 0.0;
1037 	color_t energy_color {0,0,0,255};
1038 
1039 	if(max_hitpoints > 0) {
1040 		unit_energy = static_cast<double>(hitpoints)/static_cast<double>(max_hitpoints);
1041 	}
1042 
1043 	if(1.0 == unit_energy) {
1044 		energy_color.r = 33;
1045 		energy_color.g = 225;
1046 		energy_color.b = 0;
1047 	} else if(unit_energy > 1.0) {
1048 		energy_color.r = 100;
1049 		energy_color.g = 255;
1050 		energy_color.b = 100;
1051 	} else if(unit_energy >= 0.75) {
1052 		energy_color.r = 170;
1053 		energy_color.g = 255;
1054 		energy_color.b = 0;
1055 	} else if(unit_energy >= 0.5) {
1056 		energy_color.r = 255;
1057 		energy_color.g = 175;
1058 		energy_color.b = 0;
1059 	} else if(unit_energy >= 0.25) {
1060 		energy_color.r = 255;
1061 		energy_color.g = 155;
1062 		energy_color.b = 0;
1063 	} else {
1064 		energy_color.r = 255;
1065 		energy_color.g = 0;
1066 		energy_color.b = 0;
1067 	}
1068 
1069 	return energy_color;
1070 }
1071 
hp_color() const1072 color_t unit::hp_color() const
1073 {
1074 	return hp_color_impl(hitpoints(), max_hitpoints());
1075 }
1076 
hp_color(int new_hitpoints) const1077 color_t unit::hp_color(int new_hitpoints) const
1078 {
1079 	return hp_color_impl(new_hitpoints, hitpoints());
1080 }
1081 
xp_color() const1082 color_t unit::xp_color() const
1083 {
1084 	const color_t near_advance_color {255,255,255,255};
1085 	const color_t mid_advance_color  {150,255,255,255};
1086 	const color_t far_advance_color  {0,205,205,255};
1087 	const color_t normal_color       {0,160,225,255};
1088 	const color_t near_amla_color    {225,0,255,255};
1089 	const color_t mid_amla_color     {169,30,255,255};
1090 	const color_t far_amla_color     {139,0,237,255};
1091 	const color_t amla_color         {170,0,255,255};
1092 
1093 	const bool near_advance = static_cast<int>(experience_to_advance()) <= game_config::kill_experience;
1094 	const bool mid_advance  = static_cast<int>(experience_to_advance()) <= game_config::kill_experience*2;
1095 	const bool far_advance  = static_cast<int>(experience_to_advance()) <= game_config::kill_experience*3;
1096 
1097 	color_t color = normal_color;
1098 	bool major_amla = false;
1099 	for(const config& adv:get_modification_advances()){
1100 		major_amla |= adv["major_amla"].to_bool();
1101 	}
1102 	if(advances_to().size() ||major_amla){
1103 		if(near_advance){
1104 			color=near_advance_color;
1105 		} else if(mid_advance){
1106 			color=mid_advance_color;
1107 		} else if(far_advance){
1108 			color=far_advance_color;
1109 		}
1110 	} else if(get_modification_advances().size()){
1111 		if(near_advance){
1112 			color=near_amla_color;
1113 		} else if(mid_advance){
1114 			color=mid_amla_color;
1115 		} else if(far_advance){
1116 			color=far_amla_color;
1117 		} else {
1118 			color=amla_color;
1119 		}
1120 	}
1121 
1122 	return(color);
1123 }
1124 
set_recruits(const std::vector<std::string> & recruits)1125 void unit::set_recruits(const std::vector<std::string>& recruits)
1126 {
1127 	unit_types.check_types(recruits);
1128 	recruit_list_ = recruits;
1129 	//TODO crab
1130 	//info_.minimum_recruit_price = 0;
1131 	//ai::manager::get_singleton().raise_recruit_list_changed();
1132 }
1133 
advances_to_translated() const1134 const std::vector<std::string> unit::advances_to_translated() const
1135 {
1136 	std::vector<std::string> result;
1137 	for(const std::string& adv_type_id : advances_to_) {
1138 		if(const unit_type* adv_type = unit_types.find(adv_type_id)) {
1139 			result.push_back(adv_type->type_name());
1140 		} else {
1141 			WRN_UT << "unknown unit in advances_to list of type "
1142 			<< type().log_id() << ": " << adv_type_id << std::endl;
1143 		}
1144 	}
1145 
1146 	return result;
1147 }
1148 
set_advances_to(const std::vector<std::string> & advances_to)1149 void unit::set_advances_to(const std::vector<std::string>& advances_to)
1150 {
1151 	unit_types.check_types(advances_to);
1152 	advances_to_ = advances_to;
1153 }
1154 
set_movement(int moves,bool unit_action)1155 void unit::set_movement(int moves, bool unit_action)
1156 {
1157 	// If this was because the unit acted, clear its "not acting" flags.
1158 	if(unit_action) {
1159 		end_turn_ = hold_position_ = false;
1160 	}
1161 
1162 	movement_ = std::max<int>(0, moves);
1163 }
1164 
1165 /**
1166  * Determines if @a mod_dur "matches" @a goal_dur.
1167  * If goal_dur is not empty, they match if they are equal.
1168  * If goal_dur is empty, they match if mod_dur is neither empty nor "forever".
1169  * Helper function for expire_modifications().
1170  */
mod_duration_match(const std::string & mod_dur,const std::string & goal_dur)1171 inline bool mod_duration_match(const std::string& mod_dur, const std::string& goal_dur)
1172 {
1173 	if(goal_dur.empty()) {
1174 		// Default is all temporary modifications.
1175 		return !mod_dur.empty() && mod_dur != "forever";
1176 	}
1177 
1178 	return mod_dur == goal_dur;
1179 }
1180 
expire_modifications(const std::string & duration)1181 void unit::expire_modifications(const std::string& duration)
1182 {
1183 	// If any modifications expire, then we will need to rebuild the unit.
1184 	const unit_type* rebuild_from = nullptr;
1185 	int hp = hit_points_;
1186 	// Loop through all types of modifications.
1187 	for(const auto& mod_name : ModificationTypes) {
1188 		// Loop through all modifications of this type.
1189 		// Looping in reverse since we may delete the current modification.
1190 		for(int j = modifications_.child_count(mod_name)-1; j >= 0; --j)
1191 		{
1192 			const config& mod = modifications_.child(mod_name, j);
1193 
1194 			if(mod_duration_match(mod["duration"], duration)) {
1195 				// If removing this mod means reverting the unit's type:
1196 				if(const config::attribute_value* v = mod.get("prev_type")) {
1197 					rebuild_from = &get_unit_type(v->str());
1198 				}
1199 				// Else, if we have not already specified a type to build from:
1200 				else if(rebuild_from == nullptr) {
1201 					rebuild_from = &type();
1202 				}
1203 
1204 				modifications_.remove_child(mod_name, j);
1205 			}
1206 		}
1207 	}
1208 
1209 	if(rebuild_from != nullptr) {
1210 		anim_comp_->clear_haloes();
1211 		advance_to(*rebuild_from);
1212 		hit_points_ = hp;
1213 	}
1214 }
1215 
new_turn()1216 void unit::new_turn()
1217 {
1218 	expire_modifications("turn");
1219 
1220 	end_turn_ = hold_position_;
1221 	movement_ = total_movement();
1222 	attacks_left_ = max_attacks_;
1223 	set_state(STATE_UNCOVERED, false);
1224 }
1225 
end_turn()1226 void unit::end_turn()
1227 {
1228 	expire_modifications("turn end");
1229 
1230 	set_state(STATE_SLOWED,false);
1231 	if((movement_ != total_movement()) && !(get_state(STATE_NOT_MOVED))) {
1232 		resting_ = false;
1233 	}
1234 
1235 	set_state(STATE_NOT_MOVED,false);
1236 	// Clear interrupted move
1237 	set_interrupted_move(map_location());
1238 }
1239 
new_scenario()1240 void unit::new_scenario()
1241 {
1242 	// Set the goto-command to be going to no-where
1243 	goto_ = map_location();
1244 
1245 	// Expire all temporary modifications.
1246 	expire_modifications("");
1247 
1248 	heal_fully();
1249 	set_state(STATE_SLOWED, false);
1250 	set_state(STATE_POISONED, false);
1251 	set_state(STATE_PETRIFIED, false);
1252 	set_state(STATE_GUARDIAN, false);
1253 }
1254 
heal(int amount)1255 void unit::heal(int amount)
1256 {
1257 	int max_hp = max_hitpoints();
1258 	if(hit_points_ < max_hp) {
1259 		hit_points_ += amount;
1260 
1261 		if(hit_points_ > max_hp) {
1262 			hit_points_ = max_hp;
1263 		}
1264 	}
1265 
1266 	if(hit_points_<1) {
1267 		hit_points_ = 1;
1268 	}
1269 }
1270 
get_states() const1271 const std::set<std::string> unit::get_states() const
1272 {
1273 	std::set<std::string> all_states = states_;
1274 	for(const auto& state : known_boolean_state_names_) {
1275 		if(get_state(state.second)) {
1276 			all_states.insert(state.first);
1277 		}
1278 	}
1279 
1280 	// Backwards compatibility for not_living. Don't remove before 1.12
1281 	if(all_states.count("undrainable") && all_states.count("unpoisonable") && all_states.count("unplagueable")) {
1282 		all_states.insert("not_living");
1283 	}
1284 
1285 	return all_states;
1286 }
1287 
get_state(const std::string & state) const1288 bool unit::get_state(const std::string& state) const
1289 {
1290 	state_t known_boolean_state_id = get_known_boolean_state_id(state);
1291 	if(known_boolean_state_id!=STATE_UNKNOWN){
1292 		return get_state(known_boolean_state_id);
1293 	}
1294 
1295 	// Backwards compatibility for not_living. Don't remove before 1.12
1296 	if(state == "not_living") {
1297 		return
1298 			get_state("undrainable")  &&
1299 			get_state("unpoisonable") &&
1300 			get_state("unplagueable");
1301 	}
1302 
1303 	return states_.find(state) != states_.end();
1304 }
1305 
set_state(state_t state,bool value)1306 void unit::set_state(state_t state, bool value)
1307 {
1308 	known_boolean_states_[state] = value;
1309 }
1310 
get_state(state_t state) const1311 bool unit::get_state(state_t state) const
1312 {
1313 	return known_boolean_states_[state];
1314 }
1315 
get_known_boolean_state_id(const std::string & state)1316 unit::state_t unit::get_known_boolean_state_id(const std::string& state)
1317 {
1318 	auto i = known_boolean_state_names_.find(state);
1319 	if(i != known_boolean_state_names_.end()) {
1320 		return i->second;
1321 	}
1322 
1323 	return STATE_UNKNOWN;
1324 }
1325 
1326 std::map<std::string, unit::state_t> unit::known_boolean_state_names_ {
1327 	{"slowed",     STATE_SLOWED},
1328 	{"poisoned",   STATE_POISONED},
1329 	{"petrified",  STATE_PETRIFIED},
1330 	{"uncovered",  STATE_UNCOVERED},
1331 	{"not_moved",  STATE_NOT_MOVED},
1332 	{"unhealable", STATE_UNHEALABLE},
1333 	{"guardian",   STATE_GUARDIAN},
1334 };
1335 
set_state(const std::string & state,bool value)1336 void unit::set_state(const std::string& state, bool value)
1337 {
1338 	appearance_changed_ = true;
1339 	state_t known_boolean_state_id = get_known_boolean_state_id(state);
1340 	if(known_boolean_state_id != STATE_UNKNOWN) {
1341 		set_state(known_boolean_state_id, value);
1342 		return;
1343 	}
1344 
1345 	// Backwards compatibility for not_living. Don't remove before 1.12
1346 	if(state == "not_living") {
1347 		set_state("undrainable", value);
1348 		set_state("unpoisonable", value);
1349 		set_state("unplagueable", value);
1350 	}
1351 
1352 	if(value) {
1353 		states_.insert(state);
1354 	} else {
1355 		states_.erase(state);
1356 	}
1357 }
1358 
has_ability_by_id(const std::string & ability) const1359 bool unit::has_ability_by_id(const std::string& ability) const
1360 {
1361 	for(const config::any_child &ab : this->abilities_.all_children_range()) {
1362 		if(ab.cfg["id"] == ability) {
1363 			return true;
1364 		}
1365 	}
1366 
1367 	return false;
1368 }
1369 
remove_ability_by_id(const std::string & ability)1370 void unit::remove_ability_by_id(const std::string& ability)
1371 {
1372 	config::all_children_iterator i = this->abilities_.ordered_begin();
1373 	while (i != this->abilities_.ordered_end()) {
1374 		if(i->cfg["id"] == ability) {
1375 			i = this->abilities_.erase(i);
1376 		} else {
1377 			++i;
1378 		}
1379 	}
1380 }
1381 
write(config & cfg) const1382 void unit::write(config& cfg) const
1383 {
1384 	config back;
1385 	auto write_subtag = [&](const std::string& key, const config& child)
1386 	{
1387 		cfg.clear_children(key);
1388 
1389 		if(!child.empty()) {
1390 			cfg.add_child(key, child);
1391 		} else {
1392 			back.add_child(key, child);
1393 		}
1394 	};
1395 
1396 	movement_type_.write(cfg);
1397 	cfg["small_profile"] = small_profile_;
1398 	cfg["profile"] = profile_;
1399 
1400 	if(description_ != type().unit_description()) {
1401 		cfg["description"] = description_;
1402 	}
1403 
1404 	if(halo_.get()) {
1405 		cfg["halo"] = *halo_;
1406 	}
1407 
1408 	if(ellipse_.get()) {
1409 		cfg["ellipse"] = *ellipse_;
1410 	}
1411 
1412 	if(usage_.get()) {
1413 		cfg["usage"] = *usage_;
1414 	}
1415 
1416 	write_upkeep(cfg["upkeep"]);
1417 
1418 	cfg["hitpoints"] = hit_points_;
1419 	cfg["max_hitpoints"] = max_hit_points_;
1420 
1421 	cfg["image_icon"] = type().icon();
1422 	cfg["image"] = type().image();
1423 	cfg["random_traits"] = random_traits_;
1424 	cfg["generate_name"] = generate_name_;
1425 	cfg["experience"] = experience_;
1426 	cfg["max_experience"] = max_experience_;
1427 	cfg["recall_cost"] = recall_cost_;
1428 
1429 	cfg["side"] = side_;
1430 
1431 	cfg["type"] = type_id();
1432 
1433 	if(type_id() != type().base_id()) {
1434 		cfg["parent_type"] = type().base_id();
1435 	}
1436 
1437 	// Support for unit formulas in [ai] and unit-specific variables in [ai] [vars]
1438 	formula_man_->write(cfg);
1439 
1440 	cfg["gender"] = gender_string(gender_);
1441 	cfg["variation"] = variation_;
1442 	cfg["role"] = role_;
1443 
1444 	config status_flags;
1445 	for(const std::string& state : get_states()) {
1446 		status_flags[state] = true;
1447 	}
1448 
1449 	write_subtag("variables", variables_);
1450 	write_subtag("filter_recall", filter_recall_);
1451 	write_subtag("status", status_flags);
1452 
1453 	cfg.clear_children("events");
1454 	cfg.append(events_);
1455 
1456 	cfg["overlays"] = utils::join(overlays_);
1457 
1458 	cfg["name"] = name_;
1459 	cfg["id"] = id_;
1460 	cfg["underlying_id"] = underlying_id_.value;
1461 
1462 	if(can_recruit()) {
1463 		cfg["canrecruit"] = true;
1464 	}
1465 
1466 	cfg["extra_recruit"] = utils::join(recruit_list_);
1467 
1468 	cfg["facing"] = map_location::write_direction(facing_);
1469 
1470 	cfg["goto_x"] = goto_.wml_x();
1471 	cfg["goto_y"] = goto_.wml_y();
1472 
1473 	cfg["moves"] = movement_;
1474 	cfg["max_moves"] = max_movement_;
1475 	cfg["vision"] = vision_;
1476 	cfg["jamming"] = jamming_;
1477 
1478 	cfg["resting"] = resting_;
1479 
1480 	cfg["advances_to"] = utils::join(advances_to_);
1481 
1482 	cfg["race"] = race_->id();
1483 	cfg["language_name"] = type_name_;
1484 	cfg["undead_variation"] = undead_variation_;
1485 	cfg["level"] = level_;
1486 	cfg["alignment"] = alignment_.to_string();
1487 	cfg["flag_rgb"] = flag_rgb_;
1488 	cfg["unrenamable"] = unrenamable_;
1489 
1490 	cfg["attacks_left"] = attacks_left_;
1491 	cfg["max_attacks"] = max_attacks_;
1492 	cfg["zoc"] = emit_zoc_;
1493 
1494 	cfg["hidden"] = hidden_;
1495 
1496 	cfg.clear_children("attack");
1497 	for(attack_ptr i : attacks_) {
1498 		i->write(cfg.add_child("attack"));
1499 	}
1500 
1501 	cfg["cost"] = unit_value_;
1502 
1503 	write_subtag("modifications", modifications_);
1504 	write_subtag("abilities", abilities_);
1505 
1506 	cfg.clear_children("advancement");
1507 	for(const config& advancement : this->advancements_) {
1508 		if(!advancement.empty()) {
1509 			cfg.add_child("advancement", advancement);
1510 		}
1511 	}
1512 	cfg.append(back);
1513 }
1514 
set_facing(map_location::DIRECTION dir) const1515 void unit::set_facing(map_location::DIRECTION dir) const
1516 {
1517 	if(dir != map_location::NDIRECTIONS && dir != facing_) {
1518 		appearance_changed_ = true;
1519 		facing_ = dir;
1520 	}
1521 	// Else look at yourself (not available so continue to face the same direction)
1522 }
1523 
upkeep() const1524 int unit::upkeep() const
1525 {
1526 	// Leaders do not incur upkeep.
1527 	if(can_recruit()) {
1528 		return 0;
1529 	}
1530 
1531 	return boost::apply_visitor(upkeep_value_visitor(*this), upkeep_);
1532 }
1533 
loyal() const1534 bool unit::loyal() const
1535 {
1536 	return boost::get<upkeep_loyal>(&upkeep_) != nullptr;
1537 }
1538 
defense_modifier(const t_translation::terrain_code & terrain) const1539 int unit::defense_modifier(const t_translation::terrain_code & terrain) const
1540 {
1541 	int def = movement_type_.defense_modifier(terrain);
1542 #if 0
1543 	// A [defense] ability is too costly and doesn't take into account target locations.
1544 	// Left as a comment in case someone ever wonders why it isn't a good idea.
1545 	unit_ability_list defense_abilities = get_abilities("defense");
1546 	if(!defense_abilities.empty()) {
1547 		unit_abilities::effect defense_effect(defense_abilities, def, false);
1548 		def = defense_effect.get_composite_value();
1549 	}
1550 #endif
1551 	return def;
1552 }
1553 
resistance_filter_matches(const config & cfg,bool attacker,const std::string & damage_name,int res) const1554 bool unit::resistance_filter_matches(const config& cfg, bool attacker, const std::string& damage_name, int res) const
1555 {
1556 	if(!(cfg["active_on"].empty() || (attacker && cfg["active_on"] == "offense") || (!attacker && cfg["active_on"] == "defense"))) {
1557 		return false;
1558 	}
1559 
1560 	const std::string& apply_to = cfg["apply_to"];
1561 	if(!apply_to.empty()) {
1562 		if(damage_name != apply_to) {
1563 			if(apply_to.find(',') != std::string::npos  &&
1564 			     apply_to.find(damage_name) != std::string::npos) {
1565 				const std::vector<std::string>& vals = utils::split(apply_to);
1566 				if(std::find(vals.begin(),vals.end(),damage_name) == vals.end()) {
1567 					return false;
1568 				}
1569 			} else {
1570 				return false;
1571 			}
1572 		}
1573 	}
1574 
1575 	if(!unit_abilities::filter_base_matches(cfg, res)) {
1576 		return false;
1577 	}
1578 
1579 	return true;
1580 }
1581 
resistance_against(const std::string & damage_name,bool attacker,const map_location & loc) const1582 int unit::resistance_against(const std::string& damage_name,bool attacker,const map_location& loc) const
1583 {
1584 	int res = movement_type_.resistance_against(damage_name);
1585 
1586 	unit_ability_list resistance_abilities = get_abilities("resistance",loc);
1587 	for(unit_ability_list::iterator i = resistance_abilities.begin(); i != resistance_abilities.end();) {
1588 		if(!resistance_filter_matches(*i->first, attacker, damage_name, 100-res)) {
1589 			i = resistance_abilities.erase(i);
1590 		} else {
1591 			++i;
1592 		}
1593 	}
1594 
1595 	if(!resistance_abilities.empty()) {
1596 		unit_abilities::effect resist_effect(resistance_abilities, 100-res, false);
1597 
1598 		res = 100 - std::min<int>(
1599 			resist_effect.get_composite_value(),
1600 			resistance_abilities.highest("max_value").first
1601 		);
1602 	}
1603 
1604 	return res;
1605 }
1606 
advancement_icons() const1607 std::map<std::string, std::string> unit::advancement_icons() const
1608 {
1609 	std::map<std::string,std::string> temp;
1610 	if(!can_advance()) {
1611 		return temp;
1612 	}
1613 
1614 	if(!advances_to_.empty()) {
1615 		std::ostringstream tooltip;
1616 		const std::string& image = game_config::images::level;
1617 
1618 		for(const std::string& s : advances_to()) {
1619 			if(!s.empty()) {
1620 				tooltip << s << std::endl;
1621 			}
1622 		}
1623 
1624 		temp[image] = tooltip.str();
1625 	}
1626 
1627 	for(const config& adv : get_modification_advances()) {
1628 		const std::string& image = adv["image"];
1629 		if(image.empty()) {
1630 			continue;
1631 		}
1632 
1633 		std::ostringstream tooltip;
1634 		tooltip << temp[image];
1635 
1636 		const std::string& tt = adv["description"];
1637 		if(!tt.empty()) {
1638 			tooltip << tt << std::endl;
1639 		}
1640 
1641 		temp[image] = tooltip.str();
1642 	}
1643 
1644 	return(temp);
1645 }
1646 
amla_icons() const1647 std::vector<std::pair<std::string, std::string>> unit::amla_icons() const
1648 {
1649 	std::vector<std::pair<std::string, std::string>> temp;
1650 	std::pair<std::string, std::string> icon; // <image,tooltip>
1651 
1652 	for(const config& adv : get_modification_advances()) {
1653 		icon.first = adv["icon"].str();
1654 		icon.second = adv["description"].str();
1655 
1656 		for(unsigned j = 0, j_count = modification_count("advancement", adv["id"]); j < j_count; ++j) {
1657 			temp.push_back(icon);
1658 		}
1659 	}
1660 
1661 	return(temp);
1662 }
1663 
get_modification_advances() const1664 std::vector<config> unit::get_modification_advances() const
1665 {
1666 	std::vector<config> res;
1667 	for(const config& adv : modification_advancements()) {
1668 		if(adv["strict_amla"].to_bool() && !advances_to_.empty()) {
1669 			continue;
1670 		}
1671 
1672 		if(modification_count("advancement", adv["id"]) >= static_cast<unsigned>(adv["max_times"].to_int(1))) {
1673 			continue;
1674 		}
1675 
1676 		std::vector<std::string> temp_require = utils::split(adv["require_amla"]);
1677 		std::vector<std::string> temp_exclude = utils::split(adv["exclude_amla"]);
1678 
1679 		if(temp_require.empty() && temp_exclude.empty()) {
1680 			res.push_back(adv);
1681 			continue;
1682 		}
1683 
1684 		std::sort(temp_require.begin(), temp_require.end());
1685 		std::sort(temp_exclude.begin(), temp_exclude.end());
1686 
1687 		std::vector<std::string> uniq_require, uniq_exclude;
1688 
1689 		std::unique_copy(temp_require.begin(), temp_require.end(), std::back_inserter(uniq_require));
1690 		std::unique_copy(temp_exclude.begin(), temp_exclude.end(), std::back_inserter(uniq_exclude));
1691 
1692 		bool exclusion_found = false;
1693 		for(const std::string& s : uniq_exclude) {
1694 			int max_num = std::count(temp_exclude.begin(), temp_exclude.end(), s);
1695 			int mod_num = modification_count("advancement", s);
1696 			if(mod_num >= max_num) {
1697 				exclusion_found = true;
1698 				break;
1699 			}
1700 		}
1701 
1702 		if(exclusion_found) {
1703 			continue;
1704 		}
1705 
1706 		bool requirements_done = true;
1707 		for(const std::string& s : uniq_require) {
1708 			int required_num = std::count(temp_require.begin(), temp_require.end(), s);
1709 			int mod_num = modification_count("advancement", s);
1710 			if(required_num > mod_num) {
1711 				requirements_done = false;
1712 				break;
1713 			}
1714 		}
1715 
1716 		if(requirements_done) {
1717 			res.push_back(adv);
1718 		}
1719 	}
1720 
1721 	return res;
1722 }
1723 
set_advancements(std::vector<config> advancements)1724 void unit::set_advancements(std::vector<config> advancements)
1725 {
1726 	this->advancements_.clear();
1727 	for(config& advancement : advancements) {
1728 		this->advancements_.push_back(new config());
1729 		this->advancements_.back().swap(advancement);
1730 	}
1731 }
1732 
modification_count(const std::string & mod_type,const std::string & id) const1733 size_t unit::modification_count(const std::string& mod_type, const std::string& id) const
1734 {
1735 	size_t res = 0;
1736 	for(const config& item : modifications_.child_range(mod_type)) {
1737 		if(item["id"] == id) {
1738 			++res;
1739 		}
1740 	}
1741 
1742 	// For backwards compatibility, if asked for "advancement", also count "advance"
1743 	if(mod_type == "advancement") {
1744 		res += modification_count("advance", id);
1745 	}
1746 
1747 	return res;
1748 }
1749 
1750 const std::set<std::string> unit::builtin_effects {
1751 	"alignment", "attack", "defense", "ellipse", "experience", "fearless",
1752 	"halo", "healthy", "hitpoints", "image_mod", "jamming", "jamming_costs",
1753 	"loyal", "max_attacks", "max_experience", "movement", "movement_costs",
1754 	"new_ability", "new_advancement", "new_animation", "new_attack", "overlay", "profile",
1755 	"recall_cost", "remove_ability", "remove_advancement", "remove_attacks", "resistance",
1756 	"status", "type", "variation", "vision", "vision_costs", "zoc"
1757 };
1758 
describe_builtin_effect(std::string apply_to,const config & effect)1759 std::string unit::describe_builtin_effect(std::string apply_to, const config& effect)
1760 {
1761 	if(apply_to == "attack") {
1762 		std::vector<t_string> attack_names;
1763 
1764 		std::string desc;
1765 		for(attack_ptr a : attacks_) {
1766 			bool affected = a->describe_modification(effect, &desc);
1767 			if(affected && !desc.empty()) {
1768 				attack_names.emplace_back(a->name(), "wesnoth-units");
1769 			}
1770 		}
1771 		if(!attack_names.empty()) {
1772 			utils::string_map symbols;
1773 			symbols["attack_list"] = utils::format_conjunct_list("", attack_names);
1774 			symbols["effect_description"] = desc;
1775 			return VGETTEXT("$attack_list|: $effect_description", symbols);
1776 		}
1777 	} else if(apply_to == "hitpoints") {
1778 		const std::string& increase_total = effect["increase_total"];
1779 		if(!increase_total.empty()) {
1780 			return VGETTEXT(
1781 				"<span color=\"$color\">$number_or_percent</span> HP",
1782 				{{"number_or_percent", utils::print_modifier(increase_total)}, {"color", increase_total[0] == '-' ? "red" : "green"}});
1783 		}
1784 	} else {
1785 		const std::string& increase = effect["increase"];
1786 		if(increase.empty()) {
1787 			return "";
1788 		}
1789 		if(apply_to == "movement") {
1790 			return VNGETTEXT(
1791 				"<span color=\"$color\">$number_or_percent</span> move",
1792 				"<span color=\"$color\">$number_or_percent</span> moves",
1793 				std::stoi(increase),
1794 				{{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "red" : "green"}});
1795 		} else if(apply_to == "vision") {
1796 			return VGETTEXT(
1797 				"<span color=\"$color\">$number_or_percent</span> vision",
1798 				{{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "red" : "green"}});
1799 		} else if(apply_to == "jamming") {
1800 			return VGETTEXT(
1801 				"<span color=\"$color\">$number_or_percent</span> jamming",
1802 				{{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "red" : "green"}});
1803 		} else if(apply_to == "max_experience") {
1804 			// Unlike others, decreasing experience is a *GOOD* thing
1805 			return VGETTEXT(
1806 				"<span color=\"$color\">$number_or_percent</span> XP to advance",
1807 				{{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "green" : "red"}});
1808 		} else if(apply_to == "max_attacks") {
1809 			return VNGETTEXT(
1810 					"<span color=\"$color\">$number_or_percent</span> attack per turn",
1811 					"<span color=\"$color\">$number_or_percent</span> attacks per turn",
1812 					std::stoi(increase),
1813 					{{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "red" : "green"}});
1814 		} else if(apply_to == "recall_cost") {
1815 			// Unlike others, decreasing recall cost is a *GOOD* thing
1816 			return VGETTEXT(
1817 				"<span color=\"$color\">$number_or_percent</span> cost to recall",
1818 				{{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "green" : "red"}});
1819 		}
1820 	}
1821 	return "";
1822 }
1823 
apply_builtin_effect(std::string apply_to,const config & effect)1824 void unit::apply_builtin_effect(std::string apply_to, const config& effect)
1825 {
1826 	appearance_changed_ = true;
1827 	if(apply_to == "fearless") {
1828 		is_fearless_ = effect["set"].to_bool(true);
1829 	} else if(apply_to == "healthy") {
1830 		is_healthy_ = effect["set"].to_bool(true);
1831 	} else if(apply_to == "profile") {
1832 		if(const config::attribute_value* v = effect.get("portrait")) {
1833 			std::string portrait = (*v).str();
1834 			adjust_profile(portrait);
1835 			profile_ = portrait;
1836 		}
1837 
1838 		if(const config::attribute_value* v = effect.get("small_portrait")) {
1839 			small_profile_ = (*v).str();
1840 		}
1841 
1842 		if(const config::attribute_value* v = effect.get("description")) {
1843 			description_ = *v;
1844 		}
1845 	} else if(apply_to == "new_attack") {
1846 		attacks_.emplace_back(new attack_type(effect));
1847 	} else if(apply_to == "remove_attacks") {
1848 		auto iter = std::remove_if(attacks_.begin(), attacks_.end(), [&effect](attack_ptr a) {
1849 			return a->matches_filter(effect);
1850 		});
1851 
1852 		attacks_.erase(iter, attacks_.end());
1853 	} else if(apply_to == "attack") {
1854 		for(attack_ptr a : attacks_) {
1855 			a->apply_modification(effect);
1856 		}
1857 	} else if(apply_to == "hitpoints") {
1858 		LOG_UT << "applying hitpoint mod..." << hit_points_ << "/" << max_hit_points_ << std::endl;
1859 		const std::string& increase_hp = effect["increase"];
1860 		const std::string& increase_total = effect["increase_total"];
1861 		const std::string& set_hp = effect["set"];
1862 		const std::string& set_total = effect["set_total"];
1863 
1864 		// If the hitpoints are allowed to end up greater than max hitpoints
1865 		const bool violate_max = effect["violate_maximum"].to_bool();
1866 
1867 		if(!set_hp.empty()) {
1868 			if(set_hp.back() == '%') {
1869 				hit_points_ = lexical_cast_default<int>(set_hp)*max_hit_points_/100;
1870 			} else {
1871 				hit_points_ = lexical_cast_default<int>(set_hp);
1872 			}
1873 		}
1874 
1875 		if(!set_total.empty()) {
1876 			if(set_total.back() == '%') {
1877 				max_hit_points_ = lexical_cast_default<int>(set_total)*max_hit_points_/100;
1878 			} else {
1879 				max_hit_points_ = lexical_cast_default<int>(set_total);
1880 			}
1881 		}
1882 
1883 		if(!increase_total.empty()) {
1884 			// A percentage on the end means increase by that many percent
1885 			max_hit_points_ = utils::apply_modifier(max_hit_points_, increase_total);
1886 		}
1887 
1888 		if(max_hit_points_ < 1)
1889 			max_hit_points_ = 1;
1890 
1891 		if(effect["heal_full"].to_bool()) {
1892 			heal_fully();
1893 		}
1894 
1895 		if(!increase_hp.empty()) {
1896 			hit_points_ = utils::apply_modifier(hit_points_, increase_hp);
1897 		}
1898 
1899 		LOG_UT << "modded to " << hit_points_ << "/" << max_hit_points_ << std::endl;
1900 		if(hit_points_ > max_hit_points_ && !violate_max) {
1901 			LOG_UT << "resetting hp to max" << std::endl;
1902 			hit_points_ = max_hit_points_;
1903 		}
1904 
1905 		if(hit_points_ < 1) {
1906 			hit_points_ = 1;
1907 		}
1908 	} else if(apply_to == "movement") {
1909 		const std::string& increase = effect["increase"];
1910 
1911 		if(!increase.empty()) {
1912 			max_movement_ = utils::apply_modifier(max_movement_, increase, 1);
1913 		}
1914 
1915 		max_movement_ = effect["set"].to_int(max_movement_);
1916 
1917 		if(movement_ > max_movement_) {
1918 			movement_ = max_movement_;
1919 		}
1920 	} else if(apply_to == "vision") {
1921 		const std::string& increase = effect["increase"];
1922 
1923 		if(!increase.empty()) {
1924 			const int current_vision = vision_ < 0 ? max_movement_ : vision_;
1925 			vision_ = utils::apply_modifier(current_vision, increase, 1);
1926 		}
1927 
1928 		vision_ = effect["set"].to_int(vision_);
1929 	} else if(apply_to == "jamming") {
1930 		const std::string& increase = effect["increase"];
1931 
1932 		if(!increase.empty()) {
1933 			jamming_ = utils::apply_modifier(jamming_, increase, 1);
1934 		}
1935 
1936 		jamming_ = effect["set"].to_int(jamming_);
1937 	} else if(apply_to == "experience") {
1938 		const std::string& increase = effect["increase"];
1939 		const std::string& set = effect["set"];
1940 
1941 		if(!set.empty()) {
1942 			if(set.back() == '%') {
1943 				experience_ = lexical_cast_default<int>(set)*max_experience_/100;
1944 			} else {
1945 				experience_ = lexical_cast_default<int>(set);
1946 			}
1947 		}
1948 
1949 		if(increase.empty() == false) {
1950 			experience_ = utils::apply_modifier(experience_, increase, 1);
1951 		}
1952 	} else if(apply_to == "max_experience") {
1953 		const std::string& increase = effect["increase"];
1954 		const std::string& set = effect["set"];
1955 
1956 		if(set.empty() == false) {
1957 			if(set.back() == '%') {
1958 				max_experience_ = lexical_cast_default<int>(set)*max_experience_/100;
1959 			} else {
1960 				max_experience_ = lexical_cast_default<int>(set);
1961 			}
1962 		}
1963 
1964 		if(increase.empty() == false) {
1965 			max_experience_ = utils::apply_modifier(max_experience_, increase, 1);
1966 		}
1967 	} else if(apply_to == upkeep_loyal::type()) {
1968 		upkeep_ = upkeep_loyal();
1969 	} else if(apply_to == "status") {
1970 		const std::string& add = effect["add"];
1971 		const std::string& remove = effect["remove"];
1972 
1973 		for(const std::string& to_add : utils::split(add))
1974 		{
1975 			set_state(to_add, true);
1976 		}
1977 
1978 		for(const std::string& to_remove : utils::split(remove))
1979 		{
1980 			set_state(to_remove, false);
1981 		}
1982 	} else if(std::find(movetype::effects.cbegin(), movetype::effects.cend(), apply_to) != movetype::effects.cend()) {
1983 		// "movement_costs", "vision_costs", "jamming_costs", "defense", "resistance"
1984 		if(const config& ap = effect.child(apply_to)) {
1985 			movement_type_.merge(ap, apply_to, effect["replace"].to_bool());
1986 		}
1987 	} else if(apply_to == "zoc") {
1988 		if(const config::attribute_value* v = effect.get("value")) {
1989 			emit_zoc_ = v->to_bool();
1990 		}
1991 	} else if(apply_to == "new_ability") {
1992 		if(const config& ab_effect = effect.child("abilities")) {
1993 			config to_append;
1994 			for(const config::any_child &ab : ab_effect.all_children_range()) {
1995 				if(!has_ability_by_id(ab.cfg["id"])) {
1996 					to_append.add_child(ab.key, ab.cfg);
1997 				}
1998 			}
1999 			this->abilities_.append(to_append);
2000 		}
2001 	} else if(apply_to == "remove_ability") {
2002 		if(const config& ab_effect = effect.child("abilities")) {
2003 			for(const config::any_child &ab : ab_effect.all_children_range()) {
2004 				remove_ability_by_id(ab.cfg["id"]);
2005 			}
2006 		}
2007 	} else if(apply_to == "image_mod") {
2008 		LOG_UT << "applying image_mod" << std::endl;
2009 		std::string mod = effect["replace"];
2010 		if(!mod.empty()){
2011 			image_mods_ = mod;
2012 		}
2013 		LOG_UT << "applying image_mod" << std::endl;
2014 		mod = effect["add"].str();
2015 		if(!mod.empty()){
2016 			if(!image_mods_.empty()) {
2017 				image_mods_ += '~';
2018 			}
2019 
2020 			image_mods_ += mod;
2021 		}
2022 
2023 		game_config::add_color_info(effect);
2024 		LOG_UT << "applying image_mod" << std::endl;
2025 	} else if(apply_to == "new_animation") {
2026 		anim_comp_->apply_new_animation_effect(effect);
2027 	} else if(apply_to == "ellipse") {
2028 		set_image_ellipse(effect["ellipse"]);
2029 	} else if(apply_to == "halo") {
2030 		set_image_halo(effect["halo"]);
2031 	} else if(apply_to == "overlay") {
2032 		const std::string& add = effect["add"];
2033 		const std::string& replace = effect["replace"];
2034 
2035 		if(!add.empty()) {
2036 			std::vector<std::string> temp_overlays = utils::parenthetical_split(add, ',');
2037 			std::vector<std::string>::iterator it;
2038 			for(it=temp_overlays.begin();it<temp_overlays.end();++it) {
2039 				overlays_.push_back(*it);
2040 			}
2041 		}
2042 		else if(!replace.empty()) {
2043 			overlays_ = utils::parenthetical_split(replace, ',');
2044 		}
2045 	} else if(apply_to == "new_advancement") {
2046 		const std::string& types = effect["types"];
2047 		const bool replace = effect["replace"].to_bool(false);
2048 
2049 		if(!types.empty()) {
2050 			if(replace) {
2051 				advances_to_ = utils::parenthetical_split(types, ',');
2052 			} else {
2053 				std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2054 				std::copy(temp_advances.begin(), temp_advances.end(), std::back_inserter(advances_to_));
2055 			}
2056 		}
2057 
2058 		if(effect.has_child("advancement")) {
2059 			if(replace) {
2060 				advancements_.clear();
2061 			}
2062 
2063 			config temp = effect;
2064 			boost::copy(effect.child_range("advancement"), boost::make_function_output_iterator(ptr_vector_pushback(advancements_)));
2065 		}
2066 	} else if(apply_to == "remove_advancement") {
2067 		const std::string& types = effect["types"];
2068 		const std::string& amlas = effect["amlas"];
2069 
2070 		std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2071 		std::vector<std::string>::iterator iter;
2072 		for(const std::string& unit : temp_advances) {
2073 			iter = std::find(advances_to_.begin(), advances_to_.end(), unit);
2074 			if(iter != advances_to_.end()) {
2075 				advances_to_.erase(iter);
2076 			}
2077 		}
2078 
2079 		temp_advances = utils::parenthetical_split(amlas, ',');
2080 
2081 		for(int i = advancements_.size() - 1; i >= 0; i--) {
2082 			if(std::find(temp_advances.begin(), temp_advances.end(), advancements_[i]["id"]) != temp_advances.end()) {
2083 				advancements_.erase(advancements_.begin() + i);
2084 			}
2085 		}
2086 	} else if(apply_to == "alignment") {
2087 		unit_type::ALIGNMENT new_align;
2088 		if(new_align.parse(effect["set"])) {
2089 			alignment_ = new_align;
2090 		}
2091 	} else if(apply_to == "max_attacks") {
2092 		const std::string& increase = effect["increase"];
2093 
2094 		if(!increase.empty()) {
2095 			max_attacks_ = utils::apply_modifier(max_attacks_, increase, 1);
2096 		}
2097 	} else if(apply_to == "recall_cost") {
2098 		const std::string& increase = effect["increase"];
2099 		const std::string& set = effect["set"];
2100 		const int recall_cost = recall_cost_ < 0 ? resources::gameboard->teams().at(side_).recall_cost() : recall_cost_;
2101 
2102 		if(!set.empty()) {
2103 			if(set.back() == '%') {
2104 				recall_cost_ = lexical_cast_default<int>(set)*recall_cost/100;
2105 			} else {
2106 				recall_cost_ = lexical_cast_default<int>(set);
2107 			}
2108 		}
2109 
2110 		if(!increase.empty()) {
2111 			recall_cost_ = utils::apply_modifier(recall_cost, increase, 1);
2112 		}
2113 	} else if(effect["apply_to"] == "variation") {
2114 		variation_ = effect["name"].str();
2115 		const unit_type*  base_type = unit_types.find(type().base_id());
2116 		assert(base_type != nullptr);
2117 		advance_to(*base_type);
2118 	} else if(effect["apply_to"] == "type") {
2119 		std::string prev_type = effect["prev_type"];
2120 		if(prev_type.empty()) {
2121 			prev_type = type().base_id();
2122 		}
2123 		const std::string& new_type_id = effect["name"];
2124 		const unit_type* new_type = unit_types.find(new_type_id);
2125 		if(new_type) {
2126 			const bool heal_full = effect["heal_full"].to_bool(false);
2127 			advance_to(*new_type);
2128 			preferences::encountered_units().insert(new_type_id);
2129 			if(heal_full) {
2130 				heal_fully();
2131 			}
2132 		} else {
2133 			WRN_UT << "unknown type= in [effect]apply_to=type, ignoring" << std::endl;
2134 		}
2135 	}
2136 }
2137 
add_modification(const std::string & mod_type,const config & mod,bool no_add)2138 void unit::add_modification(const std::string& mod_type, const config& mod, bool no_add)
2139 {
2140 	bool generate_description = mod["generate_description"].to_bool(true);
2141 
2142 	if(no_add == false) {
2143 		modifications_.add_child(mod_type, mod);
2144 	}
2145 
2146 	bool set_poisoned = false; // Tracks if the poisoned state was set after the type or variation was changed.
2147 	config type_effect, variation_effect;
2148 	std::vector<t_string> effects_description;
2149 	for(const config& effect : mod.child_range("effect")) {
2150 		// Apply SUF.
2151 		if(const config& afilter = effect.child("filter")) {
2152 			// @FIXME: during gamestate construction resources::filter_con is not available
2153 			if(resources::filter_con && !unit_filter(vconfig(afilter)).matches(*this, loc_)) {
2154 				continue;
2155 			}
2156 		}
2157 		const std::string& apply_to = effect["apply_to"];
2158 		int times = effect["times"].to_int(1);
2159 		t_string description;
2160 
2161 		if(effect["times"] == "per level") {
2162 			times = level_;
2163 		}
2164 
2165 		if(times) {
2166 			while (times > 0) {
2167 				times --;
2168 
2169 				bool was_poisoned = get_state(STATE_POISONED);
2170 				// Apply unit type/variation changes last to avoid double applying effects on advance.
2171 				if(apply_to == "type") {
2172 					set_poisoned = false;
2173 					type_effect = effect;
2174 					continue;
2175 				}
2176 				if(apply_to == "variation") {
2177 					set_poisoned = false;
2178 					variation_effect = effect;
2179 					continue;
2180 				}
2181 
2182 				std::string description_component;
2183 				if(resources::lua_kernel) {
2184 					description_component = resources::lua_kernel->apply_effect(apply_to, *this, effect, true);
2185 				} else if(builtin_effects.count(apply_to)) {
2186 					// Normally, the built-in effects are dispatched through Lua so that a user
2187 					// can override them if desired. However, since they're built-in, we can still
2188 					// apply them if the lua kernel is unavailable.
2189 					apply_builtin_effect(apply_to, effect);
2190 					description_component = describe_builtin_effect(apply_to, effect);
2191 				}
2192 				if(!times) {
2193 					description += description_component;
2194 				}
2195 				if(!was_poisoned && get_state(STATE_POISONED)) {
2196 					set_poisoned = true;
2197 				} else if(was_poisoned && !get_state(STATE_POISONED)) {
2198 					set_poisoned = false;
2199 				}
2200 			} // end while
2201 		} else { // for times = per level & level = 0 we still need to rebuild the descriptions
2202 			if(resources::lua_kernel) {
2203 				description += resources::lua_kernel->apply_effect(apply_to, *this, effect, false);
2204 			} else if(builtin_effects.count(apply_to)) {
2205 				description += describe_builtin_effect(apply_to, effect);
2206 			}
2207 		}
2208 
2209 		if(effect["times"] == "per level" && !times) {
2210 			description = VGETTEXT("$effect_description per level", {{"effect_description", description}});
2211 		}
2212 
2213 		if(!description.empty()) {
2214 			effects_description.push_back(description);
2215 		}
2216 	}
2217 	// Apply variations -- only apply if we are adding this for the first time.
2218 	if((!type_effect.empty() || !variation_effect.empty()) && no_add == false) {
2219 		if(!type_effect.empty()) {
2220 			std::string description;
2221 			if(resources::lua_kernel) {
2222 				description = resources::lua_kernel->apply_effect(type_effect["apply_to"], *this, type_effect, true);
2223 			} else if(builtin_effects.count(type_effect["apply_to"])) {
2224 				apply_builtin_effect(type_effect["apply_to"], type_effect);
2225 				description = describe_builtin_effect(type_effect["apply_to"], type_effect);
2226 			}
2227 			effects_description.push_back(description);
2228 		}
2229 		if(!variation_effect.empty()) {
2230 			std::string description;
2231 			if(resources::lua_kernel) {
2232 				description = resources::lua_kernel->apply_effect(variation_effect["apply_to"], *this, variation_effect, true);
2233 			} else if(builtin_effects.count(variation_effect["apply_to"])) {
2234 				apply_builtin_effect(variation_effect["apply_to"], variation_effect);
2235 				description = describe_builtin_effect(variation_effect["apply_to"], variation_effect);
2236 			}
2237 			effects_description.push_back(description);
2238 		}
2239 		if(set_poisoned)
2240 			// An effect explicitly set the poisoned state, and this
2241 			// should override the unit being immune to poison.
2242 			set_state(STATE_POISONED, true);
2243 	}
2244 
2245 	t_string description;
2246 
2247 	const t_string& mod_description = mod["description"];
2248 	if(!mod_description.empty()) {
2249 		description = mod_description;
2250 	}
2251 
2252 	// Punctuation should be translatable: not all languages use Latin punctuation.
2253 	// (However, there maybe is a better way to do it)
2254 	if(generate_description && !effects_description.empty()) {
2255 		if(!mod_description.empty()) {
2256 			description += "\n";
2257 		}
2258 
2259 		for(const auto& desc_line : effects_description) {
2260 			description += desc_line + "\n";
2261 		}
2262 	}
2263 
2264 	// store trait info
2265 	if(mod_type == "trait") {
2266 		add_trait_description(mod, description);
2267 	}
2268 
2269 	//NOTE: if not a trait, description is currently not used
2270 }
2271 
add_trait_description(const config & trait,const t_string & description)2272 void unit::add_trait_description(const config& trait, const t_string& description)
2273 {
2274 	const std::string& gender_string = gender_ == unit_race::FEMALE ? "female_name" : "male_name";
2275 	const auto& gender_specific_name = trait[gender_string];
2276 
2277 	const t_string name = gender_specific_name.empty() ? trait["name"] : gender_specific_name;
2278 
2279 	if(!name.empty()) {
2280 		trait_names_.push_back(name);
2281 		trait_descriptions_.push_back(description);
2282 	}
2283 }
2284 
absolute_image() const2285 std::string unit::absolute_image() const
2286 {
2287 	return type().icon().empty() ? type().image() : type().icon();
2288 }
2289 
default_anim_image() const2290 std::string unit::default_anim_image() const
2291 {
2292 	return type().image().empty() ? type().icon() : type().image();
2293 }
2294 
apply_modifications()2295 void unit::apply_modifications()
2296 {
2297 	log_scope("apply mods");
2298 
2299 	variables_.clear_children("mods");
2300 
2301 	for(const auto& mod : ModificationTypes) {
2302 		if(mod == "advance" && modifications_.has_child(mod)) {
2303 			deprecated_message("[advance]", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use [advancement] instead.");
2304 		}
2305 
2306 		for(const config& m : modifications_.child_range(mod)) {
2307 			lg::scope_logger inner_scope_logging_object__(lg::general(), "add mod");
2308 			add_modification(mod, m, true);
2309 		}
2310 	}
2311 
2312 	// Apply the experience acceleration last
2313 	int exp_accel = unit_experience_accelerator::get_acceleration();
2314 	max_experience_ = std::max<int>(1, (max_experience_ * exp_accel + 50)/100);
2315 }
2316 
invisible(const map_location & loc,const display_context & dc,bool see_all) const2317 bool unit::invisible(const map_location& loc, const display_context& dc, bool see_all) const
2318 {
2319 	if(loc != get_location()) {
2320 		DBG_UT << "unit::invisible called: id = " << id() << " loc = " << loc << " get_loc = " << get_location() << std::endl;
2321 	}
2322 
2323 	// This is a quick condition to check, and it does not depend on the
2324 	// location (so might as well bypass the location-based cache).
2325 	if(get_state(STATE_UNCOVERED)) {
2326 		return false;
2327 	}
2328 
2329 	// Fetch from cache
2330 	/**
2331 	 * @todo FIXME: We use the cache only when using the default see_all=true
2332 	 * Maybe add a second cache if the see_all=false become more frequent.
2333 	 */
2334 	if(see_all) {
2335 		const auto itor = invisibility_cache_.find(loc);
2336 		if(itor != invisibility_cache_.end()) {
2337 			return itor->second;
2338 		}
2339 	}
2340 
2341 	// Test hidden status
2342 	static const std::string hides("hides");
2343 	bool is_inv = get_ability_bool(hides, loc, dc);
2344 	if(is_inv){
2345 		is_inv = (resources::gameboard ? !resources::gameboard->would_be_discovered(loc, side_,see_all) : true);
2346 	}
2347 
2348 	if(see_all) {
2349 		// Add to caches
2350 		if(invisibility_cache_.empty()) {
2351 			units_with_cache.push_back(this);
2352 		}
2353 
2354 		invisibility_cache_[loc] = is_inv;
2355 	}
2356 
2357 	return is_inv;
2358 }
2359 
is_visible_to_team(const team & team,const display_context & dc,bool const see_all) const2360 bool unit::is_visible_to_team(const team& team,const  display_context& dc, bool const see_all) const
2361 {
2362 	const map_location& loc = get_location();
2363 	return is_visible_to_team(loc, team, dc, see_all);
2364 }
2365 
is_visible_to_team(const map_location & loc,const team & team,const display_context & dc,bool const see_all) const2366 bool unit::is_visible_to_team(const map_location& loc, const team& team, const  display_context& dc, bool const see_all) const
2367 {
2368 	if(!dc.map().on_board(loc)) {
2369 		return false;
2370 	}
2371 
2372 	if(see_all) {
2373 		return true;
2374 	}
2375 
2376 	if(team.is_enemy(side()) && invisible(loc, dc)) {
2377 		return false;
2378 	}
2379 
2380 	// allied planned moves are also visible under fog. (we assume that fake units on the map are always whiteboard markers)
2381 	if(!team.is_enemy(side()) && underlying_id_.is_fake()) {
2382 		return true;
2383 	}
2384 
2385 	// when the whiteboard planned unit map is applied, it uses moved the _real_ unit so
2386 	// underlying_id_.is_fake() will be false and the check above will not apply.
2387 	// TODO: improve this check so that is also works for allied planned units but without
2388 	//       breaking sp campaigns with allies under fog. We probably need an explicit flag
2389 	//       is_planned_ in unit that is set by the whiteboard.
2390 	if(team.side() == side()) {
2391 		return true;
2392 	}
2393 
2394 	if(team.fogged(loc)) {
2395 		return false;
2396 	}
2397 
2398 	return true;
2399 }
2400 
set_underlying_id(n_unit::id_manager & id_manager)2401 void unit::set_underlying_id(n_unit::id_manager& id_manager)
2402 {
2403 	if(underlying_id_.value == 0) {
2404 		if(synced_context::is_synced() || !resources::gamedata || resources::gamedata->phase() == game_data::INITIAL) {
2405 			underlying_id_ = id_manager.next_id();
2406 		} else {
2407 			underlying_id_ = id_manager.next_fake_id();
2408 		}
2409 	}
2410 
2411 	if(id_.empty() /*&& !underlying_id_.is_fake()*/) {
2412 		std::stringstream ss;
2413 		ss << (type_id().empty() ? "Unit" : type_id()) << "-" << underlying_id_.value;
2414 		id_ = ss.str();
2415 	}
2416 }
2417 
mark_clone(bool is_temporary)2418 unit& unit::mark_clone(bool is_temporary)
2419 {
2420 	n_unit::id_manager& ids = resources::gameboard ? resources::gameboard->unit_id_manager() : n_unit::id_manager::global_instance();
2421 	if(is_temporary) {
2422 		underlying_id_ = ids.next_fake_id();
2423 	} else {
2424 		if(synced_context::is_synced() || !resources::gamedata || resources::gamedata->phase() == game_data::INITIAL) {
2425 			underlying_id_ = ids.next_id();
2426 		}
2427 		else {
2428 			underlying_id_ = ids.next_fake_id();
2429 		}
2430 		std::string::size_type pos = id_.find_last_of('-');
2431 		if(pos != std::string::npos && pos+1 < id_.size()
2432 		&& id_.find_first_not_of("0123456789", pos+1) == std::string::npos) {
2433 			// this appears to be a duplicate of a generic unit, so give it a new id
2434 			WRN_UT << "assigning new id to clone of generic unit " << id_ << std::endl;
2435 			id_.clear();
2436 			set_underlying_id(ids);
2437 		}
2438 	}
2439 	return *this;
2440 }
2441 
2442 
unit_movement_resetter(const unit & u,bool operate)2443 unit_movement_resetter::unit_movement_resetter(const unit &u, bool operate)
2444 	: u_(const_cast<unit&>(u))
2445 	, moves_(u.movement_left(true))
2446 {
2447 	if(operate) {
2448 		u_.set_movement(u_.total_movement());
2449 	}
2450 }
2451 
~unit_movement_resetter()2452 unit_movement_resetter::~unit_movement_resetter()
2453 {
2454 	assert(resources::gameboard);
2455 	try {
2456 		if(!resources::gameboard->units().has_unit(&u_)) {
2457 			/*
2458 			* It might be valid that the unit is not in the unit map.
2459 			* It might also mean a no longer valid unit will be assigned to.
2460 			*/
2461 			DBG_UT << "The unit to be removed is not in the unit map." << std::endl;
2462 		}
2463 
2464 		u_.set_movement(moves_);
2465 	} catch(...) {}
2466 }
2467 
TC_image_mods() const2468 std::string unit::TC_image_mods() const
2469 {
2470 	return formatter() << "~RC(" << flag_rgb() << ">" << team::get_side_color_id(side()) << ")";
2471 }
2472 
image_mods() const2473 std::string unit::image_mods() const
2474 {
2475 	if(!image_mods_.empty()) {
2476 		return formatter() << "~" << image_mods_ << TC_image_mods();
2477 	}
2478 
2479 	return TC_image_mods();
2480 }
2481 
2482 // Called by the Lua API after resetting an attack pointer.
remove_attack(attack_ptr atk)2483 bool unit::remove_attack(attack_ptr atk)
2484 {
2485 	auto iter = std::find(attacks_.begin(), attacks_.end(), atk);
2486 	if(iter == attacks_.end()) {
2487 		return false;
2488 	}
2489 	attacks_.erase(iter);
2490 	return true;
2491 }
2492 
remove_attacks_ai()2493 void unit::remove_attacks_ai()
2494 {
2495 	if(attacks_left_ == max_attacks_) {
2496 		//TODO: add state_not_attacked
2497 	}
2498 
2499 	set_attacks(0);
2500 }
2501 
remove_movement_ai()2502 void unit::remove_movement_ai()
2503 {
2504 	if(movement_left() == total_movement()) {
2505 		set_state(STATE_NOT_MOVED,true);
2506 	}
2507 
2508 	set_movement(0, true);
2509 }
2510 
set_hidden(bool state) const2511 void unit::set_hidden(bool state) const
2512 {
2513 //	appearance_changed_ = true;
2514 	hidden_ = state;
2515 	if(!state) {
2516 		return;
2517 	}
2518 
2519 	// We need to get rid of haloes immediately to avoid display glitches
2520 	anim_comp_->clear_haloes();
2521 }
2522 
set_image_halo(const std::string & halo)2523 void unit::set_image_halo(const std::string& halo)
2524 {
2525 	appearance_changed_ = true;
2526 	anim_comp_->clear_haloes();
2527 	halo_.reset(new std::string(halo));
2528 }
2529 
parse_upkeep(const config::attribute_value & upkeep)2530 void unit::parse_upkeep(const config::attribute_value& upkeep)
2531 {
2532 	if(upkeep.empty()) {
2533 		return;
2534 	}
2535 
2536 	// TODO: create abetter way to check whether it is actually an int.
2537 	int upkeep_int = upkeep.to_int(-99);
2538 	if(upkeep_int != -99) {
2539 		upkeep_ = upkeep_int;
2540 	} else if(upkeep == upkeep_loyal::type() || upkeep == "free") {
2541 		upkeep_ = upkeep_loyal();
2542 	} else if(upkeep == upkeep_full::type()) {
2543 		upkeep_ = upkeep_full();
2544 	} else {
2545 		WRN_UT << "Found invalid upkeep=\"" << upkeep <<  "\" in a unit" << std::endl;
2546 		upkeep_ = upkeep_full();
2547 	}
2548 }
2549 
write_upkeep(config::attribute_value & upkeep) const2550 void unit::write_upkeep(config::attribute_value& upkeep) const
2551 {
2552 	upkeep = boost::apply_visitor(upkeep_type_visitor(), upkeep_);
2553 }
2554 
2555 // Filters unimportant stats from the unit config and returns a checksum of
2556 // the remaining config.
get_checksum(const unit & u)2557 std::string get_checksum(const unit& u)
2558 {
2559 	config unit_config;
2560 	config wcfg;
2561 	u.write(unit_config);
2562 
2563 	const std::string main_keys[] {
2564 		"advances_to",
2565 		"alignment",
2566 		"cost",
2567 		"experience",
2568 		"gender",
2569 		"hitpoints",
2570 		"ignore_race_traits",
2571 		"ignore_global_traits",
2572 		"level",
2573 		"recall_cost",
2574 		"max_attacks",
2575 		"max_experience",
2576 		"max_hitpoints",
2577 		"max_moves",
2578 		"movement",
2579 		"movement_type",
2580 		"race",
2581 		"random_traits",
2582 		"resting",
2583 		"undead_variation",
2584 		"upkeep",
2585 		"zoc",
2586 		""
2587 	};
2588 
2589 	for(int i = 0; !main_keys[i].empty(); ++i) {
2590 		wcfg[main_keys[i]] = unit_config[main_keys[i]];
2591 	}
2592 
2593 	const std::string attack_keys[] {
2594 		"name",
2595 		"type",
2596 		"range",
2597 		"damage",
2598 		"number",
2599 		""
2600 	};
2601 
2602 	for(const config& att : unit_config.child_range("attack")) {
2603 		config& child = wcfg.add_child("attack");
2604 
2605 		for(int i = 0; !attack_keys[i].empty(); ++i) {
2606 			child[attack_keys[i]] = att[attack_keys[i]];
2607 		}
2608 
2609 		for(const config& spec : att.child_range("specials")) {
2610 			config& child_spec = child.add_child("specials", spec);
2611 
2612 			child_spec.recursive_clear_value("description");
2613 		}
2614 	}
2615 
2616 	for(const config& abi : unit_config.child_range("abilities")) {
2617 		config& child = wcfg.add_child("abilities", abi);
2618 
2619 		child.recursive_clear_value("description");
2620 		child.recursive_clear_value("description_inactive");
2621 		child.recursive_clear_value("name");
2622 		child.recursive_clear_value("name_inactive");
2623 	}
2624 
2625 	for(const config& trait : unit_config.child_range("trait")) {
2626 		config& child = wcfg.add_child("trait", trait);
2627 
2628 		child.recursive_clear_value("description");
2629 		child.recursive_clear_value("male_name");
2630 		child.recursive_clear_value("female_name");
2631 		child.recursive_clear_value("name");
2632 	}
2633 
2634 	const std::string child_keys[] {
2635 		"advance_from",
2636 		"defense",
2637 		"movement_costs",
2638 		"vision_costs",
2639 		"jamming_costs",
2640 		"resistance",
2641 		""
2642 	};
2643 
2644 	for(int i = 0; !child_keys[i].empty(); ++i) {
2645 		for(const config& c : unit_config.child_range(child_keys[i])) {
2646 			wcfg.add_child(child_keys[i], c);
2647 		}
2648 	}
2649 
2650 	DBG_UT << wcfg;
2651 
2652 	return wcfg.hash();
2653 }
2654 
swap(unit & lhs,unit & rhs)2655 void swap(unit& lhs, unit& rhs)
2656 {
2657 	lhs.swap(rhs);
2658 }
2659