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/militarysite.h"
21 
22 #include <memory>
23 
24 #include "base/i18n.h"
25 #include "base/log.h"
26 #include "base/macros.h"
27 #include "economy/flag.h"
28 #include "economy/request.h"
29 #include "logic/editor_game_base.h"
30 #include "logic/game.h"
31 #include "logic/map_objects/findbob.h"
32 #include "logic/map_objects/tribes/battle.h"
33 #include "logic/map_objects/tribes/soldier.h"
34 #include "logic/map_objects/tribes/tribe_descr.h"
35 #include "logic/map_objects/tribes/worker.h"
36 #include "logic/message_queue.h"
37 #include "logic/player.h"
38 
39 namespace Widelands {
40 
present_soldiers() const41 std::vector<Soldier*> MilitarySite::SoldierControl::present_soldiers() const {
42 	std::vector<Soldier*> soldiers;
43 
44 	for (Worker* worker : military_site_->get_workers()) {
45 		if (upcast(Soldier, soldier, worker)) {
46 			if (military_site_->is_present(*soldier)) {
47 				soldiers.push_back(soldier);
48 			}
49 		}
50 	}
51 	return soldiers;
52 }
53 
stationed_soldiers() const54 std::vector<Soldier*> MilitarySite::SoldierControl::stationed_soldiers() const {
55 	std::vector<Soldier*> soldiers;
56 
57 	for (Worker* worker : military_site_->get_workers()) {
58 		if (upcast(Soldier, soldier, worker)) {
59 			soldiers.push_back(soldier);
60 		}
61 	}
62 	return soldiers;
63 }
64 
min_soldier_capacity() const65 Quantity MilitarySite::SoldierControl::min_soldier_capacity() const {
66 	return 1;
67 }
68 
max_soldier_capacity() const69 Quantity MilitarySite::SoldierControl::max_soldier_capacity() const {
70 	return military_site_->descr().get_max_number_of_soldiers();
71 }
72 
soldier_capacity() const73 Quantity MilitarySite::SoldierControl::soldier_capacity() const {
74 	return military_site_->capacity_;
75 }
76 
set_soldier_capacity(uint32_t const capacity)77 void MilitarySite::SoldierControl::set_soldier_capacity(uint32_t const capacity) {
78 	assert(min_soldier_capacity() <= capacity);
79 	assert(capacity <= max_soldier_capacity());
80 	assert(military_site_->capacity_ != capacity);
81 	military_site_->capacity_ = capacity;
82 	military_site_->update_soldier_request();
83 }
84 
drop_soldier(Soldier & soldier)85 void MilitarySite::SoldierControl::drop_soldier(Soldier& soldier) {
86 	Game& game = dynamic_cast<Game&>(military_site_->get_owner()->egbase());
87 
88 	if (!military_site_->is_present(soldier)) {
89 		// This can happen when the "drop soldier" player command is delayed
90 		// by network delay or a client has bugs.
91 		military_site_->molog("MilitarySite::drop_soldier(%u): not present\n", soldier.serial());
92 		return;
93 	}
94 	if (present_soldiers().size() <= min_soldier_capacity()) {
95 		military_site_->molog("cannot drop last soldier(s)\n");
96 		return;
97 	}
98 
99 	soldier.reset_tasks(game);
100 	soldier.start_task_leavebuilding(game, true);
101 
102 	military_site_->update_soldier_request();
103 }
104 
incorporate_soldier(EditorGameBase & egbase,Soldier & s)105 int MilitarySite::SoldierControl::incorporate_soldier(EditorGameBase& egbase, Soldier& s) {
106 	if (s.get_location(egbase) != military_site_) {
107 		s.set_location(military_site_);
108 	}
109 
110 	// Soldier upgrade is done once the site is full. In soldier upgrade, we
111 	// request one new soldier who is better suited than the existing ones.
112 	// Normally, I kick out one existing soldier as soon as a new guy starts walking
113 	// towards here. However, since that is done via infrequent polling, a new soldier
114 	// can sometimes reach the site before such kick-out happens. In those cases, we
115 	// should either drop one of the existing soldiers or reject the new guy, to
116 	// avoid overstocking this site.
117 
118 	if (stationed_soldiers().size() > military_site_->descr().get_max_number_of_soldiers()) {
119 		return military_site_->incorporate_upgraded_soldier(egbase, s) ? 0 : -1;
120 	}
121 
122 	if (!military_site_->didconquer_) {
123 		military_site_->conquer_area(egbase);
124 		// Building is now occupied - idle animation should be played
125 		military_site_->start_animation(
126 		   egbase, military_site_->descr().get_animation("idle", military_site_));
127 
128 		if (upcast(Game, game, &egbase)) {
129 			military_site_->send_message(
130 			   *game, Message::Type::kEconomySiteOccupied, military_site_->descr().descname(),
131 			   military_site_->descr().icon_filename(), military_site_->descr().descname(),
132 			   military_site_->descr().occupied_str_, true);
133 		}
134 	}
135 
136 	if (upcast(Game, game, &egbase)) {
137 		// Bind the worker into this house, hide him on the map
138 		s.reset_tasks(*game);
139 		s.start_task_buildingwork(*game);
140 	}
141 
142 	// Make sure the request count is reduced or the request is deleted.
143 	military_site_->update_soldier_request(true);
144 
145 	return 0;
146 }
147 
can_be_attacked() const148 bool MilitarySite::AttackTarget::can_be_attacked() const {
149 	return military_site_->didconquer_;
150 }
151 
enemy_soldier_approaches(const Soldier & enemy) const152 void MilitarySite::AttackTarget::enemy_soldier_approaches(const Soldier& enemy) const {
153 	Player* owner = military_site_->get_owner();
154 	Game& game = dynamic_cast<Game&>(owner->egbase());
155 	const Map& map = game.map();
156 	if (enemy.get_owner() == owner || enemy.get_battle() ||
157 	    military_site_->descr().get_conquers() <=
158 	       map.calc_distance(enemy.get_position(), military_site_->get_position()))
159 		return;
160 
161 	if (map.find_bobs(game,
162 	                  Area<FCoords>(map.get_fcoords(military_site_->base_flag().get_position()), 2),
163 	                  nullptr, FindBobEnemySoldier(owner)))
164 		return;
165 
166 	// We're dealing with a soldier that we might want to keep busy
167 	// Now would be the time to implement some player-definable
168 	// policy as to how many soldiers are allowed to leave as defenders
169 	std::vector<Soldier*> present = military_site_->soldier_control_.present_soldiers();
170 
171 	if (1 < present.size()) {
172 		for (Soldier* temp_soldier : present) {
173 			if (!military_site_->has_soldier_job(*temp_soldier)) {
174 				SoldierJob sj;
175 				sj.soldier = temp_soldier;
176 				sj.enemy = &enemy;
177 				sj.stayhome = false;
178 				military_site_->soldierjobs_.push_back(sj);
179 				temp_soldier->update_task_buildingwork(game);
180 				return;
181 			}
182 		}
183 	}
184 
185 	// Inform the player, that we are under attack by adding a new entry to the
186 	// message queue - a sound will automatically be played.
187 	military_site_->notify_player(game, true);
188 }
189 
attack(Soldier * enemy) const190 AttackTarget::AttackResult MilitarySite::AttackTarget::attack(Soldier* enemy) const {
191 	Game& game = dynamic_cast<Game&>(military_site_->get_owner()->egbase());
192 
193 	std::vector<Soldier*> present = military_site_->soldier_control_.present_soldiers();
194 	Soldier* defender = nullptr;
195 
196 	if (!present.empty()) {
197 		// Find soldier with greatest health
198 		uint32_t current_max = 0;
199 		for (Soldier* temp_soldier : present) {
200 			if (temp_soldier->get_current_health() > current_max) {
201 				defender = temp_soldier;
202 				current_max = defender->get_current_health();
203 			}
204 		}
205 	} else {
206 		// If one of our stationed soldiers is currently walking into the
207 		// building, give us another chance.
208 		std::vector<Soldier*> stationed = military_site_->soldier_control_.stationed_soldiers();
209 		for (Soldier* temp_soldier : stationed) {
210 			if (temp_soldier->get_position() == military_site_->get_position()) {
211 				defender = temp_soldier;
212 				break;
213 			}
214 		}
215 	}
216 
217 	if (defender) {
218 		military_site_->pop_soldier_job(defender);  // defense overrides all other jobs
219 
220 		SoldierJob sj;
221 		sj.soldier = defender;
222 		sj.enemy = enemy;
223 		sj.stayhome = true;
224 		military_site_->soldierjobs_.push_back(sj);
225 
226 		defender->update_task_buildingwork(game);
227 
228 		// Inform the player, that we are under attack by adding a new entry to
229 		// the message queue - a sound will automatically be played.
230 		military_site_->notify_player(game);
231 
232 		return AttackTarget::AttackResult::DefenderLaunched;
233 	}
234 
235 	// The enemy has defeated our forces, we should inform the player
236 	const Coords coords = military_site_->get_position();
237 	{
238 		military_site_->send_message(game, Message::Type::kWarfareSiteLost,
239 		                             /** TRANSLATORS: Militarysite lost (taken/destroyed by enemy) */
240 		                             pgettext("building", "Lost!"),
241 		                             military_site_->descr().icon_filename(), _("Militarysite lost!"),
242 		                             military_site_->descr().defeated_enemy_str_, false);
243 	}
244 
245 	// Now let's see whether the enemy conquers our militarysite, or whether
246 	// we still hold the bigger military presence in that area (e.g. if there
247 	// is a fortress one or two points away from our sentry, the fortress has
248 	// a higher presence and thus the enemy can just burn down the sentry.
249 	if (military_site_->military_presence_kept(game)) {
250 		// Okay we still got the higher military presence, so the attacked
251 		// militarysite will be destroyed.
252 		military_site_->set_defeating_player(enemy->owner().player_number());
253 		military_site_->schedule_destroy(game);
254 		return AttackTarget::AttackResult::Defenseless;
255 	}
256 
257 	// The enemy conquers the building
258 	// In fact we do not conquer it, but place a new building of same type at
259 	// the old location.
260 
261 	FormerBuildings former_buildings = military_site_->old_buildings_;
262 
263 	// The enemy conquers the building
264 	// In fact we do not conquer it, but place a new building of same type at
265 	// the old location.
266 	Player* enemyplayer = enemy->get_owner();
267 
268 	// Now we destroy the old building before we place the new one.
269 	// Waiting for the destroy playercommand causes crashes with the building window, so we need to
270 	// close it right away.
271 	military_site_->set_defeating_player(enemyplayer->player_number());
272 	military_site_->schedule_destroy(game);
273 
274 	Building* const newbuilding = &enemyplayer->force_building(coords, former_buildings);
275 	upcast(MilitarySite, newsite, newbuilding);
276 	newsite->reinit_after_conqueration(game);
277 
278 	// Of course we should inform the victorious player as well
279 	newsite->send_message(
280 	   game, Message::Type::kWarfareSiteDefeated,
281 	   /** TRANSLATORS: Message title. */
282 	   /** TRANSLATORS: If you run out of space, you can also translate this as "Success!" */
283 	   _("Enemy Defeated!"), newsite->descr().icon_filename(), _("Enemy at site defeated!"),
284 	   newsite->descr().defeated_you_str_, true);
285 
286 	return AttackTarget::AttackResult::Defenseless;
287 }
288 
289 /**
290  * The contents of 'table' are documented in
291  * /data/tribes/buildings/militarysites/atlanteans/castle/init.lua
292  */
MilitarySiteDescr(const std::string & init_descname,const LuaTable & table,const Tribes & tribes)293 MilitarySiteDescr::MilitarySiteDescr(const std::string& init_descname,
294                                      const LuaTable& table,
295                                      const Tribes& tribes)
296    : BuildingDescr(init_descname, MapObjectType::MILITARYSITE, table, tribes),
297      conquer_radius_(0),
298      num_soldiers_(0),
299      heal_per_second_(0) {
300 	i18n::Textdomain td("tribes");
301 
302 	conquer_radius_ = table.get_int("conquers");
303 	num_soldiers_ = table.get_int("max_soldiers");
304 	heal_per_second_ = table.get_int("heal_per_second");
305 
306 	if (conquer_radius_ > 0)
307 		workarea_info_[conquer_radius_].insert(name() + " conquer");
308 	prefers_heroes_at_start_ = table.get_bool("prefer_heroes");
309 
310 	std::unique_ptr<LuaTable> items_table = table.get_table("messages");
311 	occupied_str_ = _(items_table->get_string("occupied"));
312 	aggressor_str_ = _(items_table->get_string("aggressor"));
313 	attack_str_ = _(items_table->get_string("attack"));
314 	defeated_enemy_str_ = _(items_table->get_string("defeated_enemy"));
315 	defeated_you_str_ = _(items_table->get_string("defeated_you"));
316 }
317 
318 /**
319 ===============
320 Create a new building of this type
321 ===============
322 */
create_object() const323 Building& MilitarySiteDescr::create_object() const {
324 	return *new MilitarySite(*this);
325 }
326 
327 /*
328 =============================
329 
330 class MilitarySite
331 
332 =============================
333 */
334 
MilitarySite(const MilitarySiteDescr & ms_descr)335 MilitarySite::MilitarySite(const MilitarySiteDescr& ms_descr)
336    : Building(ms_descr),
337      attack_target_(this),
338      soldier_control_(this),
339      didconquer_(false),
340      capacity_(ms_descr.get_max_number_of_soldiers()),
341      nexthealtime_(0),
342      soldier_preference_(ms_descr.prefers_heroes_at_start_ ? SoldierPreference::kHeroes :
343                                                              SoldierPreference::kRookies),
344      soldier_upgrade_try_(false),
345      doing_upgrade_request_(false) {
346 	next_swap_soldiers_time_ = 0;
347 	set_attack_target(&attack_target_);
348 	set_soldier_control(&soldier_control_);
349 }
350 
~MilitarySite()351 MilitarySite::~MilitarySite() {
352 	assert(!normal_soldier_request_);
353 	assert(!upgrade_soldier_request_);
354 }
355 
356 /**
357 ===============
358 Display number of soldiers.
359 ===============
360 */
update_statistics_string(std::string * s)361 void MilitarySite::update_statistics_string(std::string* s) {
362 	s->clear();
363 	Quantity present = soldier_control_.present_soldiers().size();
364 	Quantity stationed = soldier_control_.stationed_soldiers().size();
365 
366 	if (present == stationed) {
367 		if (capacity_ > stationed) {
368 			/** TRANSLATORS: %1% is the number of soldiers the plural refers to */
369 			/** TRANSLATORS: %2% is the maximum number of soldier slots in the building */
370 			*s = (boost::format(ngettext("%1% soldier (+%2%)", "%1% soldiers (+%2%)", stationed)) %
371 			      stationed % (capacity_ - stationed))
372 			        .str();
373 		} else {
374 			/** TRANSLATORS: Number of soldiers stationed at a militarysite. */
375 			*s = (boost::format(ngettext("%u soldier", "%u soldiers", stationed)) % stationed).str();
376 		}
377 	} else {
378 		if (capacity_ > stationed) {
379 
380 			*s = (boost::format(
381 			         /** TRANSLATORS: %1% is the number of soldiers the plural refers to */
382 			         /** TRANSLATORS: %2% are currently open soldier slots in the building */
383 			         /** TRANSLATORS: %3% is the maximum number of soldier slots in the building */
384 			         ngettext("%1%(+%2%) soldier (+%3%)", "%1%(+%2%) soldiers (+%3%)", stationed)) %
385 			      present % (stationed - present) % (capacity_ - stationed))
386 			        .str();
387 		} else {
388 			/** TRANSLATORS: %1% is the number of soldiers the plural refers to */
389 			/** TRANSLATORS: %2% are currently open soldier slots in the building */
390 			*s = (boost::format(ngettext("%1%(+%2%) soldier", "%1%(+%2%) soldiers", stationed)) %
391 			      present % (stationed - present))
392 			        .str();
393 		}
394 	}
395 	*s = g_gr->styles().color_tag(
396 	   // Line break to make Codecheck happy.
397 	   *s, g_gr->styles().building_statistics_style().medium_color());
398 }
399 
init(EditorGameBase & egbase)400 bool MilitarySite::init(EditorGameBase& egbase) {
401 	Building::init(egbase);
402 
403 	upcast(Game, game, &egbase);
404 
405 	for (Worker* worker : get_workers()) {
406 		if (upcast(Soldier, soldier, worker)) {
407 			soldier->set_location_initially(*this);
408 			assert(!soldier->get_state());  //  Should be newly created.
409 			if (game)
410 				soldier->start_task_buildingwork(*game);
411 		}
412 	}
413 	update_soldier_request();
414 
415 	//  schedule the first healing
416 	nexthealtime_ = egbase.get_gametime() + 1000;
417 	if (game)
418 		schedule_act(*game, 1000);
419 	return true;
420 }
421 
422 /**
423 ===============
424 Change the economy for the wares queues.
425 Note that the workers are dealt with in the PlayerImmovable code.
426 ===============
427 */
set_economy(Economy * const e,WareWorker type)428 void MilitarySite::set_economy(Economy* const e, WareWorker type) {
429 	Building::set_economy(e, type);
430 
431 	if (normal_soldier_request_ && e && type == normal_soldier_request_->get_type())
432 		normal_soldier_request_->set_economy(e);
433 	if (upgrade_soldier_request_ && e && type == upgrade_soldier_request_->get_type())
434 		upgrade_soldier_request_->set_economy(e);
435 }
436 
437 /**
438 ===============
439 Cleanup after a military site is removed
440 ===============
441 */
cleanup(EditorGameBase & egbase)442 void MilitarySite::cleanup(EditorGameBase& egbase) {
443 	// unconquer land
444 	if (didconquer_)
445 		egbase.unconquer_area(
446 		   PlayerArea<Area<FCoords>>(
447 		      owner().player_number(),
448 		      Area<FCoords>(egbase.map().get_fcoords(get_position()), descr().get_conquers())),
449 		   defeating_player_);
450 
451 	Building::cleanup(egbase);
452 
453 	// Evict soldiers to get rid of requests
454 	while (capacity_ > 0) {
455 		update_soldier_request();
456 		--capacity_;
457 	}
458 	update_soldier_request();
459 
460 	normal_soldier_request_.reset();
461 	upgrade_soldier_request_.reset();
462 }
463 
464 /*
465  * Returns the least wanted soldier -- If player prefers zero-level guys,
466  * the most trained soldier is the "weakest guy".
467  */
468 
find_least_suited_soldier()469 Soldier* MilitarySite::find_least_suited_soldier() {
470 	const std::vector<Soldier*> present = soldier_control_.present_soldiers();
471 	const int32_t multiplier = SoldierPreference::kHeroes == soldier_preference_ ? -1 : 1;
472 	int worst_soldier_level = INT_MIN;
473 	Soldier* worst_soldier = nullptr;
474 	for (Soldier* sld : present) {
475 		int this_soldier_level =
476 		   multiplier * static_cast<int>(sld->get_level(TrainingAttribute::kTotal));
477 		if (this_soldier_level > worst_soldier_level) {
478 			worst_soldier_level = this_soldier_level;
479 			worst_soldier = sld;
480 		}
481 	}
482 	return worst_soldier;
483 }
484 
485 /*
486  * Kicks out the least wanted soldier -- If player prefers zero-level guys,
487  * the most trained soldier is the "weakest guy".
488  *
489  * Returns false, if there is only one soldier (won't drop last soldier)
490  * or the new guy is not better than the weakest one present, in which case
491  * nobody is dropped. If a new guy arrived and nobody was dropped, then
492  * caller of this should not allow the new guy to enter.
493  *
494  * Situations like the above may happen if, for example, when weakest guy
495  * that was supposed to be dropped is not present at the moment his replacement
496  * arrives.
497  */
498 
drop_least_suited_soldier(bool new_soldier_has_arrived,Soldier * newguy)499 bool MilitarySite::drop_least_suited_soldier(bool new_soldier_has_arrived, Soldier* newguy) {
500 	const std::vector<Soldier*> present = soldier_control_.present_soldiers();
501 
502 	// If I have only one soldier, and the  new guy is not here yet, I can't release.
503 	if (new_soldier_has_arrived || 1 < present.size()) {
504 		Soldier* kickoutCandidate = find_least_suited_soldier();
505 
506 		// If the arriving guy is worse than worst present, I wont't release.
507 		if (nullptr != newguy && nullptr != kickoutCandidate) {
508 			int32_t old_level = kickoutCandidate->get_level(TrainingAttribute::kTotal);
509 			int32_t new_level = newguy->get_level(TrainingAttribute::kTotal);
510 			if (SoldierPreference::kHeroes == soldier_preference_ && old_level >= new_level) {
511 				return false;
512 			} else if (SoldierPreference::kRookies == soldier_preference_ && old_level <= new_level) {
513 				return false;
514 			}
515 		}
516 
517 		// Now I know that the new guy is worthy.
518 		if (nullptr != kickoutCandidate) {
519 			Game& game = dynamic_cast<Game&>(get_owner()->egbase());
520 			kickoutCandidate->reset_tasks(game);
521 			kickoutCandidate->start_task_leavebuilding(game, true);
522 			return true;
523 		}
524 	}
525 	return false;
526 }
527 
528 /*
529  * This finds room for a soldier in an already full occupied military building.
530  *
531  * Returns false if the soldier was not incorporated.
532  */
incorporate_upgraded_soldier(EditorGameBase & egbase,Soldier & s)533 bool MilitarySite::incorporate_upgraded_soldier(EditorGameBase& egbase, Soldier& s) {
534 	// Call to drop_least routine has side effects: it tries to drop a soldier. Order is important!
535 	if (soldier_control_.stationed_soldiers().size() < capacity_ ||
536 	    drop_least_suited_soldier(true, &s)) {
537 		Game& game = dynamic_cast<Game&>(egbase);
538 		s.set_location(this);
539 		s.reset_tasks(game);
540 		s.start_task_buildingwork(game);
541 		return true;
542 	}
543 	return false;
544 }
545 
546 /*
547 ===============
548 Called when our soldier arrives.
549 ===============
550 */
request_soldier_callback(Game & game,Request &,DescriptionIndex,Worker * const w,PlayerImmovable & target)551 void MilitarySite::request_soldier_callback(
552    Game& game, Request&, DescriptionIndex, Worker* const w, PlayerImmovable& target) {
553 	MilitarySite& msite = dynamic_cast<MilitarySite&>(target);
554 	Soldier& s = dynamic_cast<Soldier&>(*w);
555 
556 	msite.soldier_control_.incorporate_soldier(game, s);
557 }
558 
559 /**
560  * Update the request for soldiers and cause soldiers to be evicted
561  * as appropriate.
562  */
update_normal_soldier_request()563 void MilitarySite::update_normal_soldier_request() {
564 	std::vector<Soldier*> present = soldier_control_.present_soldiers();
565 	Quantity const stationed = soldier_control_.stationed_soldiers().size();
566 
567 	if (stationed < capacity_) {
568 		if (!normal_soldier_request_) {
569 			normal_soldier_request_.reset(new Request(
570 			   *this, owner().tribe().soldier(), MilitarySite::request_soldier_callback, wwWORKER));
571 			normal_soldier_request_->set_requirements(soldier_requirements_);
572 		}
573 
574 		normal_soldier_request_->set_count(capacity_ - stationed);
575 	} else {
576 		normal_soldier_request_.reset();
577 	}
578 
579 	if (capacity_ < present.size()) {
580 		Game& game = dynamic_cast<Game&>(get_owner()->egbase());
581 		for (uint32_t i = 0; i < present.size() - capacity_; ++i) {
582 			Soldier& soldier = *present[i];
583 			soldier.reset_tasks(game);
584 			soldier.start_task_leavebuilding(game, true);
585 		}
586 	}
587 }
588 
589 /* There are two kinds of soldier requests: "normal", which is used whenever the military site needs
590  * more soldiers, and "upgrade" which is used when there is a preference for either heroes or
591  * rookies.
592  *
593  * In case of normal requests, the military site is filled. In case of upgrade requests, only one
594  * guy
595  * is exchanged at a time.
596  *
597  * There would be more efficient ways to get well trained soldiers. Now, new buildings appearing in
598  * battle
599  * field are more vulnerable at the beginning. This is intentional. The purpose of this upgrade
600  * thing is
601  * to reduce the benefits of site micromanagement. The intention is not to make gameplay easier in
602  * other ways.
603  */
update_upgrade_soldier_request()604 void MilitarySite::update_upgrade_soldier_request() {
605 	bool reqch = update_upgrade_requirements();
606 	if (!soldier_upgrade_try_)
607 		return;
608 
609 	bool do_rebuild_request = reqch;
610 
611 	if (upgrade_soldier_request_) {
612 		if (!upgrade_soldier_request_->is_open())
613 			// If a replacement is already walking this way, let's not change our minds.
614 			do_rebuild_request = false;
615 		if (0 == upgrade_soldier_request_->get_count())
616 			do_rebuild_request = true;
617 	} else
618 		do_rebuild_request = true;
619 
620 	if (do_rebuild_request) {
621 		upgrade_soldier_request_.reset(new Request(
622 		   *this, owner().tribe().soldier(), MilitarySite::request_soldier_callback, wwWORKER));
623 
624 		upgrade_soldier_request_->set_requirements(soldier_upgrade_requirements_);
625 		upgrade_soldier_request_->set_count(1);
626 	}
627 }
628 
629 /*
630  * I have update_soldier_request
631  *        update_upgrade_soldier_request
632  *        update_normal_soldier_request
633  *
634  * The first one handles state switching between site fill (normal more)
635  * and grabbing soldiers with proper training (upgrade mode). The last
636  * two actually make the requests.
637  *
638  * The input parameter incd is true, if we just incorporated a new soldier
639  * as a result of an upgrade request. In such cases, we will re-arm the
640  * upgrade request.
641  */
642 
update_soldier_request(bool incd)643 void MilitarySite::update_soldier_request(bool incd) {
644 	const uint32_t capacity = soldier_control_.soldier_capacity();
645 	const uint32_t stationed = soldier_control_.stationed_soldiers().size();
646 
647 	if (doing_upgrade_request_) {
648 		if (incd && upgrade_soldier_request_)  // update requests always ask for one soldier at time!
649 		{
650 			upgrade_soldier_request_.reset();
651 		}
652 		if (capacity > stationed) {
653 			// Somebody is killing my soldiers in the middle of upgrade
654 			// or I have kicked out his predecessor already.
655 			if (upgrade_soldier_request_ &&
656 			    (upgrade_soldier_request_->is_open() || 0 == upgrade_soldier_request_->get_count())) {
657 
658 				// Economy was not able to find the soldiers I need.
659 				// I can safely drop the upgrade request and go to fill mode.
660 				upgrade_soldier_request_.reset();
661 			}
662 			if (!upgrade_soldier_request_) {
663 				// phoo -- I can safely request new soldiers.
664 				doing_upgrade_request_ = false;
665 				update_normal_soldier_request();
666 			}
667 			// else -- ohno please help me! Player is in trouble -- evil grin
668 		} else                        // military site is full or overfull
669 		   if (capacity < stationed)  // player is reducing capacity
670 		{
671 			drop_least_suited_soldier(false, nullptr);
672 		} else  // capacity == stationed size
673 		{
674 			if (upgrade_soldier_request_ && (!(upgrade_soldier_request_->is_open())) &&
675 			    1 == upgrade_soldier_request_->get_count() && (!incd)) {
676 				drop_least_suited_soldier(false, nullptr);
677 			} else {
678 				update_upgrade_soldier_request();
679 			}
680 		}
681 	} else  // not doing upgrade request
682 	{
683 		if ((capacity != stationed) || (normal_soldier_request_))
684 			update_normal_soldier_request();
685 
686 		if ((capacity == stationed) && (!normal_soldier_request_)) {
687 			if (soldier_control_.present_soldiers().size() == capacity) {
688 				doing_upgrade_request_ = true;
689 				update_upgrade_soldier_request();
690 			}
691 			// Note -- if there are non-present stationed soldiers, nothing gets
692 			// called. Therefore, I revisit this routine periodically without apparent
693 			// reason, hoping that all stationed soldiers would be present.
694 		}
695 	}
696 }
697 
698 /*
699 ===============
700 Advance the program state if applicable.
701 ===============
702 */
act(Game & game,uint32_t const data)703 void MilitarySite::act(Game& game, uint32_t const data) {
704 	// TODO(unknown): do all kinds of stuff, but if you do nothing, let
705 	// Building::act() handle all this. Also note, that some Building
706 	// commands rely, that Building::act() is not called for a certain
707 	// period (like cmdAnimation). This should be reworked.
708 	// Maybe a new queueing system like MilitaryAct could be introduced.
709 
710 	Building::act(game, data);
711 
712 	const int32_t timeofgame = game.get_gametime();
713 	if (normal_soldier_request_ && upgrade_soldier_request_) {
714 		throw wexception(
715 		   "MilitarySite::act: Two soldier requests are ongoing -- should never happen!\n");
716 	}
717 
718 	// I do not get a callback when stationed, non-present soldier returns --
719 	// Therefore I must poll in some occasions. Let's do that rather infrequently,
720 	// to keep the game lightweight.
721 
722 	// TODO(unknown): I would need two new callbacks, to get rid ot this polling.
723 	if (timeofgame > next_swap_soldiers_time_) {
724 		next_swap_soldiers_time_ = timeofgame + (soldier_upgrade_try_ ? 20000 : 100000);
725 		update_soldier_request();
726 	}
727 
728 	// Heal soldiers
729 	if (nexthealtime_ <= timeofgame) {
730 		const uint32_t total_heal = descr().get_heal_per_second();
731 		uint32_t max_total_level = 0;
732 		float max_health = 0;
733 		Soldier* soldier_to_heal = nullptr;
734 
735 		for (Soldier* soldier : soldier_control_.stationed_soldiers()) {
736 			if (soldier->get_current_health() < soldier->get_max_health()) {
737 				if (is_present(*soldier)) {
738 					// The healing algorithm for present soldiers is:
739 					// * heal soldier with highest total level
740 					// * heal healthiest if multiple of same total level exist
741 					if (soldier_to_heal == nullptr || soldier->get_total_level() > max_total_level ||
742 					    (soldier->get_total_level() == max_total_level &&
743 					     soldier->get_current_health() / soldier->get_max_health() > max_health)) {
744 						max_total_level = soldier->get_total_level();
745 						max_health = soldier->get_current_health() / soldier->get_max_health();
746 						soldier_to_heal = soldier;
747 					}
748 				} else if ((soldier->get_battle() == nullptr ||
749 				            soldier->get_battle()->opponent(*soldier) == nullptr) &&
750 				           !get_economy(WareWorker::wwWORKER)->warehouses().empty()) {
751 					// Somewhat heal soldiers in the field that are not currently engaged in fighting an
752 					// opponent, but only if there is a warehouse connected.
753 					const PlayerNumber field_owner = soldier->get_position().field->get_owned_by();
754 					if (owner().player_number() == field_owner) {
755 						const unsigned int air_distance =
756 						   game.map().calc_distance(get_position(), soldier->get_position());
757 						const unsigned int heal_with_factor =
758 						   total_heal * descr().get_conquers() / std::max(air_distance * 4U, 1U);
759 						soldier->heal(std::min(total_heal, heal_with_factor));
760 					}
761 				}
762 			}
763 		}
764 
765 		if (soldier_to_heal != nullptr) {
766 			soldier_to_heal->heal(total_heal);
767 		}
768 
769 		nexthealtime_ = timeofgame + 1000;
770 		schedule_act(game, 1000);
771 	}
772 }
773 
774 /**
775  * The worker is about to be removed.
776  *
777  * After the removal of the worker, check whether we need to request
778  * new soldiers.
779  */
remove_worker(Worker & w)780 void MilitarySite::remove_worker(Worker& w) {
781 	Building::remove_worker(w);
782 
783 	if (upcast(Soldier, soldier, &w))
784 		pop_soldier_job(soldier, nullptr);
785 
786 	update_soldier_request();
787 }
788 
789 /**
790  * Called by soldiers in the building.
791  */
get_building_work(Game & game,Worker & worker,bool)792 bool MilitarySite::get_building_work(Game& game, Worker& worker, bool) {
793 	if (upcast(Soldier, soldier, &worker)) {
794 		// Evict soldiers that have returned home if the capacity is too low
795 		if (capacity_ < soldier_control_.present_soldiers().size()) {
796 			worker.reset_tasks(game);
797 			worker.start_task_leavebuilding(game, true);
798 			return true;
799 		}
800 
801 		bool stayhome;
802 		if (MapObject* const enemy = pop_soldier_job(soldier, &stayhome)) {
803 			if (upcast(Building, building, enemy)) {
804 				soldier->start_task_attack(game, *building);
805 				return true;
806 			} else if (upcast(Soldier, opponent, enemy)) {
807 				if (!opponent->get_battle()) {
808 					soldier->start_task_defense(game, stayhome);
809 					if (stayhome)
810 						opponent->send_signal(game, "sleep");
811 					return true;
812 				}
813 			} else
814 				throw wexception("MilitarySite::get_building_work: bad SoldierJob");
815 		}
816 	}
817 
818 	return false;
819 }
820 
821 /**
822  * \return \c true if the soldier is currently present and idle in the building.
823  */
is_present(Soldier & soldier) const824 bool MilitarySite::is_present(Soldier& soldier) const {
825 	return soldier.get_location(get_owner()->egbase()) == this &&
826 	       soldier.get_state() == soldier.get_state(Worker::taskBuildingwork) &&
827 	       soldier.get_position() == get_position();
828 }
829 
conquer_area(EditorGameBase & egbase)830 void MilitarySite::conquer_area(EditorGameBase& egbase) {
831 	assert(!didconquer_);
832 	egbase.conquer_area(PlayerArea<Area<FCoords>>(
833 	   owner().player_number(),
834 	   Area<FCoords>(egbase.map().get_fcoords(get_position()), descr().get_conquers())));
835 	didconquer_ = true;
836 }
837 
838 /// Initialises the militarysite after it was "conquered" (the old was replaced)
reinit_after_conqueration(Game & game)839 void MilitarySite::reinit_after_conqueration(Game& game) {
840 	clear_requirements();
841 	conquer_area(game);
842 	update_soldier_request();
843 	start_animation(game, descr().get_animation("idle", this));
844 
845 	// feature request 1247384 in launchpad bugs: Conquered buildings tend to
846 	// be in a hostile area; typically players want heroes there.
847 	set_soldier_preference(SoldierPreference::kHeroes);
848 }
849 
850 /// Calculates whether the military presence is still kept and \returns true if.
military_presence_kept(Game & game)851 bool MilitarySite::military_presence_kept(Game& game) {
852 	// collect information about immovables in the area
853 	std::vector<ImmovableFound> immovables;
854 
855 	// Search in a radius of 3 (needed for big militarysites)
856 	FCoords const fc = game.map().get_fcoords(get_position());
857 	game.map().find_immovables(game, Area<FCoords>(fc, 3), &immovables);
858 
859 	for (uint32_t i = 0; i < immovables.size(); ++i)
860 		if (upcast(MilitarySite const, militarysite, immovables[i].object))
861 			if (this != militarysite && &owner() == &militarysite->owner() &&
862 			    get_size() <= militarysite->get_size() && militarysite->didconquer_)
863 				return true;
864 	return false;
865 }
866 
867 /// Informs the player about an attack of his opponent.
notify_player(Game & game,bool const discovered)868 void MilitarySite::notify_player(Game& game, bool const discovered) {
869 	// Add a message as long as no previous message was send from a point with
870 	// radius <= 5 near the current location in the last 60 seconds
871 	send_message(game, Message::Type::kWarfareUnderAttack,
872 	             /** TRANSLATORS: Militarysite is being attacked */
873 	             pgettext("building", "Attack!"), descr().icon_filename(), _("You are under attack"),
874 	             discovered ? descr().aggressor_str_ : descr().attack_str_, false, 60 * 1000, 5);
875 }
876 
877 /*
878    MilitarySite::set_requirements
879 
880    Easy to use, overwrite with given requirements.
881 */
set_requirements(const Requirements & r)882 void MilitarySite::set_requirements(const Requirements& r) {
883 	soldier_requirements_ = r;
884 }
885 
886 /*
887    MilitarySite::clear_requirements
888 
889    This should cancel any requirement pushed at this house
890 */
clear_requirements()891 void MilitarySite::clear_requirements() {
892 	soldier_requirements_ = Requirements();
893 }
894 
send_attacker(Soldier & soldier,Building & target)895 void MilitarySite::send_attacker(Soldier& soldier, Building& target) {
896 	if (!is_present(soldier)) {
897 		// The soldier may not be present anymore due to having been kicked out. Most of the time
898 		// the function calling us will notice this, but there are cornercase where it might not,
899 		// e.g. when a soldier was ordered to leave but did not physically quit the building yet.
900 		log("MilitarySite(%3dx%3d)::send_attacker: Not sending soldier %u because he left the "
901 		    "building\n",
902 		    get_position().x, get_position().y, soldier.serial());
903 		return;
904 	}
905 
906 	if (has_soldier_job(soldier)) {
907 		return;
908 	}
909 
910 	SoldierJob sj;
911 	sj.soldier = &soldier;
912 	sj.enemy = &target;
913 	sj.stayhome = false;
914 	soldierjobs_.push_back(sj);
915 
916 	soldier.update_task_buildingwork(dynamic_cast<Game&>(get_owner()->egbase()));
917 }
918 
has_soldier_job(Soldier & soldier)919 bool MilitarySite::has_soldier_job(Soldier& soldier) {
920 	for (const SoldierJob& temp_job : soldierjobs_) {
921 		if (temp_job.soldier == &soldier) {
922 			return true;
923 		}
924 	}
925 	return false;
926 }
927 
928 /**
929  * \return the enemy, if any, that the given soldier was scheduled
930  * to attack, and remove the job.
931  */
pop_soldier_job(Soldier * const soldier,bool * const stayhome)932 MapObject* MilitarySite::pop_soldier_job(Soldier* const soldier, bool* const stayhome) {
933 	for (std::vector<SoldierJob>::iterator job_iter = soldierjobs_.begin();
934 	     job_iter != soldierjobs_.end(); ++job_iter) {
935 		if (job_iter->soldier == soldier) {
936 			MapObject* const enemy = job_iter->enemy.get(owner().egbase());
937 			if (stayhome)
938 				*stayhome = job_iter->stayhome;
939 			soldierjobs_.erase(job_iter);
940 			return enemy;
941 		}
942 	}
943 	return nullptr;
944 }
945 
946 /*
947  * When upgrading soldiers, we do not ask for just any soldiers, but soldiers
948  * that are better than what we already have. This routine sets the requirements
949  * used by the request.
950  *
951  * The routine returns true if upgrade request thresholds have changed. This information could be
952  * used to decide whether the soldier-Request should be upgraded.
953  */
update_upgrade_requirements()954 bool MilitarySite::update_upgrade_requirements() {
955 	int32_t soldier_upgrade_required_min = soldier_upgrade_requirements_.get_min();
956 	int32_t soldier_upgrade_required_max = soldier_upgrade_requirements_.get_max();
957 
958 	// Find the level of the soldier that is currently least-suited.
959 	Soldier* worst_guy = find_least_suited_soldier();
960 	if (worst_guy == nullptr) {
961 		// There could be no soldier in the militarysite right now. No reason to freak out.
962 		return false;
963 	}
964 	int32_t wg_level = worst_guy->get_level(TrainingAttribute::kTotal);
965 
966 	// Micro-optimization: I assume that the majority of military sites have only level-zero
967 	// soldiers and prefer rookies. Handle them separately.
968 	soldier_upgrade_try_ = true;
969 	if (SoldierPreference::kRookies == soldier_preference_) {
970 		if (0 == wg_level) {
971 			soldier_upgrade_try_ = false;
972 			return false;
973 		}
974 	}
975 
976 	// Now I actually build the new requirements.
977 	int32_t reqmin = SoldierPreference::kHeroes == soldier_preference_ ? 1 + wg_level : 0;
978 	int32_t reqmax = SoldierPreference::kHeroes == soldier_preference_ ? SHRT_MAX : wg_level - 1;
979 
980 	bool maxchanged = reqmax != soldier_upgrade_required_max;
981 	bool minchanged = reqmin != soldier_upgrade_required_min;
982 
983 	if (maxchanged || minchanged) {
984 		if (upgrade_soldier_request_ && (upgrade_soldier_request_->is_open())) {
985 			upgrade_soldier_request_.reset();
986 		}
987 		soldier_upgrade_requirements_ = RequireAttribute(TrainingAttribute::kTotal, reqmin, reqmax);
988 
989 		return true;
990 	}
991 
992 	return false;
993 }
994 
create_building_settings() const995 const BuildingSettings* MilitarySite::create_building_settings() const {
996 	MilitarysiteSettings* settings = new MilitarysiteSettings(descr(), owner().tribe());
997 	settings->desired_capacity =
998 	   std::min(settings->max_capacity, soldier_control_.soldier_capacity());
999 	settings->prefer_heroes = soldier_preference_ == SoldierPreference::kHeroes;
1000 	return settings;
1001 }
1002 
1003 // setters
1004 
set_soldier_preference(SoldierPreference p)1005 void MilitarySite::set_soldier_preference(SoldierPreference p) {
1006 	soldier_preference_ = p;
1007 	next_swap_soldiers_time_ = 0;
1008 }
1009 }  // namespace Widelands
1010