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