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/warehouse.h"
21
22 #include "base/log.h"
23 #include "base/macros.h"
24 #include "base/wexception.h"
25 #include "economy/economy.h"
26 #include "economy/expedition_bootstrap.h"
27 #include "economy/flag.h"
28 #include "economy/portdock.h"
29 #include "economy/request.h"
30 #include "economy/ship_fleet.h"
31 #include "economy/warehousesupply.h"
32 #include "economy/wares_queue.h"
33 #include "logic/editor_game_base.h"
34 #include "logic/game.h"
35 #include "logic/map_objects/findbob.h"
36 #include "logic/map_objects/findnode.h"
37 #include "logic/map_objects/tribes/battle.h"
38 #include "logic/map_objects/tribes/carrier.h"
39 #include "logic/map_objects/tribes/requirements.h"
40 #include "logic/map_objects/tribes/soldier.h"
41 #include "logic/map_objects/tribes/tribe_descr.h"
42 #include "logic/map_objects/tribes/worker.h"
43 #include "logic/message_queue.h"
44 #include "logic/player.h"
45
46 namespace Widelands {
47
48 namespace {
49
50 static const uint32_t WORKER_WITHOUT_COST_SPAWN_INTERVAL = 2500;
51 constexpr int kFleeingUnitsCap = 500;
52
53 // Goes through the list and removes all workers that are no longer in the
54 // game.
remove_no_longer_existing_workers(Game & game,std::vector<Worker * > * workers)55 void remove_no_longer_existing_workers(Game& game, std::vector<Worker*>* workers) {
56 for (std::vector<Worker*>::iterator i = workers->begin(); i != workers->end(); ++i) {
57 if (!game.objects().object_still_available(*i)) {
58 workers->erase(i);
59 remove_no_longer_existing_workers(game, workers);
60 return;
61 }
62 }
63 }
64
65 } // namespace
66
can_be_attacked() const67 bool Warehouse::AttackTarget::can_be_attacked() const {
68 return warehouse_->descr().get_conquers() > 0;
69 }
70
enemy_soldier_approaches(const Soldier & enemy) const71 void Warehouse::AttackTarget::enemy_soldier_approaches(const Soldier& enemy) const {
72 if (!warehouse_->descr().get_conquers())
73 return;
74
75 Player* owner = warehouse_->get_owner();
76 Game& game = dynamic_cast<Game&>(owner->egbase());
77 const Map& map = game.map();
78 if (enemy.get_owner() == owner || enemy.get_battle() ||
79 warehouse_->descr().get_conquers() <=
80 map.calc_distance(enemy.get_position(), warehouse_->get_position()))
81 return;
82
83 if (map.find_bobs(game,
84 Area<FCoords>(map.get_fcoords(warehouse_->base_flag().get_position()), 2),
85 nullptr, FindBobEnemySoldier(owner)))
86 return;
87
88 DescriptionIndex const soldier_index = owner->tribe().soldier();
89 Requirements noreq;
90
91 if (!warehouse_->count_workers(game, soldier_index, noreq, Match::kCompatible))
92 return;
93
94 Soldier& defender =
95 dynamic_cast<Soldier&>(warehouse_->launch_worker(game, soldier_index, noreq));
96 defender.start_task_defense(game, false);
97 }
98
attack(Soldier * enemy) const99 AttackTarget::AttackResult Warehouse::AttackTarget::attack(Soldier* enemy) const {
100 Player* owner = warehouse_->get_owner();
101 Game& game = dynamic_cast<Game&>(owner->egbase());
102 DescriptionIndex const soldier_index = owner->tribe().soldier();
103 Requirements noreq;
104
105 if (warehouse_->count_workers(game, soldier_index, noreq, Match::kCompatible)) {
106 Soldier& defender =
107 dynamic_cast<Soldier&>(warehouse_->launch_worker(game, soldier_index, noreq));
108 defender.start_task_defense(game, true);
109 enemy->send_signal(game, "sleep");
110 return AttackTarget::AttackResult::DefenderLaunched;
111 }
112
113 warehouse_->set_defeating_player(enemy->owner().player_number());
114 warehouse_->schedule_destroy(game);
115 return AttackTarget::AttackResult::Defenseless;
116 }
117
~WarehouseSupply()118 WarehouseSupply::~WarehouseSupply() {
119 if (ware_economy_) {
120 log("WarehouseSupply::~WarehouseSupply: Warehouse %u still belongs to "
121 "a ware_economy",
122 warehouse_->serial());
123 set_economy(nullptr, wwWARE);
124 }
125 if (worker_economy_) {
126 log("WarehouseSupply::~WarehouseSupply: Warehouse %u still belongs to "
127 "a worker_economy",
128 warehouse_->serial());
129 set_economy(nullptr, wwWORKER);
130 }
131
132 // We're removed from the Economy. Therefore, the wares can simply
133 // be cleared out. The global inventory will be okay.
134 wares_.clear();
135 workers_.clear();
136 }
137
138 /// Inform this supply, how much wares are to be handled
set_nrwares(DescriptionIndex const i)139 void WarehouseSupply::set_nrwares(DescriptionIndex const i) {
140 assert(0 == wares_.get_nrwareids());
141
142 wares_.set_nrwares(i);
143 }
set_nrworkers(DescriptionIndex const i)144 void WarehouseSupply::set_nrworkers(DescriptionIndex const i) {
145 assert(0 == workers_.get_nrwareids());
146
147 workers_.set_nrwares(i);
148 }
149
150 /// Add and remove our wares and the Supply to the economies as necessary.
set_economy(Economy * const e,WareWorker type)151 void WarehouseSupply::set_economy(Economy* const e, WareWorker type) {
152 if (e == (type == wwWARE ? ware_economy_ : worker_economy_))
153 return;
154
155 if (Economy* ec = (type == wwWARE ? ware_economy_ : worker_economy_)) {
156 ec->remove_supply(*this);
157 switch (type) {
158 case wwWARE:
159 for (DescriptionIndex i = 0; i < wares_.get_nrwareids(); ++i) {
160 if (wares_.stock(i)) {
161 ec->remove_wares_or_workers(i, wares_.stock(i));
162 }
163 }
164 break;
165 case wwWORKER:
166 for (DescriptionIndex i = 0; i < workers_.get_nrwareids(); ++i) {
167 if (workers_.stock(i)) {
168 ec->remove_wares_or_workers(i, workers_.stock(i));
169 }
170 }
171 break;
172 }
173 }
174
175 (type == wwWARE ? ware_economy_ : worker_economy_) = e;
176
177 if (Economy* ec = (type == wwWARE ? ware_economy_ : worker_economy_)) {
178 switch (type) {
179 case wwWARE:
180 for (DescriptionIndex i = 0; i < wares_.get_nrwareids(); ++i) {
181 if (wares_.stock(i)) {
182 ec->add_wares_or_workers(i, wares_.stock(i), worker_economy_);
183 }
184 }
185 break;
186 case wwWORKER:
187 for (DescriptionIndex i = 0; i < workers_.get_nrwareids(); ++i) {
188 if (workers_.stock(i)) {
189 e->add_wares_or_workers(i, workers_.stock(i), ware_economy_);
190 }
191 }
192 break;
193 }
194 ec->add_supply(*this);
195 }
196 }
197
198 /// Add wares and update the economy.
add_wares(DescriptionIndex const id,Quantity const count)199 void WarehouseSupply::add_wares(DescriptionIndex const id, Quantity const count) {
200 if (!count)
201 return;
202
203 if (ware_economy_) { // No economies in the editor
204 ware_economy_->add_wares_or_workers(id, count, worker_economy_);
205 }
206 wares_.add(id, count);
207 }
208
209 /// Remove wares and update the economy.
remove_wares(DescriptionIndex const id,uint32_t const count)210 void WarehouseSupply::remove_wares(DescriptionIndex const id, uint32_t const count) {
211 if (!count)
212 return;
213
214 wares_.remove(id, count);
215 if (ware_economy_) { // No economies in the editor
216 ware_economy_->remove_wares_or_workers(id, count);
217 }
218 }
219
220 /// Add workers and update the economy.
add_workers(DescriptionIndex const id,uint32_t const count)221 void WarehouseSupply::add_workers(DescriptionIndex const id, uint32_t const count) {
222 if (!count)
223 return;
224
225 if (worker_economy_) { // No economies in the editor
226 worker_economy_->add_wares_or_workers(id, count, ware_economy_);
227 }
228 workers_.add(id, count);
229 }
230
231 /**
232 * Remove workers and update the economy.
233 * Comments see add_workers
234 */
remove_workers(DescriptionIndex const id,uint32_t const count)235 void WarehouseSupply::remove_workers(DescriptionIndex const id, uint32_t const count) {
236 if (!count)
237 return;
238
239 workers_.remove(id, count);
240 if (worker_economy_) { // No economies in the editor
241 worker_economy_->remove_wares_or_workers(id, count);
242 }
243 }
244
245 /// Return the position of the Supply, i.e. the owning Warehouse.
get_position(Game &)246 PlayerImmovable* WarehouseSupply::get_position(Game&) {
247 return warehouse_;
248 }
249
250 /// Warehouse supplies are never active.
is_active() const251 bool WarehouseSupply::is_active() const {
252 return false;
253 }
provider_type(Game *) const254 SupplyProviders WarehouseSupply::provider_type(Game*) const {
255 return SupplyProviders::kWarehouse;
256 }
257
has_storage() const258 bool WarehouseSupply::has_storage() const {
259 return true;
260 }
261
get_ware_type(WareWorker &,DescriptionIndex &) const262 void WarehouseSupply::get_ware_type(WareWorker& /* type */, DescriptionIndex& /* ware */) const {
263 throw wexception("WarehouseSupply::get_ware_type: calling this is nonsensical");
264 }
265
send_to_storage(Game &,Warehouse *)266 void WarehouseSupply::send_to_storage(Game&, Warehouse* /* wh */) {
267 throw wexception("WarehouseSupply::send_to_storage: should never be called");
268 }
269
nr_supplies(const Game & game,const Request & req) const270 uint32_t WarehouseSupply::nr_supplies(const Game& game, const Request& req) const {
271 if (req.get_type() == wwWORKER) {
272 return warehouse_->count_workers(
273 game, req.get_index(), req.get_requirements(),
274 (req.get_exact_match() ? Warehouse::Match::kExact : Warehouse::Match::kCompatible));
275 }
276
277 // Calculate how many wares can be sent out - it might be that we need them
278 // ourselves. E.g. for hiring new soldiers.
279 int32_t const x = wares_.stock(req.get_index());
280 // only mark an ware of that type as available, if the priority of the
281 // request + number of that wares in warehouse is > priority of request
282 // of *this* warehouse + 1 (+1 is important, as else the ware would directly
283 // be taken back to the warehouse as the request of the warehouse would be
284 // highered and would have the same value as the original request)
285 int32_t const y = x + (req.get_priority(0) / 100) -
286 (warehouse_->get_priority(wwWARE, req.get_index()) / 100) - 1;
287 // But the number should never be higher than the number of wares available
288 if (y > x)
289 return x;
290 return (x > 0) ? x : 0;
291 }
292
293 /// Launch a ware.
launch_ware(Game & game,const Request & req)294 WareInstance& WarehouseSupply::launch_ware(Game& game, const Request& req) {
295 if (req.get_type() != wwWARE)
296 throw wexception("WarehouseSupply::launch_ware: called for non-ware request");
297 if (!wares_.stock(req.get_index()))
298 throw wexception("WarehouseSupply::launch_ware: called for non-existing ware");
299
300 return warehouse_->launch_ware(game, req.get_index());
301 }
302
303 /// Launch a ware as worker.
launch_worker(Game & game,const Request & req)304 Worker& WarehouseSupply::launch_worker(Game& game, const Request& req) {
305 return warehouse_->launch_worker(game, req.get_index(), req.get_requirements());
306 }
307
308 /*
309 ==============================
310 Warehouse Building
311 ==============================
312 */
313
314 /**
315 * The contents of 'table' are documented in
316 * /data/tribes/buildings/warehouses/atlanteans/headquarters/init.lua
317 */
WarehouseDescr(const std::string & init_descname,const LuaTable & table,const Tribes & tribes)318 WarehouseDescr::WarehouseDescr(const std::string& init_descname,
319 const LuaTable& table,
320 const Tribes& tribes)
321 : BuildingDescr(init_descname, MapObjectType::WAREHOUSE, table, tribes),
322 conquers_(0),
323 heal_per_second_(0) {
324 heal_per_second_ = table.get_int("heal_per_second");
325 if (table.has_key("conquers")) {
326 conquers_ = table.get_int("conquers");
327 workarea_info_[conquers_].insert(name() + " conquer");
328 }
329 }
330
present_soldiers() const331 std::vector<Soldier*> Warehouse::SoldierControl::present_soldiers() const {
332 std::vector<Soldier*> rv;
333 DescriptionIndex const soldier_index = warehouse_->owner().tribe().soldier();
334 IncorporatedWorkers::const_iterator sidx = warehouse_->incorporated_workers_.find(soldier_index);
335
336 if (sidx != warehouse_->incorporated_workers_.end()) {
337 const WorkerList& soldiers = sidx->second;
338 for (Worker* temp_soldier : soldiers) {
339 rv.push_back(static_cast<Soldier*>(temp_soldier));
340 }
341 }
342 return rv;
343 }
344
stationed_soldiers() const345 std::vector<Soldier*> Warehouse::SoldierControl::stationed_soldiers() const {
346 return present_soldiers();
347 }
348
min_soldier_capacity() const349 Quantity Warehouse::SoldierControl::min_soldier_capacity() const {
350 return 0;
351 }
352
max_soldier_capacity() const353 Quantity Warehouse::SoldierControl::max_soldier_capacity() const {
354 return std::numeric_limits<Quantity>::max();
355 }
356
soldier_capacity() const357 Quantity Warehouse::SoldierControl::soldier_capacity() const {
358 return max_soldier_capacity();
359 }
360
set_soldier_capacity(Quantity)361 void Warehouse::SoldierControl::set_soldier_capacity(Quantity /* capacity */) {
362 throw wexception("Not implemented for a Warehouse!");
363 }
364
drop_soldier(Soldier &)365 void Warehouse::SoldierControl::drop_soldier(Soldier&) {
366 throw wexception("Not implemented for a Warehouse!");
367 }
368
outcorporate_soldier(Soldier & soldier)369 int Warehouse::SoldierControl::outcorporate_soldier(Soldier& soldier) {
370 DescriptionIndex const soldier_index = warehouse_->owner().tribe().soldier();
371 if (warehouse_->incorporated_workers_.count(soldier_index)) {
372 WorkerList& soldiers = warehouse_->incorporated_workers_[soldier_index];
373
374 WorkerList::iterator i = std::find(soldiers.begin(), soldiers.end(), &soldier);
375
376 soldiers.erase(i);
377 warehouse_->supply_->remove_workers(soldier_index, 1);
378 }
379 #ifndef NDEBUG
380 else
381 throw wexception("outcorporate_soldier: soldier not in this warehouse!");
382 #endif
383 return 0;
384 }
385
incorporate_soldier(EditorGameBase & egbase,Soldier & soldier)386 int Warehouse::SoldierControl::incorporate_soldier(EditorGameBase& egbase, Soldier& soldier) {
387 warehouse_->incorporate_worker(egbase, &soldier);
388 return 0;
389 }
390
Warehouse(const WarehouseDescr & warehouse_descr)391 Warehouse::Warehouse(const WarehouseDescr& warehouse_descr)
392 : Building(warehouse_descr),
393 attack_target_(this),
394 soldier_control_(this),
395 supply_(new WarehouseSupply(this)),
396 next_military_act_(0),
397 portdock_(nullptr) {
398 next_stock_remove_act_ = 0;
399 cleanup_in_progress_ = false;
400 set_attack_target(&attack_target_);
401 set_soldier_control(&soldier_control_);
402 }
403
~Warehouse()404 Warehouse::~Warehouse() {
405 delete supply_;
406 }
407
408 /**
409 * Try to bring the given \ref PlannedWorkers up to date with our game data.
410 * Return \c false if \p pw cannot be salvaged.
411 */
load_finish_planned_worker(PlannedWorkers & pw)412 bool Warehouse::load_finish_planned_worker(PlannedWorkers& pw) {
413 const TribeDescr& tribe = owner().tribe();
414
415 if (pw.index == INVALID_INDEX || !(pw.index < supply_->get_workers().get_nrwareids())) {
416 return false;
417 }
418
419 const WorkerDescr* w_desc = tribe.get_worker_descr(pw.index);
420
421 if (!(tribe.has_worker(pw.index) && w_desc->is_buildable())) {
422 return false;
423 }
424
425 if (!(pw.index < supply_->get_workers().get_nrwareids())) {
426 return false;
427 }
428
429 const WorkerDescr::Buildcost& cost = w_desc->buildcost();
430 uint32_t idx = 0;
431
432 for (WorkerDescr::Buildcost::const_iterator cost_it = cost.begin(); cost_it != cost.end();
433 ++cost_it, ++idx) {
434 WareWorker type;
435 DescriptionIndex wareindex = owner().tribe().ware_index(cost_it->first);
436 if (owner().tribe().has_ware(wareindex)) {
437 type = wwWARE;
438 } else {
439 wareindex = owner().tribe().worker_index(cost_it->first);
440 if (owner().tribe().has_worker(wareindex)) {
441 type = wwWORKER;
442 } else {
443 return false;
444 }
445 }
446
447 if (idx < pw.requests.size()) {
448 if (pw.requests[idx]->get_type() == type && pw.requests[idx]->get_index() == wareindex)
449 continue;
450
451 std::vector<Request*>::iterator req_it = pw.requests.begin() + idx + 1;
452 while (req_it != pw.requests.end()) {
453 if ((*req_it)->get_type() == type && (*req_it)->get_index() == wareindex)
454 break;
455 ++req_it;
456 }
457
458 if (req_it != pw.requests.end()) {
459 std::swap(*req_it, pw.requests[idx]);
460 continue;
461 }
462 }
463
464 log("load_finish_planned_worker: old savegame: "
465 "need to create new request for '%s'\n",
466 cost_it->first.c_str());
467 pw.requests.insert(
468 pw.requests.begin() + idx, new Request(*this, wareindex, &Warehouse::request_cb, type));
469 }
470
471 while (pw.requests.size() > idx) {
472 log("load_finish_planned_worker: old savegame: "
473 "removing outdated request.\n");
474 delete pw.requests.back();
475 pw.requests.pop_back();
476 }
477
478 return true;
479 }
480
load_finish(EditorGameBase & egbase)481 void Warehouse::load_finish(EditorGameBase& egbase) {
482 Building::load_finish(egbase);
483
484 Time next_spawn = never();
485 const std::vector<DescriptionIndex>& worker_types_without_cost =
486 owner().tribe().worker_types_without_cost();
487 for (uint8_t i = worker_types_without_cost.size(); i;) {
488 DescriptionIndex const worker_index = worker_types_without_cost.at(--i);
489 if (owner().is_worker_type_allowed(worker_index) &&
490 next_worker_without_cost_spawn_[i] == never()) {
491 if (next_spawn == never()) {
492 next_spawn =
493 schedule_act(dynamic_cast<Game&>(egbase), WORKER_WITHOUT_COST_SPAWN_INTERVAL);
494 }
495 next_worker_without_cost_spawn_[i] = next_spawn;
496 log("WARNING: player %u is allowed to create worker type %s but his "
497 "%s %u at (%i, %i) does not have a next_spawn time set for that "
498 "worker type; setting it to %u\n",
499 owner().player_number(),
500 owner().tribe().get_worker_descr(worker_index)->name().c_str(), descr().name().c_str(),
501 serial(), get_position().x, get_position().y, next_spawn);
502 }
503 }
504
505 // Ensure consistency of PlannedWorker requests
506 {
507 uint32_t pwidx = 0;
508 while (pwidx < planned_workers_.size()) {
509 if (!load_finish_planned_worker(planned_workers_[pwidx])) {
510 planned_workers_[pwidx].cleanup();
511 planned_workers_.erase(planned_workers_.begin() + pwidx);
512 } else {
513 pwidx++;
514 }
515 }
516 }
517 }
518
init(EditorGameBase & egbase)519 bool Warehouse::init(EditorGameBase& egbase) {
520 Building::init(egbase);
521
522 Player* player = get_owner();
523
524 init_containers(*player);
525
526 // Even though technically, a warehouse might be completely empty,
527 // we let warehouse see always for simplicity's sake (since there's
528 // almost always going to be a carrier inside, that shouldn't hurt).
529 if (upcast(Game, game, &egbase)) {
530 player->see_area(
531 Area<FCoords>(egbase.map().get_fcoords(get_position()), descr().vision_range()));
532
533 {
534 uint32_t const act_time = schedule_act(*game, WORKER_WITHOUT_COST_SPAWN_INTERVAL);
535 const std::vector<DescriptionIndex>& worker_types_without_cost =
536 player->tribe().worker_types_without_cost();
537
538 for (size_t i = 0; i < worker_types_without_cost.size(); ++i) {
539 if (player->is_worker_type_allowed(worker_types_without_cost.at(i))) {
540 next_worker_without_cost_spawn_[i] = act_time;
541 }
542 }
543 }
544 // next_military_act_ is not touched in the loading code. Is only needed
545 // if the warehouse is created in the game? I assume it's for the
546 // conquer_radius thing
547 next_military_act_ = schedule_act(*game, 1000);
548
549 next_stock_remove_act_ = schedule_act(*game, 4000);
550
551 log("Message: adding %s for player %i at (%d, %d)\n", to_string(descr().type()).c_str(),
552 player->player_number(), position_.x, position_.y);
553
554 if (descr().get_isport()) {
555 send_message(*game, Message::Type::kSeafaring, descr().descname(), descr().icon_filename(),
556 descr().descname(), _("A new port was added to your economy."), true);
557 } else if (!descr().is_buildable()) {
558 send_message(*game, Message::Type::kEconomy, descr().descname(), descr().icon_filename(),
559 descr().descname(), _("A new headquarters was added to your economy."), true);
560 } else {
561 send_message(*game, Message::Type::kEconomy, descr().descname(), descr().icon_filename(),
562 descr().descname(), _("A new warehouse was added to your economy."), true);
563 }
564 }
565
566 if (uint32_t const conquer_radius = descr().get_conquers()) {
567 egbase.conquer_area(
568 PlayerArea<Area<FCoords>>(
569 player->player_number(),
570 Area<FCoords>(egbase.map().get_fcoords(get_position()), conquer_radius)),
571 true);
572 }
573
574 if (descr().get_isport()) {
575 init_portdock(egbase);
576 PortDock* pd = portdock_;
577 // should help diagnose problems with marine
578 if (!pd->get_fleet()) {
579 log(" Warning: portdock without a fleet created (%3dx%3d)\n", get_position().x,
580 get_position().y);
581 }
582 }
583 cleanup_in_progress_ = false;
584 return true;
585 }
586
init_containers(const Player & player)587 void Warehouse::init_containers(const Player& player) {
588 DescriptionIndex const nr_wares = player.egbase().tribes().nrwares();
589 DescriptionIndex const nr_workers = player.egbase().tribes().nrworkers();
590 supply_->set_nrwares(nr_wares);
591 supply_->set_nrworkers(nr_workers);
592
593 ware_policy_.resize(nr_wares, StockPolicy::kNormal);
594 worker_policy_.resize(nr_workers, StockPolicy::kNormal);
595
596 uint8_t nr_worker_types_without_cost = player.tribe().worker_types_without_cost().size();
597 next_worker_without_cost_spawn_.resize(nr_worker_types_without_cost, never());
598 }
599
600 /**
601 * Find a contiguous set of water fields close to the port for docking
602 * and initialize the @ref PortDock instance.
603 */
init_portdock(EditorGameBase & egbase)604 void Warehouse::init_portdock(EditorGameBase& egbase) {
605 molog("Setting up port dock fields\n");
606
607 std::vector<Coords> dock = egbase.map().find_portdock(get_position());
608 if (dock.empty()) {
609 log("Attempting to setup port without neighboring water (coords: %3dx%3d).\n",
610 get_position().x, get_position().y);
611 return;
612 }
613
614 molog("Found %" PRIuS " fields for the dock\n", dock.size());
615
616 portdock_ = new PortDock(this);
617 portdock_->set_owner(get_owner());
618 portdock_->set_economy(get_economy(wwWARE), wwWARE);
619 portdock_->set_economy(get_economy(wwWORKER), wwWORKER);
620 for (const Coords& coords : dock) {
621 portdock_->add_position(coords);
622 }
623 portdock_->init(egbase);
624
625 if (get_economy(wwWARE) != nullptr) {
626 portdock_->set_economy(get_economy(wwWARE), wwWARE);
627 }
628 if (get_economy(wwWORKER) != nullptr) {
629 portdock_->set_economy(get_economy(wwWORKER), wwWORKER);
630 }
631
632 // this is just to indicate something wrong is going on
633 PortDock* pd_tmp = portdock_;
634 if (!pd_tmp->get_fleet()) {
635 log(" portdock for port at %3dx%3d created but without a fleet!\n", get_position().x,
636 get_position().y);
637 }
638 }
639
destroy(EditorGameBase & egbase)640 void Warehouse::destroy(EditorGameBase& egbase) {
641 Building::destroy(egbase);
642 }
643
644 // if the port still exists and we are in game we first try to restore the portdock
restore_portdock_or_destroy(EditorGameBase & egbase)645 void Warehouse::restore_portdock_or_destroy(EditorGameBase& egbase) {
646 Warehouse::init_portdock(egbase);
647 if (!portdock_) {
648 log(" Portdock could not be restored, removing the port now (coords: %3dx%3d)\n",
649 get_position().x, get_position().y);
650 Building::destroy(egbase);
651 } else {
652 molog("Message: portdock restored\n");
653 PortDock* pd_tmp = portdock_;
654 if (!pd_tmp->get_fleet()) {
655 log(" Portdock restored but without a fleet!\n");
656 }
657 }
658 }
659
660 /// Destroy the warehouse.
cleanup(EditorGameBase & egbase)661 void Warehouse::cleanup(EditorGameBase& egbase) {
662 // if this is a port, it will remove also portdock.
663 // But portdock must know that it should not try to recreate itself
664 cleanup_in_progress_ = true;
665
666 if (egbase.objects().object_still_available(portdock_)) {
667 portdock_->remove(egbase);
668 }
669
670 if (!egbase.objects().object_still_available(portdock_)) {
671 portdock_ = nullptr;
672 }
673
674 // This will launch all workers including incorporated ones up to kFleeingUnitsCap and then empty
675 // the stock.
676 if (upcast(Game, game, &egbase)) {
677 const WareList& workers = get_workers();
678 for (DescriptionIndex id = 0; id < workers.get_nrwareids(); ++id) {
679 // If the game is running, have the workers flee the warehouse.
680 if (game->is_loaded()) {
681 // We have kFleeingUnitsCap to make sure that we won't flood the map with carriers etc.
682 Quantity stock = workers.stock(id);
683 for (Quantity i = 0; i < stock && i < kFleeingUnitsCap; ++i) {
684 launch_worker(*game, id, Requirements()).start_task_leavebuilding(*game, true);
685 }
686 }
687 // Make sure that all workers are gone
688 remove_workers(id, workers.stock(id));
689 assert(!game->is_loaded() ||
690 (!incorporated_workers_.count(id) || incorporated_workers_[id].empty()));
691 }
692 }
693 incorporated_workers_.clear();
694
695 while (!planned_workers_.empty()) {
696 planned_workers_.back().cleanup();
697 planned_workers_.pop_back();
698 }
699
700 const Map& map = egbase.map();
701 if (const uint32_t conquer_radius = descr().get_conquers())
702 egbase.unconquer_area(
703 PlayerArea<Area<FCoords>>(owner().player_number(),
704 Area<FCoords>(map.get_fcoords(get_position()), conquer_radius)),
705 defeating_player_);
706
707 // Unsee the area that we started seeing in init()
708 get_owner()->unsee_area(Area<FCoords>(map.get_fcoords(get_position()), descr().vision_range()));
709
710 Building::cleanup(egbase);
711 }
712
713 /// Act regularly to create workers of buildable types without cost. According
714 /// to intelligence, this is some highly advanced technology. Not only do the
715 /// settlers have no problems with birth control, they do not even need anybody
716 /// to procreate. They must have built-in DNA samples in those warehouses. And
717 /// what the hell are they doing, killing useless tribesmen! The Borg? Or just
718 /// like Soylent Green? Or maybe I should just stop writing comments that late
719 /// at night ;-)
act(Game & game,uint32_t const data)720 void Warehouse::act(Game& game, uint32_t const data) {
721 const int32_t gametime = game.get_gametime();
722 {
723 const std::vector<DescriptionIndex>& worker_types_without_cost =
724 owner().tribe().worker_types_without_cost();
725 for (size_t i = worker_types_without_cost.size(); i;)
726 if (next_worker_without_cost_spawn_[--i] <= gametime) {
727 DescriptionIndex const id = worker_types_without_cost.at(i);
728 if (owner().is_worker_type_allowed(id)) {
729 int32_t const stock = supply_->stock_workers(id);
730 int32_t tdelta = WORKER_WITHOUT_COST_SPAWN_INTERVAL;
731
732 if (stock < 100) {
733 tdelta -= 4 * (100 - stock);
734 insert_workers(id, 1);
735 } else if (stock > 100) {
736 tdelta -= 4 * (stock - 100);
737 if (tdelta < 10)
738 tdelta = 10;
739 remove_workers(id, 1);
740 }
741
742 next_worker_without_cost_spawn_[i] = schedule_act(game, tdelta);
743 } else
744 next_worker_without_cost_spawn_[i] = never();
745 }
746 }
747
748 // Military stuff: Kill the soldiers that are dead.
749 if (next_military_act_ <= gametime) {
750 DescriptionIndex const soldier_index = owner().tribe().soldier();
751
752 if (incorporated_workers_.count(soldier_index)) {
753 WorkerList& soldiers = incorporated_workers_[soldier_index];
754
755 uint32_t total_heal = descr().get_heal_per_second();
756 // Using an explicit iterator, as we plan to erase some
757 // of those guys
758 for (WorkerList::iterator it = soldiers.begin(); it != soldiers.end(); ++it) {
759 // This is a safe cast: we know only soldiers can land in this
760 // slot in the incorporated array
761 Soldier* soldier = static_cast<Soldier*>(*it);
762
763 // Soldier dead ...
764 if (!soldier || soldier->get_current_health() == 0) {
765 it = soldiers.erase(it);
766 supply_->remove_workers(soldier_index, 1);
767 continue;
768 }
769
770 if (soldier->get_current_health() < soldier->get_max_health()) {
771 soldier->heal(total_heal);
772 continue;
773 }
774 }
775 }
776 next_military_act_ = schedule_act(game, 1000);
777 }
778
779 if (static_cast<int32_t>(next_stock_remove_act_ - gametime) <= 0) {
780 check_remove_stock(game);
781
782 next_stock_remove_act_ = schedule_act(game, 4000);
783 }
784
785 // Update planned workers; this is to update the request amounts and
786 // check because whether we suddenly can produce a requested worker. This
787 // is mostly previously available wares may become unavailable due to
788 // secondary requests.
789 update_all_planned_workers(game);
790
791 Building::act(game, data);
792 }
793
794 /// Transfer our registration to the new economy.
set_economy(Economy * const e,WareWorker type)795 void Warehouse::set_economy(Economy* const e, WareWorker type) {
796 Economy* const old = get_economy(type);
797
798 if (old == e)
799 return;
800
801 if (old)
802 old->remove_warehouse(*this);
803
804 if (portdock_) {
805 portdock_->set_economy(e, type);
806 }
807 supply_->set_economy(e, type);
808 Building::set_economy(e, type);
809
810 for (const PlannedWorkers& pw : planned_workers_) {
811 for (Request* req : pw.requests) {
812 if (req->get_type() == type) {
813 req->set_economy(e);
814 }
815 }
816 }
817
818 if (e)
819 e->add_warehouse(*this);
820 }
821
get_wares() const822 const WareList& Warehouse::get_wares() const {
823 return supply_->get_wares();
824 }
825
get_workers() const826 const WareList& Warehouse::get_workers() const {
827 return supply_->get_workers();
828 }
829
get_incorporated_workers()830 PlayerImmovable::Workers Warehouse::get_incorporated_workers() {
831 PlayerImmovable::Workers all_workers;
832
833 for (const auto& worker_pair : incorporated_workers_) {
834 for (Worker* worker : worker_pair.second) {
835 all_workers.push_back(worker);
836 }
837 }
838 return all_workers;
839 }
840
841 /// Magically create wares in this warehouse. Updates the economy accordingly.
insert_wares(DescriptionIndex const id,Quantity const count)842 void Warehouse::insert_wares(DescriptionIndex const id, Quantity const count) {
843 supply_->add_wares(id, count);
844 }
845
846 /// Magically destroy wares.
remove_wares(DescriptionIndex const id,Quantity const count)847 void Warehouse::remove_wares(DescriptionIndex const id, Quantity const count) {
848 supply_->remove_wares(id, count);
849 }
850
851 /// Magically create workers in this warehouse. Updates the economy accordingly.
insert_workers(DescriptionIndex const id,uint32_t const count)852 void Warehouse::insert_workers(DescriptionIndex const id, uint32_t const count) {
853 supply_->add_workers(id, count);
854 }
855
856 /// Magically destroy workers.
remove_workers(DescriptionIndex const id,uint32_t const count)857 void Warehouse::remove_workers(DescriptionIndex const id, uint32_t const count) {
858 supply_->remove_workers(id, count);
859 }
860
861 /// Launch a carrier to fetch an ware from our flag.
fetch_from_flag(Game & game)862 bool Warehouse::fetch_from_flag(Game& game) {
863 DescriptionIndex const carrierid = owner().tribe().carrier();
864
865 if (!supply_->stock_workers(carrierid)) {
866 if (can_create_worker(game, carrierid)) {
867 create_worker(game, carrierid);
868 }
869 }
870 if (supply_->stock_workers(carrierid)) {
871 launch_worker(game, carrierid, Requirements()).start_task_fetchfromflag(game);
872 }
873
874 return true;
875 }
876
877 /**
878 * \return the number of workers that we can launch satisfying the given
879 * requirements.
880 */
count_workers(const Game &,DescriptionIndex worker_id,const Requirements & req,Match exact)881 Quantity Warehouse::count_workers(const Game& /* game */,
882 DescriptionIndex worker_id,
883 const Requirements& req,
884 Match exact) {
885 Quantity sum = 0;
886
887 do {
888 sum += supply_->stock_workers(worker_id);
889
890 // NOTE: This code lies about the TrainingAttributes of non-instantiated workers.
891 if (incorporated_workers_.count(worker_id)) {
892 for (Worker* worker : incorporated_workers_[worker_id]) {
893 if (!req.check(*worker)) {
894 // This is one of the workers in our sum.
895 // But he is too stupid for this job
896 --sum;
897 }
898 }
899 }
900 if (exact == Match::kCompatible) {
901 worker_id = owner().tribe().get_worker_descr(worker_id)->becomes();
902 } else {
903 worker_id = INVALID_INDEX;
904 }
905 } while (owner().tribe().has_worker(worker_id));
906
907 return sum;
908 }
909
910 /// Start a worker of a given type. The worker will
911 /// be assigned a job by the caller.
launch_worker(Game & game,DescriptionIndex worker_id,const Requirements & req)912 Worker& Warehouse::launch_worker(Game& game, DescriptionIndex worker_id, const Requirements& req) {
913 do {
914 if (supply_->stock_workers(worker_id)) {
915 uint32_t unincorporated = supply_->stock_workers(worker_id);
916
917 // look if we got one of those in stock
918 if (incorporated_workers_.count(worker_id)) {
919 // On cleanup, it could be that the worker was deleted under
920 // us, so we erase the pointer we had to it and create a new
921 // one.
922 remove_no_longer_existing_workers(game, &incorporated_workers_[worker_id]);
923 WorkerList& incorporated_workers = incorporated_workers_[worker_id];
924
925 for (std::vector<Worker*>::iterator worker_iter = incorporated_workers.begin();
926 worker_iter != incorporated_workers.end(); ++worker_iter) {
927 Worker* worker = *worker_iter;
928 --unincorporated;
929
930 if (req.check(*worker)) {
931 worker->reset_tasks(game); // forget everything you did
932 worker->set_location(this); // back in a economy
933 incorporated_workers.erase(worker_iter);
934
935 supply_->remove_workers(worker_id, 1);
936 return *worker;
937 }
938 }
939 }
940
941 assert(unincorporated <= supply_->stock_workers(worker_id));
942
943 if (unincorporated) {
944 // Create a new one
945 // NOTE: This code lies about the TrainingAttributes of the new worker
946 supply_->remove_workers(worker_id, 1);
947 const WorkerDescr& workerdescr = *game.tribes().get_worker_descr(worker_id);
948 return workerdescr.create(game, get_owner(), this, position_);
949 }
950 }
951
952 if (can_create_worker(game, worker_id)) {
953 // don't want to use an upgraded worker, so create new one.
954 create_worker(game, worker_id);
955 } else {
956 worker_id = game.tribes().get_worker_descr(worker_id)->becomes();
957 }
958 } while (owner().tribe().has_worker(worker_id));
959
960 throw wexception("Warehouse::launch_worker: worker does not actually exist");
961 }
962
incorporate_worker(EditorGameBase & egbase,Worker * w)963 void Warehouse::incorporate_worker(EditorGameBase& egbase, Worker* w) {
964 assert(w != nullptr);
965 assert(w->get_owner() == get_owner());
966
967 if (WareInstance* ware = w->fetch_carried_ware(egbase))
968 incorporate_ware(egbase, ware);
969
970 DescriptionIndex worker_index = owner().tribe().worker_index(w->descr().name().c_str());
971
972 supply_->add_workers(worker_index, 1);
973
974 // We remove free workers, but we keep other workers around.
975 // TODO(unknown): Remove all workers that do not have properties such as experience.
976 // And even such workers should be removed and only a small record
977 // with the experience (and possibly other data that must survive)
978 // may be kept.
979 // When this is done, the get_incorporated_workers method above must
980 // be reworked so that workers are recreated, and rescheduled for
981 // incorporation.
982 if (w->descr().is_buildable() && w->descr().buildcost().empty()) {
983 w->remove(egbase);
984 return;
985 }
986
987 // Incorporate the worker
988 if (!incorporated_workers_.count(worker_index))
989 incorporated_workers_[worker_index] = std::vector<Worker*>();
990 incorporated_workers_[worker_index].push_back(w);
991
992 w->set_location(nullptr); // no longer in an economy
993
994 if (upcast(Game, game, &egbase)) {
995 // Bind the worker into this house, hide him on the map.
996 w->reset_tasks(*game);
997 w->start_task_idle(*game, 0, -1);
998 }
999 }
1000
1001 /// Create an instance of a ware and make sure it gets
1002 /// carried out of the warehouse.
launch_ware(Game & game,DescriptionIndex const ware_index)1003 WareInstance& Warehouse::launch_ware(Game& game, DescriptionIndex const ware_index) {
1004 // Create the ware
1005 WareInstance& ware = *new WareInstance(ware_index, owner().tribe().get_ware_descr(ware_index));
1006 ware.init(game);
1007 if (do_launch_ware(game, ware)) {
1008 supply_->remove_wares(ware_index, 1);
1009 }
1010 return ware;
1011 }
1012
1013 /// Get a carrier to actually move this ware out of the warehouse.
do_launch_ware(Game & game,WareInstance & ware)1014 bool Warehouse::do_launch_ware(Game& game, WareInstance& ware) {
1015 // Create a carrier
1016 const DescriptionIndex carrierid = owner().tribe().carrier();
1017
1018 if (!supply_->stock_workers(carrierid)) {
1019 if (can_create_worker(game, carrierid)) {
1020 create_worker(game, carrierid);
1021 }
1022 }
1023 if (supply_->stock_workers(carrierid)) {
1024 Widelands::Worker& worker = launch_worker(game, carrierid, Requirements());
1025 // Setup the carrier
1026 worker.start_task_dropoff(game, ware);
1027 return true;
1028 }
1029
1030 // We did not launch the ware...
1031 return false;
1032 }
1033
incorporate_ware(EditorGameBase & egbase,WareInstance * ware)1034 void Warehouse::incorporate_ware(EditorGameBase& egbase, WareInstance* ware) {
1035 supply_->add_wares(ware->descr_index(), 1);
1036 ware->destroy(egbase);
1037 }
1038
1039 /// Called when a transfer for one of the idle Requests completes.
request_cb(Game & game,Request &,DescriptionIndex const ware,Worker * const w,PlayerImmovable & target)1040 void Warehouse::request_cb(
1041 Game& game, Request&, DescriptionIndex const ware, Worker* const w, PlayerImmovable& target) {
1042 Warehouse& wh = dynamic_cast<Warehouse&>(target);
1043
1044 if (w) {
1045 w->schedule_incorporate(game);
1046 } else {
1047 wh.supply_->add_wares(ware, 1);
1048
1049 // This ware may be used to build planned workers,
1050 // so it seems like a good idea to update the associated requests
1051 // and use the ware before it is sent away again.
1052 wh.update_all_planned_workers(game);
1053 }
1054 }
1055
1056 /**
1057 * Receive a ware from a transfer that was not associated to a \ref Request.
1058 */
receive_ware(Game &,DescriptionIndex ware)1059 void Warehouse::receive_ware(Game& /* game */, DescriptionIndex ware) {
1060 supply_->add_wares(ware, 1);
1061 }
1062
1063 /**
1064 * Receive a worker from a transfer that was not associated to a \ref Request.
1065 */
receive_worker(Game & game,Worker & worker)1066 void Warehouse::receive_worker(Game& game, Worker& worker) {
1067 worker.schedule_incorporate(game);
1068 }
1069
create_object() const1070 Building& WarehouseDescr::create_object() const {
1071 return *new Warehouse(*this);
1072 }
1073
can_create_worker(Game &,DescriptionIndex const worker) const1074 bool Warehouse::can_create_worker(Game&, DescriptionIndex const worker) const {
1075 assert(owner().tribe().has_worker(worker));
1076
1077 if (!(worker < supply_->get_workers().get_nrwareids()))
1078 throw wexception("worker type %d does not exists (max is %d)", worker,
1079 supply_->get_workers().get_nrwareids());
1080
1081 const WorkerDescr& w_desc = *owner().tribe().get_worker_descr(worker);
1082 assert(&w_desc);
1083 if (!w_desc.is_buildable()) {
1084 return false;
1085 }
1086
1087 // see if we have the resources
1088 for (const auto& buildcost : w_desc.buildcost()) {
1089 const std::string& input_name = buildcost.first;
1090 DescriptionIndex id_w = owner().tribe().ware_index(input_name);
1091 if (owner().tribe().has_ware(id_w)) {
1092 if (supply_->stock_wares(id_w) < buildcost.second) {
1093 return false;
1094 }
1095 } else {
1096 id_w = owner().tribe().worker_index(input_name);
1097 if (owner().tribe().has_worker(id_w)) {
1098 if (supply_->stock_workers(id_w) < buildcost.second) {
1099 return false;
1100 }
1101 } else
1102 throw wexception("worker type %s needs \"%s\" to be built but that is neither "
1103 "a ware type nor a worker type defined in the tribe %s",
1104 w_desc.name().c_str(), input_name.c_str(),
1105 owner().tribe().name().c_str());
1106 }
1107 }
1108 return true;
1109 }
1110
create_worker(Game & game,DescriptionIndex const worker)1111 void Warehouse::create_worker(Game& game, DescriptionIndex const worker) {
1112 assert(can_create_worker(game, worker));
1113
1114 const WorkerDescr& w_desc = *owner().tribe().get_worker_descr(worker);
1115
1116 for (const auto& buildcost : w_desc.buildcost()) {
1117 const std::string& input = buildcost.first;
1118 DescriptionIndex const id_ware = owner().tribe().ware_index(input);
1119 if (owner().tribe().has_ware(id_ware)) {
1120 remove_wares(id_ware, buildcost.second);
1121 // Update statistics accordingly
1122 get_owner()->ware_consumed(id_ware, buildcost.second);
1123 } else
1124 remove_workers(owner().tribe().safe_worker_index(input), buildcost.second);
1125 }
1126
1127 incorporate_worker(game, &w_desc.create(game, get_owner(), this, position_));
1128
1129 // Update PlannedWorkers::amount here if appropriate, because this function
1130 // may have been called directly by the Economy.
1131 // Do not update anything else about PlannedWorkers here, because this
1132 // function is called by update_planned_workers, so avoid recursion
1133 for (PlannedWorkers& planned_worker : planned_workers_) {
1134 if (planned_worker.index == worker && planned_worker.amount)
1135 planned_worker.amount--;
1136 }
1137 }
1138
1139 /**
1140 * Return the number of workers of the given type that we plan to
1141 * create in this warehouse.
1142 */
get_planned_workers(Game &,DescriptionIndex index) const1143 Quantity Warehouse::get_planned_workers(Game& /* game */, DescriptionIndex index) const {
1144 for (const PlannedWorkers& pw : planned_workers_) {
1145 if (pw.index == index)
1146 return pw.amount;
1147 }
1148 return 0;
1149 }
1150
1151 /**
1152 * Calculate the supply of wares available to this warehouse in each of the
1153 * buildcost wares for the given worker.
1154 *
1155 * This is the current stock plus any incoming transfers.
1156 */
calc_available_for_worker(Game &,DescriptionIndex index) const1157 std::vector<Quantity> Warehouse::calc_available_for_worker(Game& /* game */,
1158 DescriptionIndex index) const {
1159 const WorkerDescr& w_desc = *owner().tribe().get_worker_descr(index);
1160 std::vector<uint32_t> available;
1161
1162 for (const auto& buildcost : w_desc.buildcost()) {
1163 const std::string& input_name = buildcost.first;
1164 DescriptionIndex id_w = owner().tribe().ware_index(input_name);
1165 if (owner().tribe().has_ware(id_w)) {
1166 available.push_back(get_wares().stock(id_w));
1167 } else {
1168 id_w = owner().tribe().worker_index(input_name);
1169 if (owner().tribe().has_worker(id_w)) {
1170 available.push_back(get_workers().stock(id_w));
1171 } else
1172 throw wexception("Economy::create_requested_worker: buildcost inconsistency '%s'",
1173 input_name.c_str());
1174 }
1175 }
1176
1177 for (const PlannedWorkers& pw : planned_workers_) {
1178 if (pw.index == index) {
1179 assert(available.size() == pw.requests.size());
1180
1181 for (uint32_t idx = 0; idx < available.size(); ++idx) {
1182 available[idx] += pw.requests[idx]->get_num_transfers();
1183 }
1184 }
1185 }
1186
1187 return available;
1188 }
1189
1190 /**
1191 * Set the amount of workers we plan to create
1192 * of the given \p index to \p amount.
1193 */
plan_workers(Game & game,DescriptionIndex index,Quantity amount)1194 void Warehouse::plan_workers(Game& game, DescriptionIndex index, Quantity amount) {
1195 PlannedWorkers* pw = nullptr;
1196
1197 for (PlannedWorkers& planned_worker : planned_workers_) {
1198 if (planned_worker.index == index) {
1199 pw = &planned_worker;
1200 break;
1201 }
1202 }
1203
1204 if (!pw) {
1205 if (!amount)
1206 return;
1207
1208 planned_workers_.push_back(PlannedWorkers());
1209 pw = &planned_workers_.back();
1210 pw->index = index;
1211 pw->amount = 0;
1212
1213 const WorkerDescr& w_desc = *owner().tribe().get_worker_descr(pw->index);
1214 for (const auto& buildcost : w_desc.buildcost()) {
1215 const std::string& input_name = buildcost.first;
1216
1217 DescriptionIndex id_w = owner().tribe().ware_index(input_name);
1218 if (owner().tribe().has_ware(id_w)) {
1219 pw->requests.push_back(new Request(*this, id_w, &Warehouse::request_cb, wwWARE));
1220 } else {
1221 id_w = owner().tribe().worker_index(input_name);
1222 if (owner().tribe().has_worker(id_w)) {
1223 pw->requests.push_back(new Request(*this, id_w, &Warehouse::request_cb, wwWORKER));
1224 } else
1225 throw wexception("plan_workers: bad buildcost '%s'", input_name.c_str());
1226 }
1227 }
1228 }
1229
1230 pw->amount = amount;
1231 update_planned_workers(game, *pw);
1232 }
1233
1234 /**
1235 * See if we can create the workers of the given plan,
1236 * and update requests accordingly.
1237 */
update_planned_workers(Game & game,Warehouse::PlannedWorkers & pw)1238 void Warehouse::update_planned_workers(Game& game, Warehouse::PlannedWorkers& pw) {
1239 const WorkerDescr& w_desc = *owner().tribe().get_worker_descr(pw.index);
1240
1241 while (pw.amount && can_create_worker(game, pw.index)) {
1242 create_worker(game, pw.index);
1243 }
1244
1245 uint32_t idx = 0;
1246 for (const auto& buildcost : w_desc.buildcost()) {
1247
1248 const std::string& input_name = buildcost.first;
1249 Quantity supply;
1250
1251 DescriptionIndex id_w = owner().tribe().ware_index(input_name);
1252 if (owner().tribe().has_ware(id_w)) {
1253 supply = supply_->stock_wares(id_w);
1254 } else {
1255 id_w = owner().tribe().worker_index(input_name);
1256 if (owner().tribe().has_worker(id_w)) {
1257 supply = supply_->stock_workers(id_w);
1258 } else
1259 throw wexception("update_planned_workers: bad buildcost '%s'", input_name.c_str());
1260 }
1261 if (supply >= pw.amount * buildcost.second)
1262 pw.requests[idx]->set_count(0);
1263 else
1264 pw.requests[idx]->set_count(pw.amount * buildcost.second - supply);
1265 ++idx;
1266 }
1267
1268 while (pw.requests.size() > idx) {
1269 delete pw.requests.back();
1270 pw.requests.pop_back();
1271 }
1272 }
1273
1274 /**
1275 * Check all planned worker creations.
1276 *
1277 * Needs to be called periodically, because some necessary supplies might arrive
1278 * due to idle transfers instead of by explicit request.
1279 */
update_all_planned_workers(Game & game)1280 void Warehouse::update_all_planned_workers(Game& game) {
1281 uint32_t idx = 0;
1282 while (idx < planned_workers_.size()) {
1283 update_planned_workers(game, planned_workers_[idx]);
1284
1285 if (!planned_workers_[idx].amount) {
1286 planned_workers_[idx].cleanup();
1287 planned_workers_.erase(planned_workers_.begin() + idx);
1288 } else {
1289 idx++;
1290 }
1291 }
1292 }
1293
enable_spawn(Game & game,uint8_t const worker_types_without_cost_index)1294 void Warehouse::enable_spawn(Game& game, uint8_t const worker_types_without_cost_index) {
1295 assert(next_worker_without_cost_spawn_[worker_types_without_cost_index] == never());
1296 next_worker_without_cost_spawn_[worker_types_without_cost_index] =
1297 schedule_act(game, WORKER_WITHOUT_COST_SPAWN_INTERVAL);
1298 }
1299
cleanup()1300 void Warehouse::PlannedWorkers::cleanup() {
1301 while (!requests.empty()) {
1302 delete requests.back();
1303 requests.pop_back();
1304 }
1305 }
1306
get_ware_policy(DescriptionIndex ware) const1307 StockPolicy Warehouse::get_ware_policy(DescriptionIndex ware) const {
1308 assert(ware < static_cast<DescriptionIndex>(ware_policy_.size()));
1309 return ware_policy_[ware];
1310 }
1311
get_worker_policy(DescriptionIndex ware) const1312 StockPolicy Warehouse::get_worker_policy(DescriptionIndex ware) const {
1313 assert(ware < static_cast<DescriptionIndex>(worker_policy_.size()));
1314 return worker_policy_[ware];
1315 }
1316
get_stock_policy(WareWorker waretype,DescriptionIndex wareindex) const1317 StockPolicy Warehouse::get_stock_policy(WareWorker waretype, DescriptionIndex wareindex) const {
1318 if (waretype == wwWORKER)
1319 return get_worker_policy(wareindex);
1320 else
1321 return get_ware_policy(wareindex);
1322 }
1323
set_ware_policy(DescriptionIndex ware,StockPolicy policy)1324 void Warehouse::set_ware_policy(DescriptionIndex ware, StockPolicy policy) {
1325 assert(ware < static_cast<DescriptionIndex>(ware_policy_.size()));
1326 ware_policy_[ware] = policy;
1327 }
1328
set_worker_policy(DescriptionIndex ware,StockPolicy policy)1329 void Warehouse::set_worker_policy(DescriptionIndex ware, StockPolicy policy) {
1330 assert(ware < static_cast<DescriptionIndex>(worker_policy_.size()));
1331 worker_policy_[ware] = policy;
1332 }
1333
1334 /**
1335 * Check if there are remaining wares with \ref StockPolicy::kRemove,
1336 * and remove one of them if appropriate.
1337 */
check_remove_stock(Game & game)1338 void Warehouse::check_remove_stock(Game& game) {
1339 if (base_flag().current_wares() < base_flag().total_capacity() / 2) {
1340 for (DescriptionIndex ware = 0; ware < static_cast<DescriptionIndex>(ware_policy_.size());
1341 ++ware) {
1342 if (get_ware_policy(ware) != StockPolicy::kRemove || !get_wares().stock(ware))
1343 continue;
1344
1345 launch_ware(game, ware);
1346 break;
1347 }
1348 }
1349
1350 for (DescriptionIndex widx = 0; widx < static_cast<DescriptionIndex>(worker_policy_.size());
1351 ++widx) {
1352 if (get_worker_policy(widx) != StockPolicy::kRemove || !get_workers().stock(widx))
1353 continue;
1354
1355 Worker& worker = launch_worker(game, widx, Requirements());
1356 worker.start_task_leavebuilding(game, true);
1357 break;
1358 }
1359 }
1360
1361 // TODO(Nordfriese): Called by a Request/Transfer/WareInstance/whatever that enters
1362 // the expedition bootstrap. Should instead return the InputQueue that requested
1363 // this particular item. See discussion in PR #3884.
inputqueue(DescriptionIndex index,WareWorker type)1364 InputQueue& Warehouse::inputqueue(DescriptionIndex index, WareWorker type) {
1365 assert(portdock_ != nullptr);
1366 assert(portdock_->expedition_bootstrap() != nullptr);
1367 return portdock_->expedition_bootstrap()->first_empty_inputqueue(index, type);
1368 }
1369
create_building_settings() const1370 const BuildingSettings* Warehouse::create_building_settings() const {
1371 WarehouseSettings* settings = new WarehouseSettings(descr(), owner().tribe());
1372 for (auto& pair : settings->ware_preferences) {
1373 pair.second = get_ware_policy(pair.first);
1374 }
1375 for (auto& pair : settings->worker_preferences) {
1376 pair.second = get_worker_policy(pair.first);
1377 }
1378 settings->launch_expedition = portdock_ && portdock_->expedition_started();
1379 return settings;
1380 }
1381
log_general_info(const EditorGameBase & egbase) const1382 void Warehouse::log_general_info(const EditorGameBase& egbase) const {
1383 Building::log_general_info(egbase);
1384
1385 if (descr().get_isport()) {
1386 if (portdock_) {
1387 molog("Port dock: %u\n", portdock_->serial());
1388 molog("wares and workers waiting: %u\n", portdock_->count_waiting());
1389 molog("exped. in progr.: %s\n", (portdock_->expedition_started()) ? "true" : "false");
1390 ShipFleet* fleet = portdock_->get_fleet();
1391 if (fleet) {
1392 molog("* fleet: %u\n", fleet->serial());
1393 molog(" ships: %u, ports: %u\n", fleet->count_ships(), fleet->count_ports());
1394 molog(" act_pending: %s\n", (fleet->get_act_pending()) ? "true" : "false");
1395 } else {
1396 molog("No fleet?!\n");
1397 }
1398 } else {
1399 molog("No port dock!?\n");
1400 }
1401 }
1402 }
1403 } // namespace Widelands
1404