1 /*
2  * Copyright (C) 2002-2020 by the Widelands Development Team
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  *
18  */
19 
20 #include "logic/map_objects/tribes/soldier.h"
21 
22 #include <memory>
23 
24 #include "base/macros.h"
25 #include "base/math.h"
26 #include "base/wexception.h"
27 #include "economy/economy.h"
28 #include "economy/flag.h"
29 #include "graphic/animation/animation_manager.h"
30 #include "graphic/graphic.h"
31 #include "graphic/rendertarget.h"
32 #include "io/fileread.h"
33 #include "io/filewrite.h"
34 #include "logic/editor_game_base.h"
35 #include "logic/game.h"
36 #include "logic/game_controller.h"
37 #include "logic/game_data_error.h"
38 #include "logic/map_objects/checkstep.h"
39 #include "logic/map_objects/findbob.h"
40 #include "logic/map_objects/findimmovable.h"
41 #include "logic/map_objects/findnode.h"
42 #include "logic/map_objects/tribes/battle.h"
43 #include "logic/map_objects/tribes/building.h"
44 #include "logic/map_objects/tribes/militarysite.h"
45 #include "logic/map_objects/tribes/tribe_descr.h"
46 #include "logic/map_objects/tribes/warehouse.h"
47 #include "logic/message_queue.h"
48 #include "logic/player.h"
49 #include "map_io/map_object_loader.h"
50 #include "map_io/map_object_saver.h"
51 #include "wui/mapviewpixelconstants.h"
52 
53 namespace Widelands {
54 
55 namespace {
56 constexpr int kSoldierHealthBarWidth = 13;
57 constexpr int kRetreatWhenHealthDropsBelowThisPercentage = 50;
58 }  // namespace
59 
SoldierLevelRange()60 SoldierLevelRange::SoldierLevelRange()
61    : min_health(-1),
62      min_attack(-1),
63      min_defense(-1),
64      min_evade(-1),
65      max_health(-1),
66      max_attack(-1),
67      max_defense(-1),
68      max_evade(-1) {
69 }
70 
SoldierLevelRange(const LuaTable & t)71 SoldierLevelRange::SoldierLevelRange(const LuaTable& t) {
72 	min_health = t.get_int("min_health");
73 	min_attack = t.get_int("min_attack");
74 	min_defense = t.get_int("min_defense");
75 	min_evade = t.get_int("min_evade");
76 	max_health = t.get_int("max_health");
77 	max_attack = t.get_int("max_attack");
78 	max_defense = t.get_int("max_defense");
79 	max_evade = t.get_int("max_evade");
80 }
81 
matches(int32_t health,int32_t attack,int32_t defense,int32_t evade) const82 bool SoldierLevelRange::matches(int32_t health,
83                                 int32_t attack,
84                                 int32_t defense,
85                                 int32_t evade) const {
86 	return (health >= min_health && health <= max_health && attack >= min_attack &&
87 	        attack <= max_attack && defense >= min_defense && defense <= max_defense &&
88 	        evade >= min_evade && evade <= max_evade);
89 }
90 
matches(const Soldier * soldier) const91 bool SoldierLevelRange::matches(const Soldier* soldier) const {
92 	return matches(soldier->get_health_level(), soldier->get_attack_level(),
93 	               soldier->get_defense_level(), soldier->get_evade_level());
94 }
95 
SoldierDescr(const std::string & init_descname,const LuaTable & table,const Tribes & tribes)96 SoldierDescr::SoldierDescr(const std::string& init_descname,
97                            const LuaTable& table,
98                            const Tribes& tribes)
99    : WorkerDescr(init_descname, MapObjectType::SOLDIER, table, tribes),
100      health_(table.get_table("health")),
101      attack_(table.get_table("attack")),
102      defense_(table.get_table("defense")),
103      evade_(table.get_table("evade")) {
104 	add_attribute(MapObject::Attribute::SOLDIER);
105 
106 	// Battle animations
107 	// attack_success_*-> soldier is attacking and hit his opponent
108 	add_battle_animation(table.get_table("attack_success_w"), &attack_success_w_name_);
109 	add_battle_animation(table.get_table("attack_success_e"), &attack_success_e_name_);
110 
111 	// attack_failure_*-> soldier is attacking and miss hit, defender evades
112 	add_battle_animation(table.get_table("attack_failure_w"), &attack_failure_w_name_);
113 	add_battle_animation(table.get_table("attack_failure_e"), &attack_failure_e_name_);
114 
115 	// evade_success_* -> soldier is defending and opponent misses
116 	add_battle_animation(table.get_table("evade_success_w"), &evade_success_w_name_);
117 	add_battle_animation(table.get_table("evade_success_e"), &evade_success_e_name_);
118 
119 	// evade_failure_* -> soldier is defending and opponent hits
120 	add_battle_animation(table.get_table("evade_failure_w"), &evade_failure_w_name_);
121 	add_battle_animation(table.get_table("evade_failure_e"), &evade_failure_e_name_);
122 
123 	// die_*           -> soldier is dying
124 	add_battle_animation(table.get_table("die_w"), &die_w_name_);
125 	add_battle_animation(table.get_table("die_e"), &die_e_name_);
126 
127 	// per-level walking and idle animations
128 	add_battle_animation(table.get_table("idle"), &idle_name_);
129 	{
130 		std::unique_ptr<LuaTable> walk_table = table.get_table("walk");
131 		for (const auto& entry : walk_table->keys<int>()) {
132 			std::unique_ptr<LuaTable> range_table = walk_table->get_table(entry);
133 			// I would prefer to use the SoldierLevelRange as key in the table,
134 			// but LuaTable can handle only string keys :(
135 			SoldierLevelRange* range = nullptr;
136 			std::map<uint8_t, std::string> map;
137 			for (const std::string& dir_name : range_table->keys<std::string>()) {
138 				uint8_t dir;
139 				if (dir_name == "range") {
140 					range = new SoldierLevelRange(*range_table->get_table(dir_name));
141 					continue;
142 				} else if (dir_name == "sw") {
143 					dir = WALK_SW;
144 				} else if (dir_name == "se") {
145 					dir = WALK_SE;
146 				} else if (dir_name == "nw") {
147 					dir = WALK_NW;
148 				} else if (dir_name == "ne") {
149 					dir = WALK_NE;
150 				} else if (dir_name == "e") {
151 					dir = WALK_E;
152 				} else if (dir_name == "w") {
153 					dir = WALK_W;
154 				} else {
155 					throw GameDataError("Invalid walking direction: %s", dir_name.c_str());
156 				}
157 				const std::string anim_name = range_table->get_string(dir_name);
158 				if (!is_animation_known(anim_name)) {
159 					throw GameDataError(
160 					   "Trying to add unknown soldier walking animation: %s", anim_name.c_str());
161 				}
162 				map.emplace(dir, anim_name);
163 			}
164 			walk_name_.emplace(std::make_pair(std::unique_ptr<SoldierLevelRange>(range), map));
165 		}
166 	}
167 }
168 
BattleAttribute(std::unique_ptr<LuaTable> table)169 SoldierDescr::BattleAttribute::BattleAttribute(std::unique_ptr<LuaTable> table) {
170 	base = table->get_int("base");
171 
172 	if (table->has_key("maximum")) {
173 		maximum = table->get_int("maximum");
174 		if (base > maximum) {
175 			throw GameDataError(
176 			   "Base %d is greater than maximum %d for a soldier's battle attribute.", base, maximum);
177 		}
178 	} else {
179 		maximum = base;
180 	}
181 	increase = table->get_int("increase_per_level");
182 	max_level = table->get_int("max_level");
183 
184 	// Load Graphics
185 	std::vector<std::string> image_filenames =
186 	   table->get_table("pictures")->array_entries<std::string>();
187 	if (image_filenames.size() != max_level + 1) {
188 		throw GameDataError(
189 		   "Soldier needs to have %u pictures for battle attribute, but found %" PRIuS, max_level + 1,
190 		   image_filenames.size());
191 	}
192 	for (const std::string& image_filename : image_filenames) {
193 		images.push_back(g_gr->images().get(image_filename));
194 	}
195 }
196 
197 /**
198  * Get random animation of specified type
199  */
get_rand_anim(Game & game,const std::string & animation_name,const Soldier * soldier) const200 uint32_t SoldierDescr::get_rand_anim(Game& game,
201                                      const std::string& animation_name,
202                                      const Soldier* soldier) const {
203 	std::string run = animation_name;
204 
205 	const SoldierAnimationsList* animations = nullptr;
206 	if (animation_name == "attack_success_w") {
207 		animations = &attack_success_w_name_;
208 	} else if (animation_name == "attack_success_e") {
209 		animations = &attack_success_e_name_;
210 	} else if (animation_name == "attack_failure_w") {
211 		animations = &attack_failure_w_name_;
212 	} else if (animation_name == "attack_failure_e") {
213 		animations = &attack_failure_e_name_;
214 	} else if (animation_name == "evade_success_w") {
215 		animations = &evade_success_w_name_;
216 	} else if (animation_name == "evade_success_e") {
217 		animations = &evade_success_e_name_;
218 	} else if (animation_name == "evade_failure_w") {
219 		animations = &evade_failure_w_name_;
220 	} else if (animation_name == "evade_failure_e") {
221 		animations = &evade_failure_e_name_;
222 	} else if (animation_name == "die_w") {
223 		animations = &die_w_name_;
224 	} else if (animation_name == "die_e") {
225 		animations = &die_e_name_;
226 	} else {
227 		throw GameDataError("Unknown soldier battle animation: %s", animation_name.c_str());
228 	}
229 
230 	assert(!animations->empty());
231 	uint32_t nr_animations = 0;
232 	for (const auto& pair : *animations) {
233 		if (pair.second.matches(soldier)) {
234 			nr_animations++;
235 		}
236 	}
237 	if (nr_animations < 1) {
238 		throw GameDataError("No battle animations for %s found!", animation_name.c_str());
239 	}
240 	uint32_t i = game.logic_rand() % nr_animations;
241 	for (const auto& pair : *animations) {
242 		if (pair.second.matches(soldier)) {
243 			if (i == 0) {
244 				run = pair.first;
245 				break;
246 			}
247 			i--;
248 		}
249 	}
250 
251 	if (!is_animation_known(run)) {
252 		log("Missing animation '%s' for soldier %s. Reverting to idle.\n", run.c_str(),
253 		    name().c_str());
254 		run = "idle";
255 	}
256 	return get_animation(run, soldier);
257 }
258 
get_animation(const std::string & anim,const MapObject * mo) const259 uint32_t SoldierDescr::get_animation(const std::string& anim, const MapObject* mo) const {
260 	const Soldier* soldier = dynamic_cast<const Soldier*>(mo);
261 	if (!soldier || anim != "idle") {
262 		// We only need to check for a level-dependent idle animation.
263 		// The walking anims can also be level-dependent,
264 		// but that is taken care of by get_right_walk_anims().
265 		// For battle animations, the level is already taken into account by the random selector.
266 		return WorkerDescr::get_animation(anim, mo);
267 	}
268 	for (const auto& pair : idle_name_) {
269 		if (pair.second.matches(soldier)) {
270 			// Use the parent method here, so we don't end up in
271 			// an endless loop if the idle anim is called "idle"
272 			return WorkerDescr::get_animation(pair.first, mo);
273 		}
274 	}
275 	throw GameDataError("This soldier does not have an idle animation for this training level!");
276 }
277 
get_right_walk_anims(bool const ware,Worker * worker) const278 const DirAnimations& SoldierDescr::get_right_walk_anims(bool const ware, Worker* worker) const {
279 	Soldier* soldier = dynamic_cast<Soldier*>(worker);
280 	if (!soldier) {
281 		return WorkerDescr::get_right_walk_anims(ware, worker);
282 	}
283 	auto& cache = soldier->get_walking_animations_cache();
284 	if (cache.first && cache.first->matches(soldier)) {
285 		return *cache.second;
286 	}
287 	for (const auto& pair : walk_name_) {
288 		if (pair.first->matches(soldier)) {
289 			cache.first.reset(new SoldierLevelRange(*pair.first));
290 			cache.second.reset(new DirAnimations());
291 			for (uint8_t dir = 1; dir <= 6; ++dir) {
292 				cache.second->set_animation(dir, get_animation(pair.second.at(dir), worker));
293 			}
294 			return *cache.second;
295 		}
296 	}
297 	throw GameDataError(
298 	   "Soldier %s does not have walking animations for his level!", name().c_str());
299 }
300 
301 /**
302  * Create a new soldier
303  */
create_object() const304 Bob& SoldierDescr::create_object() const {
305 	return *new Soldier(*this);
306 }
307 
add_battle_animation(std::unique_ptr<LuaTable> table,SoldierAnimationsList * result)308 void SoldierDescr::add_battle_animation(std::unique_ptr<LuaTable> table,
309                                         SoldierAnimationsList* result) {
310 	for (const std::string& anim_name : table->keys<std::string>()) {
311 		if (!is_animation_known(anim_name)) {
312 			throw GameDataError("Trying to add unknown battle animation: %s", anim_name.c_str());
313 		}
314 		result->emplace(anim_name, SoldierLevelRange(*table->get_table(anim_name)));
315 	}
316 }
317 
318 /*
319 ==============================
320 
321 IMPLEMENTATION
322 
323 ==============================
324 */
325 
326 /// all done through init
Soldier(const SoldierDescr & soldier_descr)327 Soldier::Soldier(const SoldierDescr& soldier_descr) : Worker(soldier_descr) {
328 	battle_ = nullptr;
329 	health_level_ = 0;
330 	attack_level_ = 0;
331 	defense_level_ = 0;
332 	evade_level_ = 0;
333 
334 	current_health_ = get_max_health();
335 	retreat_health_ = 0;
336 
337 	combat_walking_ = CD_NONE;
338 	combat_walkstart_ = 0;
339 	combat_walkend_ = 0;
340 }
341 
init(EditorGameBase & egbase)342 bool Soldier::init(EditorGameBase& egbase) {
343 	health_level_ = 0;
344 	attack_level_ = 0;
345 	defense_level_ = 0;
346 	evade_level_ = 0;
347 	retreat_health_ = 0;
348 
349 	current_health_ = get_max_health();
350 
351 	combat_walking_ = CD_NONE;
352 	combat_walkstart_ = 0;
353 	combat_walkend_ = 0;
354 
355 	return Worker::init(egbase);
356 }
357 
cleanup(EditorGameBase & egbase)358 void Soldier::cleanup(EditorGameBase& egbase) {
359 	Worker::cleanup(egbase);
360 }
361 
is_evict_allowed()362 bool Soldier::is_evict_allowed() {
363 	return !is_on_battlefield();
364 }
365 
366 /*
367  * Set this soldiers level. Automatically sets the new values
368  */
set_level(uint32_t const health,uint32_t const attack,uint32_t const defense,uint32_t const evade)369 void Soldier::set_level(uint32_t const health,
370                         uint32_t const attack,
371                         uint32_t const defense,
372                         uint32_t const evade) {
373 	set_health_level(health);
374 	set_attack_level(attack);
375 	set_defense_level(defense);
376 	set_evade_level(evade);
377 }
set_health_level(const uint32_t health)378 void Soldier::set_health_level(const uint32_t health) {
379 	assert(health_level_ <= health);
380 	assert(health <= descr().get_max_health_level());
381 
382 	uint32_t oldmax = get_max_health();
383 
384 	health_level_ = health;
385 
386 	uint32_t newmax = get_max_health();
387 	current_health_ = current_health_ * newmax / oldmax;
388 }
set_attack_level(const uint32_t attack)389 void Soldier::set_attack_level(const uint32_t attack) {
390 	assert(attack_level_ <= attack);
391 	assert(attack <= descr().get_max_attack_level());
392 
393 	attack_level_ = attack;
394 }
set_defense_level(const uint32_t defense)395 void Soldier::set_defense_level(const uint32_t defense) {
396 	assert(defense_level_ <= defense);
397 	assert(defense <= descr().get_max_defense_level());
398 
399 	defense_level_ = defense;
400 }
set_evade_level(const uint32_t evade)401 void Soldier::set_evade_level(const uint32_t evade) {
402 	assert(evade_level_ <= evade);
403 	assert(evade <= descr().get_max_evade_level());
404 
405 	evade_level_ = evade;
406 }
set_retreat_health(const uint32_t retreat)407 void Soldier::set_retreat_health(const uint32_t retreat) {
408 	assert(retreat <= get_max_health());
409 
410 	retreat_health_ = retreat;
411 }
412 
get_level(TrainingAttribute const at) const413 uint32_t Soldier::get_level(TrainingAttribute const at) const {
414 	switch (at) {
415 	case TrainingAttribute::kHealth:
416 		return health_level_;
417 	case TrainingAttribute::kAttack:
418 		return attack_level_;
419 	case TrainingAttribute::kDefense:
420 		return defense_level_;
421 	case TrainingAttribute::kEvade:
422 		return evade_level_;
423 	case TrainingAttribute::kTotal:
424 		return health_level_ + attack_level_ + defense_level_ + evade_level_;
425 	}
426 	NEVER_HERE();
427 }
428 
get_training_attribute(TrainingAttribute const attr) const429 int32_t Soldier::get_training_attribute(TrainingAttribute const attr) const {
430 	switch (attr) {
431 	case TrainingAttribute::kHealth:
432 		return health_level_;
433 	case TrainingAttribute::kAttack:
434 		return attack_level_;
435 	case TrainingAttribute::kDefense:
436 		return defense_level_;
437 	case TrainingAttribute::kEvade:
438 		return evade_level_;
439 	case TrainingAttribute::kTotal:
440 		return health_level_ + attack_level_ + defense_level_ + evade_level_;
441 	}
442 	return Worker::get_training_attribute(attr);
443 }
444 
get_max_health() const445 uint32_t Soldier::get_max_health() const {
446 	return descr().get_base_health() + health_level_ * descr().get_health_incr_per_level();
447 }
448 
get_min_attack() const449 uint32_t Soldier::get_min_attack() const {
450 	return descr().get_base_min_attack() + attack_level_ * descr().get_attack_incr_per_level();
451 }
452 
get_max_attack() const453 uint32_t Soldier::get_max_attack() const {
454 	return descr().get_base_max_attack() + attack_level_ * descr().get_attack_incr_per_level();
455 }
456 
get_defense() const457 uint32_t Soldier::get_defense() const {
458 	return descr().get_base_defense() + defense_level_ * descr().get_defense_incr_per_level();
459 }
460 
get_evade() const461 uint32_t Soldier::get_evade() const {
462 	return descr().get_base_evade() + evade_level_ * descr().get_evade_incr_per_level();
463 }
464 
465 //  Unsignedness ensures that we can only heal, not hurt through this method.
heal(const uint32_t health)466 void Soldier::heal(const uint32_t health) {
467 	molog("[soldier] healing (%d+)%d/%d\n", health, current_health_, get_max_health());
468 	assert(health);
469 	assert(current_health_ < get_max_health());
470 	current_health_ += std::min(health, get_max_health() - current_health_);
471 	assert(current_health_ <= get_max_health());
472 }
473 
474 /**
475  * This only subs the specified number of health points, don't do anything more.
476  */
damage(const uint32_t value)477 void Soldier::damage(const uint32_t value) {
478 	assert(current_health_ > 0);
479 
480 	molog("[soldier] damage %d(-%d)/%d\n", current_health_, value, get_max_health());
481 	if (current_health_ < value)
482 		current_health_ = 0;
483 	else
484 		current_health_ -= value;
485 }
486 
487 /// Calculates the actual position to draw on from the base node position.
488 /// This function takes battling into account.
489 ///
490 /// pos is the location, in pixels, of the node position_ (height is already
491 /// taken into account).
calc_drawpos(const EditorGameBase & game,const Vector2f & field_on_dst,const float scale) const492 Vector2f Soldier::calc_drawpos(const EditorGameBase& game,
493                                const Vector2f& field_on_dst,
494                                const float scale) const {
495 	if (combat_walking_ == CD_NONE) {
496 		return Bob::calc_drawpos(game, field_on_dst, scale);
497 	}
498 
499 	bool moving = false;
500 	Vector2f spos = field_on_dst, epos = field_on_dst;
501 
502 	const float triangle_width = kTriangleWidth * scale;
503 	switch (combat_walking_) {
504 	case CD_WALK_W:
505 		moving = true;
506 		epos.x -= triangle_width / 4;
507 		break;
508 	case CD_WALK_E:
509 		moving = true;
510 		epos.x += triangle_width / 4;
511 		break;
512 	case CD_RETURN_W:
513 		moving = true;
514 		spos.x -= triangle_width / 4;
515 		break;
516 	case CD_RETURN_E:
517 		moving = true;
518 		spos.x += triangle_width / 4;
519 		break;
520 	case CD_COMBAT_W:
521 		moving = false;
522 		epos.x -= triangle_width / 4;
523 		break;
524 	case CD_COMBAT_E:
525 		moving = false;
526 		epos.x += triangle_width / 4;
527 		break;
528 	case CD_NONE:
529 		break;
530 	}
531 
532 	if (moving) {
533 		const float f = math::clamp(static_cast<float>(game.get_gametime() - combat_walkstart_) /
534 		                               (combat_walkend_ - combat_walkstart_),
535 		                            0.f, 1.f);
536 		assert(combat_walkstart_ <= game.get_gametime());
537 		assert(combat_walkstart_ < combat_walkend_);
538 		epos.x = f * epos.x + (1 - f) * spos.x;
539 	}
540 	return epos;
541 }
542 
543 /*
544  * Draw this soldier. This basically draws him as a worker, but add health points
545  */
draw(const EditorGameBase & game,const InfoToDraw & info_to_draw,const Vector2f & field_on_dst,const Coords & coords,float scale,RenderTarget * dst) const546 void Soldier::draw(const EditorGameBase& game,
547                    const InfoToDraw& info_to_draw,
548                    const Vector2f& field_on_dst,
549                    const Coords& coords,
550                    float scale,
551                    RenderTarget* dst) const {
552 	const uint32_t anim = get_current_anim();
553 	if (!anim) {
554 		return;
555 	}
556 
557 	const Vector2f point_on_dst = calc_drawpos(game, field_on_dst, scale);
558 	draw_info_icon(
559 	   point_on_dst.cast<int>() -
560 	      Vector2i(0, (g_gr->animations().get_animation(get_current_anim()).height() - 7) * scale),
561 	   scale, InfoMode::kWalkingAround, info_to_draw, dst);
562 	draw_inner(game, point_on_dst, coords, scale, dst);
563 }
564 
565 /**
566  * Draw the info icon (level indicators + health bar) for this soldier.
567  * 'draw_mode' determines whether the soldier info is displayed in a building window
568  * or on top of a soldier walking around. 'info_to_draw' checks which info the user wants to see
569  * for soldiers walking around.
570  */
draw_info_icon(Vector2i draw_position,float scale,const InfoMode draw_mode,const InfoToDraw info_to_draw,RenderTarget * dst) const571 void Soldier::draw_info_icon(Vector2i draw_position,
572                              float scale,
573                              const InfoMode draw_mode,
574                              const InfoToDraw info_to_draw,
575                              RenderTarget* dst) const {
576 	if (!(info_to_draw & InfoToDraw::kSoldierLevels)) {
577 		return;
578 	}
579 
580 	// Since the graphics below are all pixel perfect and scaling them as floats
581 	// looks weird, we round to the nearest fullest integer. We do allow half size though.
582 	scale = std::max(0.5f, std::round(scale));
583 
584 #ifndef NDEBUG
585 	{
586 		// This function assumes stuff about our data files: level icons are all the
587 		// same size and this is smaller than the width of the healthbar. This
588 		// simplifies the drawing code below a lot. Before it had a lot of if () that
589 		// were never tested - since our data files never changed.
590 		const Image* healthpic = get_health_level_pic();
591 
592 		const Image* attackpic = get_attack_level_pic();
593 		const Image* defensepic = get_defense_level_pic();
594 		const Image* evadepic = get_evade_level_pic();
595 
596 		const int dimension = attackpic->width();
597 		assert(attackpic->height() == dimension);
598 		assert(healthpic->width() == dimension);
599 		assert(healthpic->height() == dimension);
600 		assert(defensepic->width() == dimension);
601 		assert(defensepic->height() == dimension);
602 		assert(evadepic->width() == dimension);
603 		assert(evadepic->height() == dimension);
604 		assert(kSoldierHealthBarWidth > dimension);
605 	}
606 #endif
607 
608 	const int icon_size = get_health_level_pic()->height();
609 
610 	// Draw health info in building windows, or if kSoldierLevels is on.
611 	const bool draw_health_bar =
612 	   draw_mode == InfoMode::kInBuilding || (info_to_draw & InfoToDraw::kSoldierLevels);
613 
614 	switch (draw_mode) {
615 	case InfoMode::kInBuilding:
616 		draw_position.x += kSoldierHealthBarWidth * scale;
617 		draw_position.y += 2 * icon_size * scale;
618 		break;
619 	case InfoMode::kWalkingAround:
620 		if (draw_health_bar) {
621 			draw_position.y -= 5 * scale;
622 		}
623 	}
624 
625 	if (draw_health_bar) {
626 		// Draw energy bar
627 		assert(get_max_health());
628 		const RGBColor& color = owner().get_playercolor();
629 		const uint16_t color_sum = color.r + color.g + color.b;
630 
631 		// The frame gets a slight tint of player color
632 		const Recti energy_outer(draw_position - Vector2i(kSoldierHealthBarWidth, 0) * scale,
633 		                         kSoldierHealthBarWidth * 2 * scale, 5 * scale);
634 		dst->fill_rect(energy_outer, color);
635 		dst->brighten_rect(energy_outer, 230 - color_sum / 3);
636 
637 		// Adjust health to current animation tick
638 		uint32_t health_to_show = current_health_;
639 		if (battle_) {
640 			uint32_t pending_damage = battle_->get_pending_damage(this);
641 			if (pending_damage > 0) {
642 				int32_t timeshift = owner().egbase().get_gametime() - get_animstart();
643 				timeshift = std::min(std::max(0, timeshift), 1000);
644 
645 				pending_damage *= timeshift;
646 				pending_damage /= 1000;
647 
648 				if (pending_damage > health_to_show) {
649 					health_to_show = 0;
650 				} else {
651 					health_to_show -= pending_damage;
652 				}
653 			}
654 		}
655 
656 		// Now draw the health bar itself
657 		const int health_width = 2 * (kSoldierHealthBarWidth - 1) * health_to_show / get_max_health();
658 
659 		Recti energy_inner(draw_position + Vector2i(-kSoldierHealthBarWidth + 1, 1) * scale,
660 		                   health_width * scale, 3 * scale);
661 		Recti energy_complement(energy_inner.origin() + Vector2i(health_width, 0) * scale,
662 		                        (2 * (kSoldierHealthBarWidth - 1) - health_width) * scale, 3 * scale);
663 
664 		const RGBColor complement_color =
665 		   color_sum > 128 * 3 ? RGBColor(32, 32, 32) : RGBColor(224, 224, 224);
666 		dst->fill_rect(energy_inner, color);
667 		dst->fill_rect(energy_complement, complement_color);
668 	}
669 
670 	// Draw level info in building windows, or if kSoldierLevels is on.
671 	if (draw_mode == InfoMode::kInBuilding || (info_to_draw & InfoToDraw::kSoldierLevels)) {
672 		const auto draw_level_image = [icon_size, scale, &draw_position, dst](
673 		   const Vector2i& offset, const Image* image) {
674 			dst->blitrect_scale(
675 			   Rectf(draw_position + offset * icon_size * scale, icon_size * scale, icon_size * scale),
676 			   image, Recti(0, 0, icon_size, icon_size), 1.f, BlendMode::UseAlpha);
677 		};
678 
679 		draw_level_image(Vector2i(-1, -2), get_attack_level_pic());
680 		draw_level_image(Vector2i(0, -2), get_defense_level_pic());
681 		draw_level_image(Vector2i(-1, -1), get_health_level_pic());
682 		draw_level_image(Vector2i(0, -1), get_evade_level_pic());
683 	}
684 }
685 
686 /**
687  * Compute the size of the info icon (level indicators + health bar) for soldiers of
688  * the given tribe.
689  */
calc_info_icon_size(const TribeDescr & tribe,int & w,int & h)690 void Soldier::calc_info_icon_size(const TribeDescr& tribe, int& w, int& h) {
691 	const SoldierDescr* soldierdesc =
692 	   static_cast<const SoldierDescr*>(tribe.get_worker_descr(tribe.soldier()));
693 	// The function draw_info_icon() already assumes that all icons have the same dimensions,
694 	// so we can make the same assumption here too.
695 	const int dimension = soldierdesc->get_health_level_pic(0)->height();
696 	w = 2 * std::max(dimension, kSoldierHealthBarWidth);
697 	h = 5 + 2 * dimension;
698 }
699 
pop_task_or_fight(Game & game)700 void Soldier::pop_task_or_fight(Game& game) {
701 	if (battle_)
702 		start_task_battle(game);
703 	else
704 		pop_task(game);
705 }
706 
707 /**
708  *
709  *
710  */
start_animation(EditorGameBase & egbase,const std::string & animname,uint32_t const time)711 void Soldier::start_animation(EditorGameBase& egbase,
712                               const std::string& animname,
713                               uint32_t const time) {
714 	molog("[soldier] starting animation %s", animname.c_str());
715 	Game& game = dynamic_cast<Game&>(egbase);
716 	return start_task_idle(game, descr().get_rand_anim(game, animname, this), time);
717 }
718 
719 /**
720  * \return \c true if this soldier is considered to be on the battlefield
721  */
is_on_battlefield()722 bool Soldier::is_on_battlefield() {
723 	return get_state(taskAttack) || get_state(taskDefense);
724 }
725 
726 /**
727  * \return \c true if this soldier is considered to be attacking the player
728  */
is_attacking_player(Game & game,Player & player)729 bool Soldier::is_attacking_player(Game& game, Player& player) {
730 	State* state = get_state(taskAttack);
731 	if (state) {
732 		if (upcast(PlayerImmovable, imm, state->objvar1.get(game))) {
733 			return (imm->get_owner() == &player);
734 		}
735 	}
736 	return false;
737 }
738 
get_battle() const739 Battle* Soldier::get_battle() const {
740 	return battle_;
741 }
742 
743 /**
744  * Determine whether this soldier can be challenged by an opponent.
745  *
746  * Such a challenge might override a battle that the soldier is currently
747  * walking towards, to avoid lockups when the combatants cannot reach
748  * each other.
749  */
can_be_challenged()750 bool Soldier::can_be_challenged() {
751 	if (current_health_ < 1) {  // Soldier is dead!
752 		return false;
753 	}
754 	if (!is_on_battlefield()) {
755 		return false;
756 	}
757 	if (!battle_) {
758 		return true;
759 	}
760 	return !battle_->locked(dynamic_cast<Game&>(get_owner()->egbase()));
761 }
762 
763 /**
764  * Assign the soldier to a battle (may be zero).
765  *
766  * \note must only be called by the \ref Battle object
767  */
set_battle(Game & game,Battle * const battle)768 void Soldier::set_battle(Game& game, Battle* const battle) {
769 	if (battle_ != battle) {
770 		battle_ = battle;
771 		send_signal(game, "battle");
772 	}
773 }
774 
775 /**
776  * Set a fallback task.
777  */
init_auto_task(Game & game)778 void Soldier::init_auto_task(Game& game) {
779 	if (get_current_health() < 1) {
780 		molog("[soldier] init_auto_task: die\n");
781 		return start_task_die(game);
782 	}
783 
784 	return Worker::init_auto_task(game);
785 }
786 
787 struct FindNodeOwned {
FindNodeOwnedWidelands::FindNodeOwned788 	explicit FindNodeOwned(PlayerNumber owner) : owner_(owner) {
789 	}
acceptWidelands::FindNodeOwned790 	bool accept(const EditorGameBase&, const FCoords& coords) const {
791 		return (coords.field->get_owned_by() == owner_);
792 	}
793 
794 private:
795 	PlayerNumber owner_;
796 };
797 
798 /**
799  * Leave our home building and single-mindedly try to attack
800  * and conquer the given building.
801  *
802  * The following variables are used:
803  * \li objvar1 the \ref Building we're attacking.
804  */
805 Bob::Task const Soldier::taskAttack = {"attack", static_cast<Bob::Ptr>(&Soldier::attack_update),
806                                        nullptr, static_cast<Bob::Ptr>(&Soldier::attack_pop), true};
807 
start_task_attack(Game & game,Building & building)808 void Soldier::start_task_attack(Game& game, Building& building) {
809 	push_task(game, taskAttack);
810 	State& state = top_state();
811 	state.objvar1 = &building;
812 	state.coords = building.get_position();
813 	state.ivar2 = 0;  // The return state 1=go home 2=go back in known land
814 	state.ivar3 = 0;  // Counts how often the soldier is blocked in a row
815 
816 	state.ivar1 |= CF_RETREAT_WHEN_INJURED;
817 	set_retreat_health(kRetreatWhenHealthDropsBelowThisPercentage * get_max_health() / 100);
818 
819 	// Injured soldiers are not allowed to attack
820 	if (get_retreat_health() > get_current_health()) {
821 		set_retreat_health(get_current_health());
822 	}
823 	molog("[attack] starting, retreat health: %d\n", get_retreat_health());
824 }
825 
attack_update(Game & game,State & state)826 void Soldier::attack_update(Game& game, State& state) {
827 	std::string signal = get_signal();
828 	uint32_t defenders = 0;
829 
830 	if (signal.size()) {
831 		if (signal == "battle" || signal == "wakeup" || signal == "sleep") {
832 			state.ivar3 = 0;
833 			signal_handled();
834 		} else if (signal == "blocked") {
835 			state.ivar3++;
836 			signal_handled();
837 		} else if (signal == "fail") {
838 			state.ivar3 = 0;
839 			signal_handled();
840 			if (state.objvar1.get(game)) {
841 				molog("[attack] failed to reach enemy\n");
842 				state.objvar1 = nullptr;
843 			} else {
844 				molog("[attack] unexpected fail\n");
845 				return pop_task(game);
846 			}
847 		} else if (signal == "location") {
848 			molog("[attack] Location destroyed\n");
849 			state.ivar3 = 0;
850 			signal_handled();
851 			if (state.ivar2 == 0) {
852 				state.ivar2 = 1;
853 			}
854 		} else {
855 			molog("[attack] cancelled by unexpected signal '%s'\n", signal.c_str());
856 			return pop_task(game);
857 		}
858 	} else {
859 		// no signals means no consecutive block -> we're not stuck anymore
860 		state.ivar3 = 0;
861 	}
862 
863 	//  We are at enemy building flag, and a defender is coming, sleep until he
864 	// "wake up"s me
865 	if (signal == "sleep") {
866 		return start_task_idle(game, descr().get_animation("idle", this), -1);
867 	}
868 
869 	upcast(Building, location, get_location(game));
870 	upcast(Building, enemy, state.objvar1.get(game));
871 
872 	// Handle returns
873 	const Map& map = game.map();
874 	if (state.ivar2 > 0) {
875 		if (state.ivar2 == 1) {
876 			// Return home
877 			if (!location || !is_a(MilitarySite, location)) {
878 				molog("[attack] No more site to go back to\n");
879 				state.ivar2 = 2;
880 				return schedule_act(game, 10);
881 			}
882 			Flag& baseflag = location->base_flag();
883 			if (get_position() == baseflag.get_position()) {
884 				// At flag, enter building
885 				return start_task_move(
886 				   game, WALK_NW, descr().get_right_walk_anims(does_carry_ware(), this), true);
887 			}
888 			if (get_position() == location->get_position()) {
889 				// At building, check if attack is required
890 				if (!enemy) {
891 					molog("[attack] returned home\n");
892 					return pop_task_or_fight(game);
893 				}
894 				state.ivar2 = 0;
895 				return start_task_leavebuilding(game, false);
896 			}
897 			// Head to home
898 			if (state.ivar3 > kBockCountIsStuck)
899 				molog("[attack] soldier is stuck, blocked nodes will be ignored\n");
900 
901 			if (start_task_movepath(game, baseflag.get_position(),
902 			                        4,  // use larger persist when returning home
903 			                        descr().get_right_walk_anims(does_carry_ware(), this), false, -1,
904 			                        state.ivar3 > kBockCountIsStuck))
905 				return;
906 			else {
907 				molog("[attack] failed to return home\n");
908 				return pop_task(game);
909 			}
910 		}
911 		if (state.ivar2 == 2) {
912 			// No more home, so return to homeland
913 			upcast(Flag, flag, map.get_immovable(get_position()));
914 			if (flag && flag->get_owner() == get_owner()) {
915 				// At a flag
916 				molog("[attack] Returned to own flag\n");
917 				return pop_task(game);
918 			}
919 			Coords target;
920 			if (get_location(game)) {
921 				// We still have a location, head for the flag
922 				target = get_location(game)->base_flag().get_position();
923 				molog("[attack] Going back to our flag\n");
924 			} else {
925 				// No location
926 				if (get_position().field->get_owned_by() == get_owner()->player_number()) {
927 					// We are in our land, become fugitive
928 					molog("[attack] Back to our land\n");
929 					return pop_task(game);
930 				}
931 				// Try to find our land
932 				std::vector<Coords> coords;
933 				uint32_t maxdist = descr().vision_range() * 2;
934 				Area<FCoords> area(map.get_fcoords(get_position()), maxdist);
935 				if (map.find_reachable_fields(game, area, &coords, CheckStepDefault(descr().movecaps()),
936 				                              FindNodeOwned(get_owner()->player_number()))) {
937 					// Found home land
938 					target = coords.front();
939 					molog("[attack] Going back to our land\n");
940 				} else {
941 					// Become fugitive
942 					molog("[attack] No land in sight\n");
943 					return pop_task(game);
944 				}
945 			}
946 			if (start_task_movepath(game, target,
947 			                        4,  // use larger persist when returning home
948 			                        descr().get_right_walk_anims(does_carry_ware(), this)))
949 				return;
950 			else {
951 				molog("[attack] failed to return to own land\n");
952 				return pop_task(game);
953 			}
954 		}
955 	}
956 
957 	if (battle_)
958 		return start_task_battle(game);
959 
960 	if (signal == "blocked") {
961 		// Wait before we try again. Note that this must come *after*
962 		// we check for a battle
963 		// Note that we *should* be woken via send_space_signals,
964 		// so the timeout is just an additional safety net.
965 		return start_task_idle(game, descr().get_animation("idle", this), 5000);
966 	}
967 
968 	// Count remaining defenders
969 	if (enemy) {
970 		if (enemy->soldier_control() != nullptr) {
971 			defenders = enemy->soldier_control()->present_soldiers().size();
972 		}
973 		if (upcast(Warehouse, wh, enemy)) {
974 			Requirements noreq;
975 			defenders =
976 			   wh->count_workers(game, wh->owner().tribe().soldier(), noreq, Warehouse::Match::kExact);
977 		}
978 		//  Any enemy soldier at baseflag count as defender.
979 		std::vector<Bob*> soldiers;
980 		map.find_bobs(game, Area<FCoords>(map.get_fcoords(enemy->base_flag().get_position()), 0),
981 		              &soldiers, FindBobEnemySoldier(get_owner()));
982 		defenders += soldiers.size();
983 	}
984 
985 	if (!enemy || (get_retreat_health() > get_current_health() && defenders > 0)) {
986 		// Injured soldiers will try to return to safe site at home.
987 		if (get_retreat_health() > get_current_health()) {
988 			assert(state.ivar1 & CF_RETREAT_WHEN_INJURED);
989 			if (defenders) {
990 				molog(" [attack] badly injured (%d), retreating...\n", get_current_health());
991 				state.coords = Coords::null();
992 				state.objvar1 = nullptr;
993 			}
994 		}
995 		// The old militarysite gets replaced by a new one, so if "enemy" is not
996 		// valid anymore, we either "conquered" the new building, or it was
997 		// destroyed.
998 		if (state.coords) {
999 			BaseImmovable* const newimm = map[state.coords].get_immovable();
1000 			upcast(MilitarySite, newsite, newimm);
1001 			if (newsite && (&newsite->owner() == &owner())) {
1002 				const SoldierControl* soldier_control = newsite->soldier_control();
1003 				assert(soldier_control != nullptr);  // 'newsite' is a military site
1004 				state.objvar1 = nullptr;
1005 				// We may also have our location destroyed in between
1006 				if (soldier_control->stationed_soldiers().size() <
1007 				       soldier_control->soldier_capacity() &&
1008 				    (!location ||
1009 				     location->base_flag().get_position() != newsite->base_flag().get_position())) {
1010 					molog("[attack] enemy belongs to us now, move in\n");
1011 					pop_task(game);
1012 					set_location(newsite);
1013 					newsite->update_soldier_request();
1014 					return schedule_act(game, 10);
1015 				}
1016 			}
1017 		}
1018 		// Return home
1019 		state.ivar2 = 1;
1020 		return schedule_act(game, 10);
1021 	}
1022 
1023 	// At this point, we know that the enemy building still stands,
1024 	// and that we're outside in the plains.
1025 	if (get_position() != enemy->base_flag().get_position()) {
1026 		if (start_task_movepath(game, enemy->base_flag().get_position(), 3,
1027 		                        descr().get_right_walk_anims(does_carry_ware(), this)))
1028 			return;
1029 		else {
1030 			molog("[attack] failed to move towards building flag, cancel attack "
1031 			      "and return home!\n");
1032 			state.coords = Coords::null();
1033 			state.objvar1 = nullptr;
1034 			state.ivar2 = 1;
1035 			return schedule_act(game, 10);
1036 		}
1037 	}
1038 
1039 	assert(enemy->attack_target() != nullptr);
1040 
1041 	molog("[attack] attacking target building\n");
1042 	//  give the enemy soldier some time to act
1043 	schedule_act(
1044 	   game, enemy->attack_target()->attack(this) == AttackTarget::AttackResult::DefenderLaunched ?
1045 	            1000 :
1046 	            10);
1047 }
1048 
attack_pop(Game & game,State &)1049 void Soldier::attack_pop(Game& game, State&) {
1050 	if (battle_)
1051 		battle_->cancel(game, *this);
1052 }
1053 
1054 /**
1055  * Accept Bob when is a Soldier alive that is attacking the Player.
1056  */
1057 struct FindBobSoldierAttackingPlayer : public FindBob {
FindBobSoldierAttackingPlayerWidelands::FindBobSoldierAttackingPlayer1058 	FindBobSoldierAttackingPlayer(Game& g, Player& p) : player(p), game(g) {
1059 	}
1060 
acceptWidelands::FindBobSoldierAttackingPlayer1061 	bool accept(Bob* const bob) const override {
1062 		if (upcast(Soldier, soldier, bob)) {
1063 			return soldier->get_current_health() && soldier->is_attacking_player(game, player) &&
1064 			       soldier->owner().is_hostile(player);
1065 		}
1066 		return false;
1067 	}
1068 
1069 	Player& player;
1070 	Game& game;
1071 };
1072 
1073 /**
1074  * Soldiers with this task go out of his buildings. They will
1075  * try to find an enemy in his lands and go to hunt them down (signaling
1076  * "battle"). If no enemy was found inside our lands, but an enemy is found
1077  * outside our lands, then wait until the enemy goes inside or dissapear.
1078  * If no enemy is found, then return home.
1079  *
1080  * Variables used:
1081  * \li ivar1 used to store \c CombatFlags
1082  * \li ivar2 when CF_DEFEND_STAYHOME, 1 if it has reached the flag
1083 //           when CF_RETREAT_WHEN_INJURED, the lesser health before fleeing
1084  */
1085 Bob::Task const Soldier::taskDefense = {"defense", static_cast<Bob::Ptr>(&Soldier::defense_update),
1086                                         nullptr, static_cast<Bob::Ptr>(&Soldier::defense_pop),
1087                                         true};
1088 
start_task_defense(Game & game,bool stayhome)1089 void Soldier::start_task_defense(Game& game, bool stayhome) {
1090 	molog("[defense] starting\n");
1091 	push_task(game, taskDefense);
1092 	State& state = top_state();
1093 
1094 	state.ivar1 = 0;
1095 	state.ivar2 = 0;
1096 
1097 	// Here goes 'configuration'
1098 	if (stayhome) {
1099 		state.ivar1 |= CF_DEFEND_STAYHOME;
1100 		set_retreat_health(0);
1101 	} else {
1102 		/* Flag defenders are not allowed to flee, to avoid abuses */
1103 		state.ivar1 |= CF_RETREAT_WHEN_INJURED;
1104 		set_retreat_health(get_max_health() * kRetreatWhenHealthDropsBelowThisPercentage / 100);
1105 
1106 		// Soldier must defend even if he starts injured
1107 		// (current health is below retreat treshold)
1108 		if (get_retreat_health() > get_current_health()) {
1109 			set_retreat_health(get_current_health());
1110 		}
1111 	}
1112 	molog("[defense] retreat health set: %d\n", get_retreat_health());
1113 }
1114 
1115 struct SoldierDistance {
1116 	Soldier* s;
1117 	int dist;
1118 
SoldierDistanceWidelands::SoldierDistance1119 	SoldierDistance(Soldier* a, int d) : dist(d) {
1120 		s = a;
1121 	}
1122 
1123 	struct Greater {
operator ()Widelands::SoldierDistance::Greater1124 		bool operator()(const SoldierDistance& a, const SoldierDistance& b) {
1125 			return (a.dist > b.dist);
1126 		}
1127 	};
1128 };
1129 
defense_update(Game & game,State & state)1130 void Soldier::defense_update(Game& game, State& state) {
1131 	std::string signal = get_signal();
1132 
1133 	if (signal.size()) {
1134 		if (signal == "blocked" || signal == "battle" || signal == "wakeup") {
1135 			signal_handled();
1136 		} else {
1137 			molog("[defense] cancelled by signal '%s'\n", signal.c_str());
1138 			return pop_task(game);
1139 		}
1140 	}
1141 
1142 	PlayerImmovable* const location = get_location(game);
1143 	BaseImmovable* const position = game.map()[get_position()].get_immovable();
1144 
1145 	/**
1146 	 * Attempt to fix a crash when player bulldozes a building being defended
1147 	 * by soldiers.
1148 	 */
1149 	if (!location)
1150 		return pop_task(game);
1151 
1152 	Flag& baseflag = location->base_flag();
1153 
1154 	if (battle_)
1155 		return start_task_battle(game);
1156 
1157 	if (signal == "blocked")
1158 		// Wait before we try again. Note that this must come *after*
1159 		// we check for a battle
1160 		// Note that we *should* be woken via send_space_signals,
1161 		// so the timeout is just an additional safety net.
1162 		return start_task_idle(game, descr().get_animation("idle", this), 5000);
1163 
1164 	// If we only are defending our home ...
1165 	if (state.ivar1 & CF_DEFEND_STAYHOME) {
1166 		if (position == location && state.ivar2 == 1) {
1167 			molog("[defense] stayhome: returned home\n");
1168 			return pop_task_or_fight(game);
1169 		}
1170 
1171 		if (position == &baseflag) {
1172 			state.ivar2 = 1;
1173 			assert(state.ivar2 == 1);
1174 
1175 			if (battle_)
1176 				return start_task_battle(game);
1177 
1178 			// Check if any attacker is waiting us to fight
1179 			std::vector<Bob*> soldiers;
1180 			game.map().find_bobs(
1181 			   game, Area<FCoords>(get_position(), 0), &soldiers, FindBobEnemySoldier(get_owner()));
1182 
1183 			for (Bob* temp_bob : soldiers) {
1184 				if (upcast(Soldier, temp_soldier, temp_bob)) {
1185 					if (temp_soldier->can_be_challenged()) {
1186 						assert(temp_soldier != nullptr);
1187 						new Battle(game, this, temp_soldier);
1188 						return start_task_battle(game);
1189 					}
1190 				}
1191 			}
1192 
1193 			if (state.ivar2 == 1) {
1194 				molog("[defense] stayhome: return home\n");
1195 				return start_task_return(game, false);
1196 			}
1197 		}
1198 
1199 		molog("[defense] stayhome: leavebuilding\n");
1200 		return start_task_leavebuilding(game, false);
1201 	}
1202 
1203 	// We are outside our building, get list of enemy soldiers attacking us
1204 	std::vector<Bob*> soldiers;
1205 	game.map().find_bobs(game, Area<FCoords>(get_position(), 10), &soldiers,
1206 	                     FindBobSoldierAttackingPlayer(game, *get_owner()));
1207 
1208 	if (soldiers.empty() || (get_current_health() < get_retreat_health())) {
1209 		if (get_retreat_health() > get_current_health()) {
1210 			assert(state.ivar1 & CF_RETREAT_WHEN_INJURED);
1211 		}
1212 
1213 		if (get_current_health() < get_retreat_health()) {
1214 			molog("[defense] I am heavily injured (%d)!\n", get_current_health());
1215 		} else
1216 			molog("[defense] no enemy soldiers found, ending task\n");
1217 
1218 		// If no enemy was found, return home
1219 		if (!location) {
1220 			molog("[defense] location disappeared during battle\n");
1221 			return pop_task(game);
1222 		}
1223 
1224 		// Soldier is inside of building
1225 		if (position == location) {
1226 			molog("[defense] returned home\n");
1227 			return pop_task_or_fight(game);
1228 		}
1229 
1230 		// Soldier is on base flag
1231 		if (position == &baseflag) {
1232 			return start_task_move(
1233 			   game, WALK_NW, descr().get_right_walk_anims(does_carry_ware(), this), true);
1234 		}
1235 		molog("[defense] return home\n");
1236 		if (start_task_movepath(game, baseflag.get_position(),
1237 		                        4,  // use larger persist when returning home
1238 		                        descr().get_right_walk_anims(does_carry_ware(), this)))
1239 			return;
1240 
1241 		molog("[defense] could not find way home\n");
1242 		return pop_task(game);
1243 	}
1244 
1245 	// Go through soldiers
1246 	std::vector<SoldierDistance> targets;
1247 	for (Bob* temp_bob : soldiers) {
1248 
1249 		// If enemy is in our land, then go after it!
1250 		if (upcast(Soldier, soldier, temp_bob)) {
1251 			assert(soldier != this);
1252 			Field const f = game.map().operator[](soldier->get_position());
1253 
1254 			//  Check soldier, be sure that we can fight against soldier.
1255 			// Soldiers can not go over enemy land when defending.
1256 			if ((soldier->can_be_challenged()) && (f.get_owned_by() == get_owner()->player_number())) {
1257 				uint32_t thisDist = game.map().calc_distance(get_position(), soldier->get_position());
1258 				targets.push_back(SoldierDistance(soldier, thisDist));
1259 			}
1260 		}
1261 	}
1262 
1263 	std::stable_sort(targets.begin(), targets.end(), SoldierDistance::Greater());
1264 
1265 	while (!targets.empty()) {
1266 		const SoldierDistance& target = targets.back();
1267 
1268 		if (position == location) {
1269 			return start_task_leavebuilding(game, false);
1270 		}
1271 
1272 		if (target.dist <= 1) {
1273 			molog("[defense] starting battle with %u!\n", target.s->serial());
1274 			assert(target.s != nullptr);
1275 			new Battle(game, this, target.s);
1276 			return start_task_battle(game);
1277 		}
1278 
1279 		// Move towards soldier
1280 		if (start_task_movepath(game, target.s->get_position(), 3,
1281 		                        descr().get_right_walk_anims(does_carry_ware(), this), false, 1)) {
1282 			molog("[defense] move towards soldier %u\n", target.s->serial());
1283 			return;
1284 		} else {
1285 			molog("[defense] failed to move towards attacking soldier %u\n", target.s->serial());
1286 			targets.pop_back();
1287 		}
1288 	}
1289 	// If the enemy is not in our land, wait
1290 	return start_task_idle(game, descr().get_animation("idle", this), 250);
1291 }
1292 
defense_pop(Game & game,State &)1293 void Soldier::defense_pop(Game& game, State&) {
1294 	if (battle_)
1295 		battle_->cancel(game, *this);
1296 }
1297 
1298 Bob::Task const Soldier::taskMoveInBattle = {
1299    "moveInBattle", static_cast<Bob::Ptr>(&Soldier::move_in_battle_update), nullptr, nullptr, true};
1300 
start_task_move_in_battle(Game & game,CombatWalkingDir dir)1301 void Soldier::start_task_move_in_battle(Game& game, CombatWalkingDir dir) {
1302 	int32_t mapdir = IDLE;
1303 
1304 	switch (dir) {
1305 	case CD_WALK_W:
1306 	case CD_RETURN_E:
1307 		mapdir = WALK_W;
1308 		break;
1309 	case CD_WALK_E:
1310 	case CD_RETURN_W:
1311 		mapdir = WALK_E;
1312 		break;
1313 	case CD_NONE:
1314 	case CD_COMBAT_E:
1315 	case CD_COMBAT_W:
1316 		throw GameDataError("bad direction '%d'", dir);
1317 	}
1318 
1319 	const Map& map = game.map();
1320 	int32_t const tdelta = (map.calc_cost(get_position(), mapdir)) / 2;
1321 	molog("[move_in_battle] dir: (%d) tdelta: (%d)\n", dir, tdelta);
1322 	combat_walking_ = dir;
1323 	combat_walkstart_ = game.get_gametime();
1324 	combat_walkend_ = combat_walkstart_ + tdelta;
1325 
1326 	push_task(game, taskMoveInBattle);
1327 	State& state = top_state();
1328 	state.ivar1 = dir;
1329 	set_animation(game, descr().get_animation(mapdir == WALK_E ? "walk_e" : "walk_w", this));
1330 }
1331 
move_in_battle_update(Game & game,State &)1332 void Soldier::move_in_battle_update(Game& game, State&) {
1333 	if (game.get_gametime() >= combat_walkend_) {
1334 		switch (combat_walking_) {
1335 		case CD_NONE:
1336 			break;
1337 		case CD_WALK_W:
1338 			combat_walking_ = CD_COMBAT_W;
1339 			break;
1340 		case CD_WALK_E:
1341 			combat_walking_ = CD_COMBAT_E;
1342 			break;
1343 		case CD_RETURN_W:
1344 		case CD_RETURN_E:
1345 		case CD_COMBAT_W:
1346 		case CD_COMBAT_E:
1347 			combat_walking_ = CD_NONE;
1348 			break;
1349 		}
1350 		return pop_task(game);
1351 	} else
1352 		//  Only end the task once we've actually completed the step
1353 		// Ignore signals until then
1354 		return schedule_act(game, combat_walkend_ - game.get_gametime());
1355 }
1356 
1357 /**
1358  * \return \c true if the defending soldier should not stray from
1359  * his home flag.
1360  */
stay_home()1361 bool Soldier::stay_home() {
1362 	if (State const* const state = get_state(taskDefense))
1363 		return state->ivar1 & CF_DEFEND_STAYHOME;
1364 	return false;
1365 }
1366 
1367 /**
1368  * We are out in the open and involved in a challenge/battle.
1369  * Meet with the other soldier and fight.
1370  */
1371 Bob::Task const Soldier::taskBattle = {"battle", static_cast<Bob::Ptr>(&Soldier::battle_update),
1372                                        nullptr, static_cast<Bob::Ptr>(&Soldier::battle_pop), true};
1373 
start_task_battle(Game & game)1374 void Soldier::start_task_battle(Game& game) {
1375 	assert(battle_);
1376 	combat_walking_ = CD_NONE;
1377 
1378 	push_task(game, taskBattle);
1379 }
1380 
battle_update(Game & game,State &)1381 void Soldier::battle_update(Game& game, State&) {
1382 	std::string signal = get_signal();
1383 	molog("[battle] update for player %u's soldier: signal = \"%s\"\n", owner().player_number(),
1384 	      signal.c_str());
1385 
1386 	if (signal.size()) {
1387 		if (signal == "blocked") {
1388 			signal_handled();
1389 			return start_task_idle(game, descr().get_animation("idle", this), 5000);
1390 		} else if (signal == "location" || signal == "battle" || signal == "wakeup")
1391 			signal_handled();
1392 		else {
1393 			molog("[battle] interrupted by unexpected signal '%s'\n", signal.c_str());
1394 			return pop_task(game);
1395 		}
1396 	}
1397 
1398 	// The opponent might have died on us
1399 	if (!battle_ || battle_->opponent(*this) == nullptr) {
1400 		if (combat_walking_ == CD_COMBAT_W) {
1401 			return start_task_move_in_battle(game, CD_RETURN_W);
1402 		}
1403 		if (combat_walking_ == CD_COMBAT_E) {
1404 			return start_task_move_in_battle(game, CD_RETURN_E);
1405 		}
1406 		assert(combat_walking_ == CD_NONE);
1407 		molog("[battle] is over\n");
1408 		send_space_signals(game);
1409 		return pop_task(game);
1410 	}
1411 
1412 	const Map& map = game.map();
1413 	Soldier& opponent = *battle_->opponent(*this);
1414 	if (opponent.get_position() != get_position()) {
1415 		if (is_a(Building, map[get_position()].get_immovable())) {
1416 			// Note that this does not use the "leavebuilding" task,
1417 			// because that task is geared towards orderly workers leaving
1418 			// their location, whereas this case can also happen when
1419 			// a player starts a construction site over a waiting soldier.
1420 			molog("[battle] we are in a building, leave it\n");
1421 			return start_task_move(
1422 			   game, WALK_SE, descr().get_right_walk_anims(does_carry_ware(), this), true);
1423 		}
1424 	}
1425 
1426 	if (stay_home()) {
1427 		if (this == battle_->first()) {
1428 			molog("[battle] stay_home, so reverse roles\n");
1429 			new Battle(game, battle_->second(), battle_->first());
1430 			return skip_act();  //  we will get a signal via set_battle()
1431 		} else {
1432 			if (combat_walking_ != CD_COMBAT_E) {
1433 				opponent.send_signal(game, "wakeup");
1434 				return start_task_move_in_battle(game, CD_WALK_E);
1435 			}
1436 		}
1437 	} else {
1438 		if (opponent.stay_home() && (this == battle_->second())) {
1439 			// Wait until correct roles are assigned
1440 			new Battle(game, battle_->second(), battle_->first());
1441 			return schedule_act(game, 10);
1442 		}
1443 
1444 		if (opponent.get_position() != get_position()) {
1445 			Coords dest = opponent.get_position();
1446 
1447 			if (upcast(Building, building, map[dest].get_immovable()))
1448 				dest = building->base_flag().get_position();
1449 
1450 			uint32_t const dist = map.calc_distance(get_position(), dest);
1451 
1452 			if (dist >= 2 || this == battle_->first()) {
1453 				// Only make small steps at a time, so we can adjust to the
1454 				// opponent's change of position.
1455 				if (start_task_movepath(game, dest, 0,
1456 				                        descr().get_right_walk_anims(does_carry_ware(), this), false,
1457 				                        (dist + 3) / 4)) {
1458 					molog("[battle] player %u's soldier started task_movepath to (%i,%i)\n",
1459 					      owner().player_number(), dest.x, dest.y);
1460 					return;
1461 				} else {
1462 					BaseImmovable const* const immovable_position =
1463 					   get_position().field->get_immovable();
1464 					BaseImmovable const* const immovable_dest = map[dest].get_immovable();
1465 
1466 					const std::string messagetext =
1467 					   (boost::format("The game engine has encountered a logic error. The %s "
1468 					                  "#%u of player %u could not find a way from (%i, %i) "
1469 					                  "(with %s immovable) to the opponent (%s #%u of player "
1470 					                  "%u) at (%i, %i) (with %s immovable). The %s will now "
1471 					                  "desert (but will not be executed). Strange things may "
1472 					                  "happen. No solution for this problem has been "
1473 					                  "implemented yet. (bug #536066) (The game has been "
1474 					                  "paused.)") %
1475 					    descr().descname().c_str() % serial() %
1476 					    static_cast<unsigned int>(owner().player_number()) % get_position().x %
1477 					    get_position().y %
1478 					    (immovable_position ? immovable_position->descr().descname().c_str() : ("no")) %
1479 					    opponent.descr().descname().c_str() % opponent.serial() %
1480 					    static_cast<unsigned int>(opponent.owner().player_number()) % dest.x % dest.y %
1481 					    (immovable_dest ? immovable_dest->descr().descname().c_str() : ("no")) %
1482 					    descr().descname().c_str())
1483 					      .str();
1484 					get_owner()->add_message(
1485 					   game, std::unique_ptr<Message>(
1486 					            new Message(Message::Type::kGameLogic, game.get_gametime(),
1487 					                        descr().descname(), "images/ui_basic/menu_help.png",
1488 					                        _("Logic error"), messagetext, get_position(), serial_)));
1489 					opponent.get_owner()->add_message(
1490 					   game, std::unique_ptr<Message>(new Message(
1491 					            Message::Type::kGameLogic, game.get_gametime(), descr().descname(),
1492 					            "images/ui_basic/menu_help.png", _("Logic error"), messagetext,
1493 					            opponent.get_position(), serial_)));
1494 					game.game_controller()->set_desired_speed(0);
1495 					return pop_task(game);
1496 				}
1497 			}
1498 		} else {
1499 			assert(opponent.get_position() == get_position());
1500 			assert(battle_ == opponent.get_battle());
1501 
1502 			if (opponent.is_walking()) {
1503 				molog("[battle]: Opponent '%d' is walking, sleeping\n", opponent.serial());
1504 				// We should be woken up by our opponent, but add a timeout anyway for robustness
1505 				return start_task_idle(game, descr().get_animation("idle", this), 5000);
1506 			}
1507 
1508 			if (battle_->first()->serial() == serial()) {
1509 				if (combat_walking_ != CD_COMBAT_W) {
1510 					molog("[battle]: Moving west\n");
1511 					opponent.send_signal(game, "wakeup");
1512 					return start_task_move_in_battle(game, CD_WALK_W);
1513 				}
1514 			} else {
1515 				if (combat_walking_ != CD_COMBAT_E) {
1516 					molog("[battle]: Moving east\n");
1517 					opponent.send_signal(game, "wakeup");
1518 					return start_task_move_in_battle(game, CD_WALK_E);
1519 				}
1520 			}
1521 		}
1522 	}
1523 
1524 	battle_->get_battle_work(game, *this);
1525 }
1526 
battle_pop(Game & game,State &)1527 void Soldier::battle_pop(Game& game, State&) {
1528 	if (battle_)
1529 		battle_->cancel(game, *this);
1530 }
1531 
1532 Bob::Task const Soldier::taskDie = {"die", static_cast<Bob::Ptr>(&Soldier::die_update), nullptr,
1533                                     static_cast<Bob::Ptr>(&Soldier::die_pop), true};
1534 
start_task_die(Game & game)1535 void Soldier::start_task_die(Game& game) {
1536 	push_task(game, taskDie);
1537 	top_state().ivar1 = game.get_gametime() + 1000;
1538 
1539 	// Dead soldier is not owned by a location
1540 	set_location(nullptr);
1541 
1542 	const uint32_t anim =
1543 	   descr().get_rand_anim(game, combat_walking_ == CD_COMBAT_W ? "die_w" : "die_e", this);
1544 	start_task_idle(game, anim, 1000);
1545 }
1546 
die_update(Game & game,State & state)1547 void Soldier::die_update(Game& game, State& state) {
1548 	std::string signal = get_signal();
1549 	molog("[die] update for player %u's soldier: signal = \"%s\"\n", owner().player_number(),
1550 	      signal.c_str());
1551 
1552 	if (signal.size()) {
1553 		signal_handled();
1554 	}
1555 
1556 	if ((state.ivar1 >= 0) && (static_cast<uint32_t>(state.ivar1) > game.get_gametime()))
1557 		return schedule_act(game, state.ivar1 - game.get_gametime());
1558 
1559 	// When task updated, dead is near!
1560 	return pop_task(game);
1561 }
1562 
die_pop(Game & game,State &)1563 void Soldier::die_pop(Game& game, State&) {
1564 	// Destroy the soldier!
1565 	molog("[die] soldier %u has died\n", serial());
1566 	schedule_destroy(game);
1567 }
1568 
1569 /**
1570  * Accept a Bob if it is a Soldier, is not dead and is running taskAttack or
1571  * taskDefense.
1572  */
1573 struct FindBobSoldierOnBattlefield : public FindBob {
acceptWidelands::FindBobSoldierOnBattlefield1574 	bool accept(Bob* const bob) const override {
1575 		if (upcast(Soldier, soldier, bob))
1576 			return soldier->is_on_battlefield() && soldier->get_current_health();
1577 		return false;
1578 	}
1579 };
1580 
1581 /**
1582  * Override \ref Bob::check_node_blocked.
1583  *
1584  * As long as we're on the battlefield, check for other soldiers.
1585  */
check_node_blocked(Game & game,const FCoords & field,bool const commit)1586 bool Soldier::check_node_blocked(Game& game, const FCoords& field, bool const commit) {
1587 	State* attackdefense = get_state(taskAttack);
1588 
1589 	if (!attackdefense)
1590 		attackdefense = get_state(taskDefense);
1591 
1592 	if (!attackdefense || ((attackdefense->ivar1 & CF_RETREAT_WHEN_INJURED) &&
1593 	                       get_retreat_health() > get_current_health())) {
1594 		// Retreating or non-combatant soldiers act like normal bobs
1595 		return Bob::check_node_blocked(game, field, commit);
1596 	}
1597 
1598 	if (field.field->get_immovable() && field.field->get_immovable() == get_location(game)) {
1599 		if (commit)
1600 			send_space_signals(game);
1601 		return false;  // we can always walk home
1602 	}
1603 
1604 	Soldier* foundsoldier = nullptr;
1605 	bool foundbattle = false;
1606 	bool foundopponent = false;
1607 	bool multiplesoldiers = false;
1608 
1609 	for (Bob* bob = field.field->get_first_bob(); bob; bob = bob->get_next_on_field()) {
1610 		if (upcast(Soldier, soldier, bob)) {
1611 			if (!soldier->is_on_battlefield() || !soldier->get_current_health())
1612 				continue;
1613 
1614 			if (!foundsoldier) {
1615 				foundsoldier = soldier;
1616 			} else {
1617 				multiplesoldiers = true;
1618 			}
1619 
1620 			if (soldier->get_battle()) {
1621 				foundbattle = true;
1622 
1623 				if (battle_ && battle_->opponent(*this) == soldier)
1624 					foundopponent = true;
1625 			}
1626 		}
1627 	}
1628 
1629 	if (!foundopponent && (foundbattle || foundsoldier)) {
1630 		if (commit && !foundbattle && !multiplesoldiers) {
1631 			if (foundsoldier->owner().is_hostile(*get_owner()) && foundsoldier->can_be_challenged()) {
1632 				molog("[check_node_blocked] attacking a soldier (%u)\n", foundsoldier->serial());
1633 				new Battle(game, this, foundsoldier);
1634 			}
1635 		}
1636 
1637 		return true;
1638 	} else {
1639 		if (commit)
1640 			send_space_signals(game);
1641 		return false;
1642 	}
1643 }
1644 
1645 /**
1646  * Send a "wakeup" signal to all surrounding soldiers that are out in the open,
1647  * so that they may repeat pathfinding.
1648  */
send_space_signals(Game & game)1649 void Soldier::send_space_signals(Game& game) {
1650 	std::vector<Bob*> soldiers;
1651 
1652 	game.map().find_bobs(
1653 	   game, Area<FCoords>(get_position(), 1), &soldiers, FindBobSoldierOnBattlefield());
1654 
1655 	for (Bob* temp_soldier : soldiers) {
1656 		if (upcast(Soldier, soldier, temp_soldier)) {
1657 			if (soldier != this) {
1658 				soldier->send_signal(game, "wakeup");
1659 			}
1660 		}
1661 	}
1662 
1663 	PlayerNumber const land_owner = get_position().field->get_owned_by();
1664 	// First check if the soldier is standing on someone else's territory
1665 	if (land_owner != owner().player_number()) {
1666 		// Let's collect all reachable attack_target sites in vicinity (militarysites mainly)
1667 		std::vector<BaseImmovable*> attack_targets;
1668 		game.map().find_reachable_immovables_unique(
1669 		   game, Area<FCoords>(get_position(), kMaxProtectionRadius), attack_targets,
1670 		   CheckStepWalkOn(descr().movecaps(), false), FindImmovableAttackTarget());
1671 
1672 		for (BaseImmovable* temp_attack_target : attack_targets) {
1673 			Building* building = dynamic_cast<Building*>(temp_attack_target);
1674 			assert(building != nullptr && building->attack_target() != nullptr);
1675 			const Player& attack_target_player = building->owner();
1676 			// Let's inform the site that this (=enemy) soldier is nearby and within the site's owner's
1677 			// territory
1678 			if (attack_target_player.player_number() == land_owner &&
1679 			    attack_target_player.is_hostile(*get_owner())) {
1680 				building->attack_target()->enemy_soldier_approaches(*this);
1681 			}
1682 		}
1683 	}
1684 }
1685 
log_general_info(const EditorGameBase & egbase) const1686 void Soldier::log_general_info(const EditorGameBase& egbase) const {
1687 	Worker::log_general_info(egbase);
1688 	molog("[Soldier]\n");
1689 	molog("Levels: %d/%d/%d/%d\n", health_level_, attack_level_, defense_level_, evade_level_);
1690 	molog("Health:   %d/%d\n", current_health_, get_max_health());
1691 	molog("Retreat:  %d\n", retreat_health_);
1692 	molog("Attack:   %d-%d\n", get_min_attack(), get_max_attack());
1693 	molog("Defense:  %d%%\n", get_defense());
1694 	molog("Evade:    %d%%\n", get_evade());
1695 	molog("CombatWalkingDir:   %i\n", combat_walking_);
1696 	molog("CombatWalkingStart: %i\n", combat_walkstart_);
1697 	molog("CombatWalkEnd:      %i\n", combat_walkend_);
1698 	molog("HasBattle:   %s\n", battle_ ? "yes" : "no");
1699 	if (battle_) {
1700 		molog("BattleSerial: %u\n", battle_->serial());
1701 		molog("Opponent: %u\n", battle_->opponent(*this)->serial());
1702 	}
1703 }
1704 
1705 /*
1706 ==============================
1707 
1708 Load/save support
1709 
1710 ==============================
1711 */
1712 
1713 constexpr uint8_t kCurrentPacketVersion = 3;
1714 // TODO(TiborB): This is only for map compatibility in regression tests, we should get rid of this
1715 // ASAP
1716 constexpr uint8_t kOldPacketVersion = 2;
1717 
Loader()1718 Soldier::Loader::Loader() : battle_(0) {
1719 }
1720 
load(FileRead & fr)1721 void Soldier::Loader::load(FileRead& fr) {
1722 	Worker::Loader::load(fr);
1723 
1724 	try {
1725 		uint8_t packet_version = fr.unsigned_8();
1726 		if (packet_version == kCurrentPacketVersion || packet_version == kOldPacketVersion) {
1727 
1728 			Soldier& soldier = get<Soldier>();
1729 			soldier.current_health_ = fr.unsigned_32();
1730 			if (packet_version == kCurrentPacketVersion) {
1731 				soldier.retreat_health_ = fr.unsigned_32();
1732 			} else {
1733 				// not ideal but will be used only for regression tests
1734 				soldier.retreat_health_ = 0;
1735 			}
1736 
1737 			soldier.health_level_ = std::min(fr.unsigned_32(), soldier.descr().get_max_health_level());
1738 			soldier.attack_level_ = std::min(fr.unsigned_32(), soldier.descr().get_max_attack_level());
1739 			soldier.defense_level_ =
1740 			   std::min(fr.unsigned_32(), soldier.descr().get_max_defense_level());
1741 			soldier.evade_level_ = std::min(fr.unsigned_32(), soldier.descr().get_max_evade_level());
1742 
1743 			if (soldier.current_health_ > soldier.get_max_health())
1744 				soldier.current_health_ = soldier.get_max_health();
1745 
1746 			if (soldier.retreat_health_ > soldier.get_max_health())
1747 				soldier.retreat_health_ = soldier.get_max_health();
1748 
1749 			soldier.combat_walking_ = static_cast<CombatWalkingDir>(fr.unsigned_8());
1750 			if (soldier.combat_walking_ != CD_NONE) {
1751 				soldier.combat_walkstart_ = fr.unsigned_32();
1752 				soldier.combat_walkend_ = fr.unsigned_32();
1753 			}
1754 
1755 			battle_ = fr.unsigned_32();
1756 		} else {
1757 			throw UnhandledVersionError("Soldier", packet_version, kCurrentPacketVersion);
1758 		}
1759 	} catch (const std::exception& e) {
1760 		throw wexception("loading soldier: %s", e.what());
1761 	}
1762 }
1763 
load_pointers()1764 void Soldier::Loader::load_pointers() {
1765 	Worker::Loader::load_pointers();
1766 
1767 	Soldier& soldier = get<Soldier>();
1768 
1769 	if (battle_)
1770 		soldier.battle_ = &mol().get<Battle>(battle_);
1771 }
1772 
get_task(const std::string & name)1773 const Bob::Task* Soldier::Loader::get_task(const std::string& name) {
1774 	if (name == "attack")
1775 		return &taskAttack;
1776 	if (name == "defense")
1777 		return &taskDefense;
1778 	if (name == "battle")
1779 		return &taskBattle;
1780 	if (name == "moveInBattle")
1781 		return &taskMoveInBattle;
1782 	if (name == "die")
1783 		return &taskDie;
1784 	return Worker::Loader::get_task(name);
1785 }
1786 
create_loader()1787 Soldier::Loader* Soldier::create_loader() {
1788 	return new Loader;
1789 }
1790 
do_save(EditorGameBase & egbase,MapObjectSaver & mos,FileWrite & fw)1791 void Soldier::do_save(EditorGameBase& egbase, MapObjectSaver& mos, FileWrite& fw) {
1792 	Worker::do_save(egbase, mos, fw);
1793 
1794 	fw.unsigned_8(kCurrentPacketVersion);
1795 	fw.unsigned_32(current_health_);
1796 	fw.unsigned_32(retreat_health_);
1797 	fw.unsigned_32(health_level_);
1798 	fw.unsigned_32(attack_level_);
1799 	fw.unsigned_32(defense_level_);
1800 	fw.unsigned_32(evade_level_);
1801 
1802 	fw.unsigned_8(combat_walking_);
1803 	if (combat_walking_ != CD_NONE) {
1804 		fw.unsigned_32(combat_walkstart_);
1805 		fw.unsigned_32(combat_walkend_);
1806 	}
1807 
1808 	fw.unsigned_32(mos.get_object_file_index_or_zero(battle_));
1809 }
1810 }  // namespace Widelands
1811