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 = ⌖
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