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/trainingsite.h"
21
22 #include <memory>
23
24 #include "base/i18n.h"
25 #include "base/macros.h"
26 #include "base/wexception.h"
27 #include "economy/request.h"
28 #include "logic/editor_game_base.h"
29 #include "logic/game.h"
30 #include "logic/map_objects/tribes/production_program.h"
31 #include "logic/map_objects/tribes/soldier.h"
32 #include "logic/map_objects/tribes/tribe_descr.h"
33 #include "logic/map_objects/tribes/worker.h"
34 #include "logic/player.h"
35
36 namespace Widelands {
37
38 const uint32_t TrainingSite::training_state_multiplier_ = 12;
39
40 /**
41 * The contents of 'table' are documented in
42 * /data/tribes/buildings/trainingsites/atlanteans/dungeon/init.lua
43 */
TrainingSiteDescr(const std::string & init_descname,const std::string & msgctxt,const LuaTable & table,const Tribes & tribes,const World & world)44 TrainingSiteDescr::TrainingSiteDescr(const std::string& init_descname,
45 const std::string& msgctxt,
46 const LuaTable& table,
47 const Tribes& tribes,
48 const World& world)
49 : ProductionSiteDescr(init_descname, msgctxt, MapObjectType::TRAININGSITE, table, tribes, world),
50 num_soldiers_(table.get_int("soldier_capacity")),
51 max_stall_(table.get_int("trainer_patience")),
52
53 train_health_(false),
54 train_attack_(false),
55 train_defense_(false),
56 train_evade_(false),
57 min_health_(0),
58 min_attack_(0),
59 min_defense_(0),
60 min_evade_(0),
61 max_health_(0),
62 max_attack_(0),
63 max_defense_(0),
64 max_evade_(0) {
65 // Read the range of levels that can update this building
66 // TODO(unknown): This is currently hardcoded to "soldier" but it should search for
67 // sections starting with the name of each soldier type.
68 // These sections also seem redundant. Eliminate them (having the
69 // programs should be enough).
70 std::unique_ptr<LuaTable> items_table;
71 if (table.has_key("soldier health")) {
72 items_table = table.get_table("soldier health");
73 train_health_ = true;
74 min_health_ = items_table->get_int("min_level");
75 max_health_ = items_table->get_int("max_level");
76 add_training_inputs(*items_table, &food_health_, &weapons_health_);
77 }
78
79 if (table.has_key("soldier attack")) {
80 items_table = table.get_table("soldier attack");
81 train_attack_ = true;
82 min_attack_ = items_table->get_int("min_level");
83 max_attack_ = items_table->get_int("max_level");
84 add_training_inputs(*items_table, &food_attack_, &weapons_attack_);
85 }
86 if (table.has_key("soldier defense")) {
87 items_table = table.get_table("soldier defense");
88 train_defense_ = true;
89 min_defense_ = items_table->get_int("min_level");
90 max_defense_ = items_table->get_int("max_level");
91 add_training_inputs(*items_table, &food_defense_, &weapons_defense_);
92 }
93 if (table.has_key("soldier evade")) {
94 items_table = table.get_table("soldier evade");
95 train_evade_ = true;
96 min_evade_ = items_table->get_int("min_level");
97 max_evade_ = items_table->get_int("max_level");
98 add_training_inputs(*items_table, &food_evade_, &weapons_evade_);
99 }
100 }
101
102 /**
103 * Create a new training site
104 * \return the new training site
105 */
create_object() const106 Building& TrainingSiteDescr::create_object() const {
107 return *new TrainingSite(*this);
108 }
109
110 /**
111 * \param at the attribute to investigate
112 * \return the minimum level to which this building can downgrade a
113 * specified attribute
114 */
get_min_level(const TrainingAttribute at) const115 int32_t TrainingSiteDescr::get_min_level(const TrainingAttribute at) const {
116 switch (at) {
117 case TrainingAttribute::kHealth:
118 return min_health_;
119 case TrainingAttribute::kAttack:
120 return min_attack_;
121 case TrainingAttribute::kDefense:
122 return min_defense_;
123 case TrainingAttribute::kEvade:
124 return min_evade_;
125 case TrainingAttribute::kTotal:
126 throw wexception("Unknown attribute value!");
127 }
128 NEVER_HERE();
129 }
130
131 /**
132 * Returns the maximum level to which this building can upgrade a
133 * specified attribute
134 * \param at the attribute to investigate
135 * \return the maximum level to be attained at this site
136 */
get_max_level(const TrainingAttribute at) const137 int32_t TrainingSiteDescr::get_max_level(const TrainingAttribute at) const {
138 switch (at) {
139 case TrainingAttribute::kHealth:
140 return max_health_;
141 case TrainingAttribute::kAttack:
142 return max_attack_;
143 case TrainingAttribute::kDefense:
144 return max_defense_;
145 case TrainingAttribute::kEvade:
146 return max_evade_;
147 case TrainingAttribute::kTotal:
148 throw wexception("Unknown attribute value!");
149 }
150 NEVER_HERE();
151 }
152
153 /**
154 * Return the maximum level that can be trained, both by school type
155 * and resourcing.
156 */
get_max_unstall_level(const TrainingAttribute at,const TrainingSiteDescr & tsd) const157 int32_t TrainingSite::get_max_unstall_level(const TrainingAttribute at,
158 const TrainingSiteDescr& tsd) const {
159 const int32_t max = tsd.get_max_level(at);
160 const int32_t min = tsd.get_min_level(at);
161 int32_t lev = min;
162 int32_t rtv = min;
163 while (lev < max) {
164 TypeAndLevel train_tl(at, ++lev);
165 TrainFailCount::const_iterator tstep = training_failure_count_.find(train_tl);
166 if (max_stall_val_ > tstep->second.first) {
167 rtv = lev;
168 } else {
169 lev = max;
170 }
171 }
172
173 return rtv;
174 }
175
get_max_stall() const176 int32_t TrainingSiteDescr::get_max_stall() const {
177 return max_stall_;
178 }
179
add_training_inputs(const LuaTable & table,std::vector<std::vector<std::string>> * food,std::vector<std::string> * weapons)180 void TrainingSiteDescr::add_training_inputs(const LuaTable& table,
181 std::vector<std::vector<std::string>>* food,
182 std::vector<std::string>* weapons) {
183
184 if (table.has_key("food")) {
185 std::unique_ptr<LuaTable> food_table = table.get_table("food");
186 for (const int key : food_table->keys<int>()) {
187 std::vector<std::string> food_vector;
188 for (const std::string& food_item :
189 food_table->get_table(key)->array_entries<std::string>()) {
190 food_vector.push_back(food_item);
191 }
192 food->push_back(food_vector);
193 }
194 }
195 if (table.has_key("weapons")) {
196 for (const std::string& weapon : table.get_table("weapons")->array_entries<std::string>()) {
197 weapons->push_back(weapon);
198 }
199 }
200 }
201
202 // TODO(sirver): This SoldierControl looks very similar to te one in
203 // MilitarySite. Pull out a class to reuse code.
present_soldiers() const204 std::vector<Soldier*> TrainingSite::SoldierControl::present_soldiers() const {
205 return training_site_->soldiers_;
206 }
207
stationed_soldiers() const208 std::vector<Soldier*> TrainingSite::SoldierControl::stationed_soldiers() const {
209 return training_site_->soldiers_;
210 }
211
min_soldier_capacity() const212 Quantity TrainingSite::SoldierControl::min_soldier_capacity() const {
213 return 0;
214 }
max_soldier_capacity() const215 Quantity TrainingSite::SoldierControl::max_soldier_capacity() const {
216 return training_site_->descr().get_max_number_of_soldiers();
217 }
soldier_capacity() const218 Quantity TrainingSite::SoldierControl::soldier_capacity() const {
219 return training_site_->capacity_;
220 }
221
set_soldier_capacity(Quantity const capacity)222 void TrainingSite::SoldierControl::set_soldier_capacity(Quantity const capacity) {
223 assert(min_soldier_capacity() <= capacity);
224 assert(capacity <= max_soldier_capacity());
225 assert(training_site_->capacity_ != capacity);
226 // Said in github issue #3869 discussion:
227 //
228 // > the problem will always be if the capacity of a training site will be
229 // > increased AND you don't see soldiers leaving the warehouse while you
230 // > know they are there you will be confused. So we should keep this very
231 // > short anything more then 5 sec will cause confusion I believe.
232 //
233 // This piece implements this demand. If we add more control buttons to the
234 // UI later, side-effects like this could go away.
235 if (capacity > training_site_->capacity_) {
236 // This is the capacity increased part from above.
237 // Splitting a bit futher.
238 if (0 == training_site_->capacity_ && 1 == capacity) {
239 // If the site had a capacity of zero, then the player probably micromanages
240 // and wants a partially trained soldier, if available. Resetting the state.
241 training_site_->repeated_layoff_ctr_ = 0;
242 training_site_->latest_trainee_was_kickout_ = false;
243 } else {
244 // Now the player just wants soldier. Any soldiers.
245 training_site_->recent_capacity_increase_ = true;
246 }
247 }
248 training_site_->capacity_ = capacity;
249 training_site_->update_soldier_request(false);
250 }
251
252 /**
253 * Drop a given soldier.
254 *
255 * 'Dropping' means releasing the soldier from the site. The soldier then
256 * becomes available to the economy.
257 *
258 * \note This is called from player commands, so we need to verify that the
259 * soldier is actually stationed here, without breaking anything if he isn't.
260 */
drop_soldier(Soldier & soldier)261 void TrainingSite::SoldierControl::drop_soldier(Soldier& soldier) {
262 Game& game = dynamic_cast<Game&>(training_site_->get_owner()->egbase());
263
264 std::vector<Soldier*>::iterator it =
265 std::find(training_site_->soldiers_.begin(), training_site_->soldiers_.end(), &soldier);
266 if (it == training_site_->soldiers_.end()) {
267 training_site_->molog(
268 "TrainingSite::SoldierControl::drop_soldier: soldier not in training site");
269 return;
270 }
271
272 training_site_->soldiers_.erase(it);
273
274 soldier.reset_tasks(game);
275 soldier.start_task_leavebuilding(game, true);
276
277 // Schedule, so that we can call new soldiers on next act()
278 training_site_->schedule_act(game, 100);
279 Notifications::publish(
280 NoteTrainingSiteSoldierTrained(training_site_, training_site_->get_owner()));
281 }
282
incorporate_soldier(EditorGameBase & egbase,Soldier & s)283 int TrainingSite::SoldierControl::incorporate_soldier(EditorGameBase& egbase, Soldier& s) {
284 if (s.get_location(egbase) != training_site_) {
285 if (stationed_soldiers().size() + 1 > training_site_->descr().get_max_number_of_soldiers())
286 return -1;
287
288 s.set_location(training_site_);
289 }
290
291 // Bind the worker into this house, hide him on the map
292 if (upcast(Game, game, &egbase))
293 s.start_task_idle(*game, 0, -1);
294
295 // Make sure the request count is reduced or the request is deleted.
296
297 training_site_->update_soldier_request(true);
298
299 return 0;
300 }
301
302 /*
303 =============================
304
305 class TrainingSite
306
307 =============================
308 */
309
TrainingSite(const TrainingSiteDescr & d)310 TrainingSite::TrainingSite(const TrainingSiteDescr& d)
311 : ProductionSite(d),
312 soldier_control_(this),
313 soldier_request_(nullptr),
314 capacity_(descr().get_max_number_of_soldiers()),
315 build_heroes_(false),
316 result_(ProgramResult::kFailed) {
317 set_soldier_control(&soldier_control_);
318
319 // Initialize this in the constructor so that loading code may
320 // overwrite priorities.
321 calc_upgrades();
322 current_upgrade_ = nullptr;
323 set_post_timer(6000);
324 training_failure_count_.clear();
325 max_stall_val_ = training_state_multiplier_ * d.get_max_stall();
326 highest_trainee_level_seen_ = 1;
327 latest_trainee_kickout_level_ = 1;
328 latest_trainee_was_kickout_ = false;
329 requesting_weak_trainees_ = false;
330 request_open_since_ = 0;
331 trainee_general_lower_bound_ = 2;
332 repeated_layoff_ctr_ = 0;
333 repeated_layoff_inc_ = false;
334 recent_capacity_increase_ = false;
335
336 if (d.get_train_health())
337 init_kick_state(TrainingAttribute::kHealth, d);
338 if (d.get_train_attack())
339 init_kick_state(TrainingAttribute::kAttack, d);
340 if (d.get_train_defense())
341 init_kick_state(TrainingAttribute::kDefense, d);
342 if (d.get_train_evade())
343 init_kick_state(TrainingAttribute::kEvade, d);
344 }
init_kick_state(const TrainingAttribute & art,const TrainingSiteDescr & d)345 void TrainingSite::init_kick_state(const TrainingAttribute& art, const TrainingSiteDescr& d) {
346 // Now with kick-out state saving implemented, initializing is an overkill
347 for (int t = d.get_min_level(art); t <= d.get_max_level(art); t++)
348 training_attempted(art, t);
349 }
350
351 /**
352 * Setup the building and request soldiers
353 */
init(EditorGameBase & egbase)354 bool TrainingSite::init(EditorGameBase& egbase) {
355 ProductionSite::init(egbase);
356
357 upcast(Game, game, &egbase);
358
359 for (Soldier* soldier : soldiers_) {
360 soldier->set_location_initially(*this);
361 assert(!soldier->get_state()); // Should be newly created.
362
363 if (game) {
364 soldier->start_task_idle(*game, 0, -1);
365 }
366 }
367 update_soldier_request(false);
368 return true;
369 }
370
371 /**
372 * Change the economy this site belongs to.
373 * \par e The new economy. Can be 0 (unconnected buildings have no economy).
374 * \note the worker (but not the soldiers) is dealt with in the
375 * PlayerImmovable code.
376 */
set_economy(Economy * e,WareWorker type)377 void TrainingSite::set_economy(Economy* e, WareWorker type) {
378 ProductionSite::set_economy(e, type);
379
380 if (soldier_request_ && type == soldier_request_->get_type())
381 soldier_request_->set_economy(e);
382 }
383
384 /**
385 * Cleanup after a Training site is removed
386 *
387 * Cancel all soldier requests and release all soldiers
388 */
cleanup(EditorGameBase & egbase)389 void TrainingSite::cleanup(EditorGameBase& egbase) {
390 delete soldier_request_;
391 soldier_request_ = nullptr;
392
393 ProductionSite::cleanup(egbase);
394 }
395
add_worker(Worker & w)396 void TrainingSite::add_worker(Worker& w) {
397 ProductionSite::add_worker(w);
398
399 if (upcast(Soldier, soldier, &w)) {
400 // Note that the given Soldier might already be in the array
401 // for loadgames.
402 if (std::find(soldiers_.begin(), soldiers_.end(), soldier) == soldiers_.end())
403 soldiers_.push_back(soldier);
404
405 if (upcast(Game, game, &get_owner()->egbase()))
406 schedule_act(*game, 100);
407 }
408 }
409
remove_worker(Worker & w)410 void TrainingSite::remove_worker(Worker& w) {
411 upcast(Game, game, &get_owner()->egbase());
412
413 if (upcast(Soldier, soldier, &w)) {
414 std::vector<Soldier*>::iterator const it =
415 std::find(soldiers_.begin(), soldiers_.end(), soldier);
416 if (it != soldiers_.end()) {
417 soldiers_.erase(it);
418
419 if (game)
420 schedule_act(*game, 100);
421 }
422 }
423
424 ProductionSite::remove_worker(w);
425 }
426
427 /**
428 * Request soldiers up to capacity, or let go of surplus soldiers.
429 *
430 * Now, we attempt to intelligently select most suitable soldiers
431 * (either already somwhat trained, or if training stalls, less
432 * trained ones). If no luck, the criteria is made relaxed until
433 * somebody shows up.
434 */
update_soldier_request(bool did_incorporate)435 void TrainingSite::update_soldier_request(bool did_incorporate) {
436 Game* game = get_owner() ? dynamic_cast<Game*>(&(get_owner()->egbase())) : nullptr;
437 bool rebuild_request = false;
438 bool need_more_soldiers = false;
439 uint32_t dynamic_timeout = acceptance_threshold_timeout;
440 uint8_t trainee_general_upper_bound = std::numeric_limits<uint8_t>::max() - 1;
441 bool limit_upper_bound = false;
442
443 if (soldiers_.size() < capacity_) {
444 // If not full, I need more soldiers.
445 need_more_soldiers = true;
446 }
447
448 // Usually, we prefer already partially trained soldiers here.
449 // In some conditions, this can lead to same soldiers walking back and forth.
450 // this tries to break that cycle. The goal is that this code only kicks in
451 // in those specific conditions. This if statement is true if we repeatedly
452 // incorporate and release soldiers, without training them at all.
453 if (kUpperBoundThreshold_ < repeated_layoff_ctr_) {
454 if (repeated_layoff_ctr_ > kUpperBoundThreshold_ + highest_trainee_level_seen_) {
455 repeated_layoff_ctr_ = 0;
456 } else {
457 trainee_general_upper_bound =
458 kUpperBoundThreshold_ + highest_trainee_level_seen_ - repeated_layoff_ctr_;
459 limit_upper_bound = true;
460 }
461 if (did_incorporate) {
462 rebuild_request = need_more_soldiers;
463 }
464 }
465 // This boolean ensures that kicking out many soldiers in a row does not count as
466 // soldiers entering and leaving without training. We need to repeatedly incorporate
467 // and release for the last resort to kick in. I need this boolean, to detect that
468 // a soldier was incorporated between soldiers leaving.
469 if (did_incorporate) {
470 repeated_layoff_inc_ = true;
471 }
472
473 const uint32_t timeofgame = game ? game->get_gametime() : 0;
474
475 if (did_incorporate && latest_trainee_was_kickout_ != requesting_weak_trainees_) {
476 // If type of desired recruits has been changed, the request is rebuild after incorporate
477 // even if (wrong/old) type recruits are on the way.
478 rebuild_request = need_more_soldiers;
479 requesting_weak_trainees_ = latest_trainee_was_kickout_;
480 }
481
482 if (did_incorporate) {
483 // If we got somebody in, lets become picky again.
484 // Request is not regenerated at this point. Should it?
485 if (requesting_weak_trainees_) {
486 trainee_general_lower_bound_ = latest_trainee_kickout_level_;
487 } else {
488 trainee_general_lower_bound_ = static_cast<uint8_t>(std::max<unsigned>(
489 1, (std::min<unsigned>(highest_trainee_level_seen_,
490 (static_cast<unsigned>(trainee_general_lower_bound_) + 1 +
491 static_cast<unsigned>(highest_trainee_level_seen_)) /
492 2))));
493 }
494 request_open_since_ = timeofgame;
495 }
496 if (soldier_request_ && need_more_soldiers) {
497 if ((!requesting_weak_trainees_) && (!limit_upper_bound)) {
498 // If requesting strong folks, the acceptance time can sometimes grow unbearable large
499 // without this.
500 // In request weak mode, resources are typically thin and this harms less, In addition,
501 // the starting value tends to be much smaller in request-weak mode.
502 dynamic_timeout =
503 acceptance_threshold_timeout /
504 std::max<uint32_t>(1, static_cast<unsigned>(trainee_general_lower_bound_));
505 // In the special case of training not working at all, there is no need for this speedup
506 // (hence the 2nd check)
507 }
508 if (0 == soldier_request_->get_num_transfers() &&
509 timeofgame > request_open_since_ + dynamic_timeout) {
510 // Timeout: We have been asking for certain type of soldiers, nobody is answering the call.
511 // Relaxing the criteria (and thus rebuild the request)
512 rebuild_request = need_more_soldiers;
513 if (0 < trainee_general_lower_bound_) {
514 trainee_general_lower_bound_--;
515 dynamic_timeout =
516 acceptance_threshold_timeout /
517 std::max<uint32_t>(1, static_cast<unsigned>(trainee_general_lower_bound_));
518 } else if (requesting_weak_trainees_) {
519 // If requesting weak trainees, and no people show up:
520 // set the state back to request_strong, which will allow everybody in
521 // when threshold is zero. Hopefully, you are fine with this misuse
522 // of variable names.
523 requesting_weak_trainees_ = false;
524 latest_trainee_was_kickout_ = false;
525 }
526 if (kUpperBoundThreshold_ <= repeated_layoff_ctr_ && soldiers_.empty()) {
527 // Repeated layoff ctr breaks the cycle when same few soldiers pendle back and forth.
528 // If no soldiers are arriving and none are present, this cannot be the case.
529 // Trainingsites without soldiers for long confuse players, thus retracting.
530 repeated_layoff_ctr_ = 0;
531 requesting_weak_trainees_ = false;
532 latest_trainee_was_kickout_ = false;
533 }
534 }
535 }
536
537 if (!soldier_request_) {
538 rebuild_request = need_more_soldiers;
539 }
540
541 if (rebuild_request) {
542 // I've changed my acceptance criteria
543 if (soldier_request_) {
544 delete soldier_request_;
545 soldier_request_ = nullptr;
546 }
547
548 assert(need_more_soldiers);
549 if (recent_capacity_increase_) {
550 // See comments in TrainingSite::SoldierControl::set_soldier_capacity() for details
551 // In short: If user interacts, I accept anybody regardless of state.
552 requesting_weak_trainees_ = false;
553 limit_upper_bound = false;
554 trainee_general_lower_bound_ = 0;
555 recent_capacity_increase_ = false;
556 }
557
558 soldier_request_ = new Request(
559 *this, owner().tribe().soldier(), TrainingSite::request_soldier_callback, wwWORKER);
560
561 RequireOr r;
562
563 // set requirements to match this site
564 if (descr().get_train_attack()) {
565 // In "request weak trainees" mode, we ask for soldiers that are below stalled level
566 if (requesting_weak_trainees_) {
567 r.add(RequireAttribute(TrainingAttribute::kAttack,
568 descr().get_min_level(TrainingAttribute::kAttack),
569 get_max_unstall_level(TrainingAttribute::kAttack, descr())));
570 } else {
571 r.add(RequireAttribute(TrainingAttribute::kAttack,
572 descr().get_min_level(TrainingAttribute::kAttack),
573 descr().get_max_level(TrainingAttribute::kAttack)));
574 }
575 }
576 if (descr().get_train_defense()) {
577 if (requesting_weak_trainees_) {
578 r.add(RequireAttribute(TrainingAttribute::kDefense,
579 descr().get_min_level(TrainingAttribute::kDefense),
580 get_max_unstall_level(TrainingAttribute::kDefense, descr())));
581 } else {
582 r.add(RequireAttribute(TrainingAttribute::kDefense,
583 descr().get_min_level(TrainingAttribute::kDefense),
584 descr().get_max_level(TrainingAttribute::kDefense)));
585 }
586 }
587 if (descr().get_train_evade()) {
588 if (requesting_weak_trainees_) {
589 r.add(RequireAttribute(TrainingAttribute::kEvade,
590 descr().get_min_level(TrainingAttribute::kEvade),
591 get_max_unstall_level(TrainingAttribute::kEvade, descr())));
592 } else {
593 r.add(RequireAttribute(TrainingAttribute::kEvade,
594 descr().get_min_level(TrainingAttribute::kEvade),
595 descr().get_max_level(TrainingAttribute::kEvade)));
596 }
597 }
598 if (descr().get_train_health()) {
599 if (requesting_weak_trainees_) {
600 r.add(RequireAttribute(TrainingAttribute::kHealth,
601 descr().get_min_level(TrainingAttribute::kHealth),
602 get_max_unstall_level(TrainingAttribute::kHealth, descr())));
603 } else {
604 r.add(RequireAttribute(TrainingAttribute::kHealth,
605 descr().get_min_level(TrainingAttribute::kHealth),
606 descr().get_max_level(TrainingAttribute::kHealth)));
607 }
608 }
609
610 // The above selects everybody that could be trained here. If I am picky, then also exclude
611 // those
612 // that I could train but do not wish to spend time & resources on.
613 if (limit_upper_bound) {
614 RequireAnd qr;
615 qr.add(RequireAttribute(TrainingAttribute::kTotal, 0, trainee_general_upper_bound));
616 qr.add(r);
617 soldier_request_->set_requirements(qr);
618 } else if (0 < trainee_general_lower_bound_) {
619 RequireAnd qr;
620 qr.add(RequireAttribute(TrainingAttribute::kTotal, trainee_general_lower_bound_ + 1,
621 std::numeric_limits<uint8_t>::max() - 1));
622 qr.add(r);
623 soldier_request_->set_requirements(qr);
624 if (game) {
625 schedule_act(*game, 1 + dynamic_timeout);
626 }
627 } else {
628 soldier_request_->set_requirements(r);
629 }
630 soldier_request_->set_count(capacity_ - soldiers_.size());
631 request_open_since_ = timeofgame;
632
633 } else if (!need_more_soldiers) {
634 delete soldier_request_;
635 soldier_request_ = nullptr;
636
637 while (soldiers_.size() > capacity_) {
638 soldier_control_.drop_soldier(**soldiers_.rbegin());
639 }
640 } else {
641 soldier_request_->set_count(capacity_ - soldiers_.size());
642 }
643 }
644
645 /**
646 * Soldier callback. Since the soldier was already added via add_worker,
647 * we only need to update the request structure.
648 */
request_soldier_callback(Game & game,Request & rq,DescriptionIndex,Worker * const w,PlayerImmovable & target)649 void TrainingSite::request_soldier_callback(Game& game,
650 #ifndef NDEBUG
651 Request& rq,
652 #else
653 Request&,
654 #endif
655 DescriptionIndex,
656 Worker* const w,
657 PlayerImmovable& target) {
658 TrainingSite& tsite = dynamic_cast<TrainingSite&>(target);
659 Soldier& s = dynamic_cast<Soldier&>(*w);
660
661 assert(s.get_location(game) == &tsite);
662 assert(tsite.soldier_request_ == &rq);
663
664 tsite.soldier_control_.incorporate_soldier(game, s);
665 }
666
667 /**
668 * Drop all the soldiers that can not be upgraded further at this building.
669 */
drop_unupgradable_soldiers(Game &)670 void TrainingSite::drop_unupgradable_soldiers(Game&) {
671 std::vector<Soldier*> droplist;
672
673 for (uint32_t i = 0; i < soldiers_.size(); ++i) {
674 std::vector<Upgrade>::iterator it = upgrades_.begin();
675 for (; it != upgrades_.end(); ++it) {
676 int32_t level = soldiers_[i]->get_level(it->attribute);
677 if (level >= it->min && level <= it->max)
678 break;
679 }
680
681 if (it == upgrades_.end())
682 droplist.push_back(soldiers_[i]);
683 }
684
685 // Drop soldiers only now, so that changes in the soldiers array don't
686 // mess things up
687 for (Soldier* soldier : droplist) {
688 uint8_t level = soldier->get_level(TrainingAttribute::kTotal);
689 if (level > highest_trainee_level_seen_) {
690 highest_trainee_level_seen_ = level;
691 }
692
693 soldier_control_.drop_soldier(*soldier);
694 repeated_layoff_ctr_ = 0; // redundant, but safe (also reset whenever level increases)
695 if (latest_trainee_was_kickout_) {
696 // If I am calling in weaklings: Stop that. Immediately.
697 latest_trainee_was_kickout_ = false;
698 update_soldier_request(true);
699 }
700 repeated_layoff_inc_ = false;
701 }
702 }
703
704 /**
705 * Drop all the soldiers that can not be upgraded further at this level of resourcing.
706 *
707 */
drop_stalled_soldiers(Game &)708 void TrainingSite::drop_stalled_soldiers(Game&) {
709 Soldier* soldier_to_drop = nullptr;
710 uint8_t highest_soldier_level_seen = 0;
711
712 for (uint32_t i = 0; i < soldiers_.size(); ++i) {
713 uint8_t this_soldier_level = soldiers_[i]->get_level(TrainingAttribute::kTotal);
714
715 bool this_soldier_is_safe = false;
716 if (this_soldier_level <= highest_soldier_level_seen) {
717 // Skip the innermost loop for soldiers that would not be kicked out anyway.
718 // level-zero soldiers are excepted from kick-out implicitly. This is intentional.
719 this_soldier_is_safe = true;
720 } else {
721 for (const Upgrade& upgrade : upgrades_) {
722 if (!this_soldier_is_safe) {
723 // Soldier is safe, if he:
724 // - is below maximum, and
725 // - is not in a stalled state
726 // Check done separately for each art.
727 int32_t level = soldiers_[i]->get_level(upgrade.attribute);
728
729 // Below maximum -check
730 if (level > upgrade.max) {
731 continue;
732 }
733
734 TypeAndLevel train_tl(upgrade.attribute, level);
735 TrainFailCount::iterator tstep = training_failure_count_.find(train_tl);
736 if (tstep == training_failure_count_.end()) {
737 log("\nTrainingSite::drop_stalled_soldiers: ");
738 log("training step %d,%d not found in this school!\n",
739 static_cast<unsigned int>(upgrade.attribute), level);
740 break;
741 }
742
743 tstep->second.second = 1; // a soldier is present at this level
744
745 // Stalled state -check
746 if (max_stall_val_ > tstep->second.first) {
747 this_soldier_is_safe = true;
748 break;
749 }
750 }
751 }
752 }
753 if (!this_soldier_is_safe) {
754 // Make this soldier a kick-out candidate
755 soldier_to_drop = soldiers_[i];
756 highest_soldier_level_seen = this_soldier_level;
757 }
758 }
759
760 // Finally drop the soldier.
761 if (nullptr != soldier_to_drop) {
762 log("TrainingSite::drop_stalled_soldiers: Kicking somebody out.\n");
763 uint8_t level = soldier_to_drop->get_level(TrainingAttribute::kTotal);
764 if (level > highest_trainee_level_seen_) {
765 highest_trainee_level_seen_ = level;
766 }
767 latest_trainee_kickout_level_ = level;
768 soldier_control_.drop_soldier(*soldier_to_drop);
769 latest_trainee_was_kickout_ = true;
770 // We can enter into state where same soldiers repeatedly enter the site
771 // even if they cannot be promited (lack of gold, lack of an equipmentsmith
772 // of some kind or so). The repeated_layoff_ctr_ works around that.
773 //
774 // Only repeated drops with incorporating new soldiers in between causes this to happen!
775 if (std::numeric_limits<uint8_t>::max() - 1 > repeated_layoff_ctr_ && repeated_layoff_inc_) {
776 repeated_layoff_ctr_++;
777 repeated_layoff_inc_ = false;
778 }
779 }
780 }
781
create_building_settings() const782 const BuildingSettings* TrainingSite::create_building_settings() const {
783 TrainingsiteSettings* settings = new TrainingsiteSettings(descr(), owner().tribe());
784 settings->apply(*ProductionSite::create_building_settings());
785 settings->desired_capacity =
786 std::min(settings->max_capacity, soldier_control_.soldier_capacity());
787 return settings;
788 }
789
790 /**
791 * In addition to advancing the program, update soldier status.
792 */
act(Game & game,uint32_t const data)793 void TrainingSite::act(Game& game, uint32_t const data) {
794 // unit of gametime is [ms].
795 ProductionSite::act(game, data);
796 update_soldier_request(false);
797 }
798
program_end(Game & game,ProgramResult const result)799 void TrainingSite::program_end(Game& game, ProgramResult const result) {
800 result_ = result;
801 ProductionSite::program_end(game, result);
802 // For unknown reasons sometimes there is a fully upgraded soldier
803 // that failed to be send away, so at the end of this function
804 // we test for such soldiers, unless another drop_soldiers
805 // function were run
806 bool leftover_soldiers_check = true;
807
808 if (current_upgrade_) {
809 if (result_ == ProgramResult::kCompleted) {
810 drop_unupgradable_soldiers(game);
811 leftover_soldiers_check = false;
812 current_upgrade_->lastsuccess = true;
813 current_upgrade_->failures = 0;
814
815 // I try to already somewhat trained soldiers here, except when
816 // no training happens. Now some training has happened, hence zero.
817 // read in update_soldier_request
818 repeated_layoff_ctr_ = 0;
819 repeated_layoff_inc_ = false;
820 } else {
821 current_upgrade_->failures++;
822 drop_stalled_soldiers(game);
823 leftover_soldiers_check = false;
824 }
825 current_upgrade_ = nullptr;
826 }
827
828 if (leftover_soldiers_check) {
829 drop_unupgradable_soldiers(game);
830 }
831
832 training_done();
833 }
834
835 /**
836 * Find and start the next training program.
837 *
838 * Prioritize such that if UpgradeA.prio is twice UpgradeB.prio, then
839 * start_upgrade will be called twice as often for UpgradeA.
840 * If all priorities are zero, nothing will happen.
841 */
find_and_start_next_program(Game & game)842 void TrainingSite::find_and_start_next_program(Game& game) {
843 for (;;) {
844 uint32_t maxprio = 0;
845 uint32_t maxcredit = 0;
846
847 for (Upgrade& upgrade : upgrades_) {
848 if (upgrade.credit >= 10) {
849 upgrade.credit -= 10;
850 return start_upgrade(game, upgrade);
851 }
852
853 if (maxprio < upgrade.prio)
854 maxprio = upgrade.prio;
855 if (maxcredit < upgrade.credit)
856 maxcredit = upgrade.credit;
857 }
858
859 if (maxprio == 0) {
860 return program_start(game, "sleep");
861 }
862
863 uint32_t const multiplier = 1 + (10 - maxcredit) / maxprio;
864
865 for (Upgrade& upgrade : upgrades_) {
866 upgrade.credit += multiplier * upgrade.prio;
867 }
868 }
869 }
870
871 /**
872 * The prioritizer decided that the given type of upgrade should run.
873 * Let's do our worst.
874 */
start_upgrade(Game & game,Upgrade & upgrade)875 void TrainingSite::start_upgrade(Game& game, Upgrade& upgrade) {
876 int32_t minlevel = upgrade.max;
877 int32_t maxlevel = upgrade.min;
878
879 for (Soldier* soldier : soldiers_) {
880 int32_t const level = soldier->get_level(upgrade.attribute);
881
882 if (level > upgrade.max || level < upgrade.min)
883 continue;
884 if (level < minlevel)
885 minlevel = level;
886 if (level > maxlevel)
887 maxlevel = level;
888 }
889
890 if (minlevel > maxlevel)
891 return program_start(game, "sleep");
892
893 int32_t level;
894
895 if (upgrade.lastsuccess || upgrade.lastattempt < 0) {
896 // Start greedily on the first ever attempt, and restart greedily
897 // after a sucessful upgrade
898 if (build_heroes_)
899 level = maxlevel;
900 else
901 level = minlevel;
902 } else {
903 // The last attempt wasn't successful;
904 // This happens e.g. when lots of low-level soldiers are present,
905 // but the prerequisites for improving them aren't.
906 if (build_heroes_) {
907 level = upgrade.lastattempt - 1;
908 if (level < minlevel)
909 level = maxlevel;
910 } else {
911 level = upgrade.lastattempt + 1;
912 if (level > maxlevel)
913 level = minlevel;
914 }
915 }
916
917 current_upgrade_ = &upgrade;
918 upgrade.lastattempt = level;
919 upgrade.lastsuccess = false;
920
921 return program_start(game, (boost::format("%s%i") % upgrade.prefix.c_str() % level).str());
922 }
923
get_upgrade(TrainingAttribute const atr)924 TrainingSite::Upgrade* TrainingSite::get_upgrade(TrainingAttribute const atr) {
925 for (Upgrade& upgrade : upgrades_) {
926 if (upgrade.attribute == atr) {
927 return &upgrade;
928 }
929 }
930 return nullptr;
931 }
932
933 /**
934 * Gets the priority of given attribute
935 */
get_pri(TrainingAttribute atr)936 int32_t TrainingSite::get_pri(TrainingAttribute atr) {
937 for (const Upgrade& upgrade : upgrades_) {
938 if (upgrade.attribute == atr) {
939 return upgrade.prio;
940 }
941 }
942 return 0;
943 }
944
945 /**
946 * Sets the priority of given attribute
947 */
set_pri(TrainingAttribute atr,int32_t prio)948 void TrainingSite::set_pri(TrainingAttribute atr, int32_t prio) {
949 if (prio < 0)
950 prio = 0;
951
952 for (Upgrade& upgrade : upgrades_) {
953 if (upgrade.attribute == atr) {
954 upgrade.prio = prio;
955 return;
956 }
957 }
958 }
959
960 /**
961 * Only called from \ref calc_upgrades
962 */
add_upgrade(TrainingAttribute const atr,const std::string & prefix)963 void TrainingSite::add_upgrade(TrainingAttribute const atr, const std::string& prefix) {
964 Upgrade u;
965 u.attribute = atr;
966 u.prefix = prefix;
967 u.min = descr().get_min_level(atr);
968 u.max = descr().get_max_level(atr);
969 u.prio = 6;
970 u.credit = 0;
971 u.lastattempt = -1;
972 u.lastsuccess = false;
973 u.failures = 0;
974 upgrades_.push_back(u);
975 }
976
977 /**
978 * Called once at initialization to populate \ref upgrades_.
979 */
calc_upgrades()980 void TrainingSite::calc_upgrades() {
981 assert(upgrades_.empty());
982
983 // TODO(unknown): This is currently hardcoded for "soldier" but it should allow any
984 // soldier type name.
985 if (descr().get_train_health())
986 add_upgrade(TrainingAttribute::kHealth, "upgrade_soldier_health_");
987 if (descr().get_train_attack())
988 add_upgrade(TrainingAttribute::kAttack, "upgrade_soldier_attack_");
989 if (descr().get_train_defense())
990 add_upgrade(TrainingAttribute::kDefense, "upgrade_soldier_defense_");
991 if (descr().get_train_evade())
992 add_upgrade(TrainingAttribute::kEvade, "upgrade_soldier_evade_");
993 }
994
training_attempted(TrainingAttribute type,uint32_t level)995 void TrainingSite::training_attempted(TrainingAttribute type, uint32_t level) {
996 TypeAndLevel key(type, level);
997 if (training_failure_count_.find(key) == training_failure_count_.end())
998 training_failure_count_[key] = std::make_pair(training_state_multiplier_, 0);
999 else
1000 training_failure_count_[key].first += training_state_multiplier_;
1001 }
1002
1003 /**
1004 * Called whenever it was possible to promote another guy
1005 */
1006
training_successful(TrainingAttribute type,uint32_t level)1007 void TrainingSite::training_successful(TrainingAttribute type, uint32_t level) {
1008 TypeAndLevel key(type, level);
1009 // Here I assume that key exists: training has been attempted before it can succeed.
1010 training_failure_count_[key].first = 0;
1011 }
1012
training_done()1013 void TrainingSite::training_done() {
1014 for (auto& fail_and_presence : training_failure_count_) {
1015 // If a soldier is present at this training level and site is running, deteoriate
1016 if (fail_and_presence.second.second && (!is_stopped())) {
1017 fail_and_presence.second.first++;
1018 fail_and_presence.second.second = 0;
1019 } else if (0 < fail_and_presence.second.first) { // If no soldier, let's become optimistic
1020 fail_and_presence.second.first--;
1021 }
1022 }
1023 }
1024 } // namespace Widelands
1025