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 "economy/request.h"
21 
22 #include "base/macros.h"
23 #include "economy/economy.h"
24 #include "economy/portdock.h"
25 #include "economy/transfer.h"
26 #include "economy/ware_instance.h"
27 #include "io/fileread.h"
28 #include "io/filewrite.h"
29 #include "logic/game.h"
30 #include "logic/map_objects/tribes/constructionsite.h"
31 #include "logic/map_objects/tribes/productionsite.h"
32 #include "logic/map_objects/tribes/soldier.h"
33 #include "logic/map_objects/tribes/tribe_descr.h"
34 #include "logic/map_objects/tribes/warehouse.h"
35 #include "logic/map_objects/tribes/worker.h"
36 #include "logic/player.h"
37 #include "map_io/map_object_loader.h"
38 #include "map_io/map_object_saver.h"
39 
40 namespace Widelands {
41 
42 /*
43 ==============================================================================
44 
45 Request IMPLEMENTATION
46 
47 ==============================================================================
48 */
49 
Request(PlayerImmovable & init_target,DescriptionIndex const index,CallbackFn const cbfn,WareWorker const w)50 Request::Request(PlayerImmovable& init_target,
51                  DescriptionIndex const index,
52                  CallbackFn const cbfn,
53                  WareWorker const w)
54    : type_(w),
55      target_(init_target),
56      target_building_(dynamic_cast<Building*>(&init_target)),
57      target_productionsite_(dynamic_cast<ProductionSite*>(&init_target)),
58      target_warehouse_(dynamic_cast<Warehouse*>(&init_target)),
59      target_constructionsite_(dynamic_cast<ConstructionSite*>(&init_target)),
60      economy_(init_target.get_economy(w)),
61      index_(index),
62      count_(1),
63      exact_match_(false),
64      callbackfn_(cbfn),
65      required_time_(init_target.owner().egbase().get_gametime()),
66      required_interval_(0),
67      last_request_time_(required_time_) {
68 	assert(type_ == wwWARE || type_ == wwWORKER);
69 	if (w == wwWARE && !init_target.owner().egbase().tribes().ware_exists(index))
70 		throw wexception(
71 		   "creating ware request with index %u, but the ware for this index doesn't exist", index);
72 	if (w == wwWORKER && !init_target.owner().egbase().tribes().worker_exists(index))
73 		throw wexception(
74 		   "creating worker request with index %u, but the worker for this index doesn't exist",
75 		   index);
76 	if (economy_)
77 		economy_->add_request(*this);
78 }
79 
~Request()80 Request::~Request() {
81 	// Remove from the economy
82 	if (is_open() && economy_)
83 		economy_->remove_request(*this);
84 
85 	// Cancel all ongoing transfers
86 	while (transfers_.size())
87 		cancel_transfer(0);
88 }
89 
90 // Modified to allow Requirements and SoldierRequests
91 constexpr uint16_t kCurrentPacketVersion = 6;
92 
93 /**
94  * Read this request from a file
95  *
96  * it is most probably created by some init function,
97  * so ad least target/economy is correct. Some Transports
98  * might have been initialized. We have to kill them and replace
99  * them through the data in the file
100  */
read(FileRead & fr,Game & game,MapObjectLoader & mol,const TribesLegacyLookupTable & tribes_lookup_table)101 void Request::read(FileRead& fr,
102                    Game& game,
103                    MapObjectLoader& mol,
104                    const TribesLegacyLookupTable& tribes_lookup_table) {
105 	try {
106 		uint16_t const packet_version = fr.unsigned_16();
107 		if (packet_version == kCurrentPacketVersion) {
108 			const TribeDescr& tribe = target_.owner().tribe();
109 			char const* const type_name = fr.c_string();
110 			DescriptionIndex const wai = tribe.ware_index(tribes_lookup_table.lookup_ware(type_name));
111 			if (tribe.has_ware(wai)) {
112 				type_ = wwWARE;
113 				index_ = wai;
114 			} else {
115 				DescriptionIndex const woi =
116 				   tribe.worker_index(tribes_lookup_table.lookup_worker(type_name));
117 				if (tribe.has_worker(woi)) {
118 					type_ = wwWORKER;
119 					index_ = woi;
120 				} else {
121 					throw wexception("Request::read: unknown type '%s'.\n", type_name);
122 				}
123 			}
124 
125 			// Overwrite initial economy because our WareWorker type may have changed
126 			if (economy_) {
127 				economy_->remove_request(*this);
128 			}
129 			economy_ = target_.get_economy(type_);
130 			assert(economy_);
131 
132 			count_ = fr.unsigned_32();
133 			required_time_ = fr.unsigned_32();
134 			required_interval_ = fr.unsigned_32();
135 
136 			last_request_time_ = fr.unsigned_32();
137 
138 			assert(transfers_.empty());
139 
140 			uint16_t const nr_transfers = fr.unsigned_16();
141 			for (uint16_t i = 0; i < nr_transfers; ++i)
142 				try {
143 					MapObject* obj = &mol.get<MapObject>(fr.unsigned_32());
144 					Transfer* transfer;
145 
146 					if (upcast(Worker, worker, obj)) {
147 						transfer = worker->get_transfer();
148 						if (type_ != wwWORKER || !worker->descr().can_act_as(index_)) {
149 							throw wexception("Request::read: incompatible transfer type");
150 						}
151 					} else if (upcast(WareInstance, ware, obj)) {
152 						transfer = ware->get_transfer();
153 						if (type_ != wwWARE || ware->descr_index() != index_) {
154 							throw wexception("Request::read: incompatible transfer type");
155 						}
156 					} else {
157 						throw wexception("transfer target %u is neither ware nor worker", obj->serial());
158 					}
159 
160 					if (!transfer) {
161 						log("WARNING: loading request, transferred object %u has no transfer\n",
162 						    obj->serial());
163 					} else {
164 						transfer->set_request(this);
165 						transfers_.push_back(transfer);
166 					}
167 				} catch (const WException& e) {
168 					throw wexception("transfer %u: %s", i, e.what());
169 				}
170 			requirements_.read(fr, game, mol);
171 			if (is_open()) {
172 				economy_->add_request(*this);
173 			}
174 		} else {
175 			throw UnhandledVersionError("Request", packet_version, kCurrentPacketVersion);
176 		}
177 	} catch (const WException& e) {
178 		throw wexception("request: %s", e.what());
179 	}
180 }
181 
182 /**
183  * Write this request to a file
184  */
write(FileWrite & fw,Game & game,MapObjectSaver & mos) const185 void Request::write(FileWrite& fw, Game& game, MapObjectSaver& mos) const {
186 	fw.unsigned_16(kCurrentPacketVersion);
187 
188 	//  Target and economy should be set. Same is true for callback stuff.
189 
190 	assert(type_ == wwWARE || type_ == wwWORKER);
191 	switch (type_) {
192 	case wwWARE:
193 		assert(game.tribes().ware_exists(index_));
194 		fw.c_string(game.tribes().get_ware_descr(index_)->name());
195 		break;
196 	case wwWORKER:
197 		assert(game.tribes().worker_exists(index_));
198 		fw.c_string(game.tribes().get_worker_descr(index_)->name());
199 		break;
200 	}
201 
202 	fw.unsigned_32(count_);
203 
204 	fw.unsigned_32(required_time_);
205 	fw.unsigned_32(required_interval_);
206 
207 	fw.unsigned_32(last_request_time_);
208 
209 	fw.unsigned_16(transfers_.size());  //  Write number of current transfers.
210 	for (uint32_t i = 0; i < transfers_.size(); ++i) {
211 		Transfer& trans = *transfers_[i];
212 		if (trans.ware_) {  //  write ware/worker
213 			assert(mos.is_object_known(*trans.ware_));
214 			fw.unsigned_32(mos.get_object_file_index(*trans.ware_));
215 		} else if (trans.worker_) {
216 			assert(mos.is_object_known(*trans.worker_));
217 			fw.unsigned_32(mos.get_object_file_index(*trans.worker_));
218 		}
219 	}
220 	requirements_.write(fw, game, mos);
221 }
222 
223 /**
224  * Figure out the flag we need to deliver to.
225  */
target_flag() const226 Flag& Request::target_flag() const {
227 	return target().base_flag();
228 }
229 
230 /**
231  * Return the point in time at which we want the ware of the given number to
232  * be delivered. nr is in the range [0..count_[
233  */
get_base_required_time(EditorGameBase & egbase,uint32_t const nr) const234 int32_t Request::get_base_required_time(EditorGameBase& egbase, uint32_t const nr) const {
235 	if (count_ <= nr) {
236 		if (!(count_ == 1 && nr == 1)) {
237 			log("Request::get_base_required_time: WARNING nr = %u but count is %u, "
238 			    "which is not allowed according to the comment for this function\n",
239 			    nr, count_);
240 		}
241 	}
242 	int32_t const curtime = egbase.get_gametime();
243 
244 	if (!nr || !required_interval_)
245 		return required_time_;
246 
247 	if ((curtime - required_time_) > (required_interval_ * 2)) {
248 		if (nr == 1)
249 			return required_time_ + (curtime - required_time_) / 2;
250 
251 		assert(2 <= nr);
252 		return curtime + (nr - 2) * required_interval_;
253 	}
254 
255 	return required_time_ + nr * required_interval_;
256 }
257 
258 /**
259  * Return the time when the requested ware is needed.
260  * Can be in the past, indicating that we have been idling, waiting for the
261  * ware.
262  */
get_required_time() const263 int32_t Request::get_required_time() const {
264 	return get_base_required_time(economy_->owner().egbase(), transfers_.size());
265 }
266 
267 #define PRIORITY_MAX_COST 50000
268 #define COST_WEIGHT_IN_PRIORITY 1
269 #define WAITTIME_WEIGHT_IN_PRIORITY 2
270 
271 /**
272  * Return the request priority used to sort requests or -1 to skip request
273  */
274 // TODO(sirver): this is pretty weird design: we ask the building for the
275 // priority it assigns to the ware, at the same time, we also adjust the
276 // priorities depending on the building type. Move all of this into the
277 // building code.
get_priority(int32_t cost) const278 int32_t Request::get_priority(int32_t cost) const {
279 	int MAX_IDLE_PRIORITY = 100;
280 	bool is_construction_site = false;
281 	int32_t modifier = kPriorityNormal;
282 
283 	if (target_building_) {
284 		modifier = target_building_->get_priority(get_type(), get_index());
285 		if (target_constructionsite_)
286 			is_construction_site = true;
287 		else if (target_warehouse_) {
288 			// If there is no expedition at this warehouse, use the default
289 			// warehouse calculation. Otherwise we use the default priority for
290 			// the ware.
291 			if (!target_warehouse_->get_portdock() ||
292 			    !target_warehouse_->get_portdock()->expedition_bootstrap()) {
293 				modifier =
294 				   std::max(1, MAX_IDLE_PRIORITY - cost * MAX_IDLE_PRIORITY / PRIORITY_MAX_COST);
295 			}
296 		}
297 	}
298 
299 	if (cost > PRIORITY_MAX_COST)
300 		cost = PRIORITY_MAX_COST;
301 
302 	// priority is higher if building waits for ware a long time
303 	// additional factor - cost to deliver, so nearer building
304 	// with same priority will get ware first
305 	//  make sure that idle request are lower
306 	return MAX_IDLE_PRIORITY +
307 	       std::max(uint32_t(1),
308 	                ((economy_->owner().egbase().get_gametime() -
309 	                  (is_construction_site ? get_required_time() : get_last_request_time())) *
310 	                    WAITTIME_WEIGHT_IN_PRIORITY +
311 	                 (PRIORITY_MAX_COST - cost) * COST_WEIGHT_IN_PRIORITY) *
312 	                   modifier);
313 }
314 
315 /**
316  * Return the transfer priority, based on the priority set at the destination
317  */
318 // TODO(sirver): Same comment as for Request::get_priority.
get_transfer_priority() const319 uint32_t Request::get_transfer_priority() const {
320 	uint32_t pri = 0;
321 
322 	if (target_building_) {
323 		pri = target_building_->get_priority(get_type(), get_index());
324 		if (target_constructionsite_)
325 			return pri + 3;
326 		else if (target_warehouse_)
327 			return pri - 2;
328 	}
329 	return pri;
330 }
331 
332 /**
333  * Change the Economy we belong to.
334  */
set_economy(Economy * const e)335 void Request::set_economy(Economy* const e) {
336 	if (economy_ != e) {
337 		if (economy_ && is_open())
338 			economy_->remove_request(*this);
339 		economy_ = e;
340 		if (economy_ && is_open())
341 			economy_->add_request(*this);
342 	}
343 }
344 
345 /**
346  * Change the number of wares we need.
347  */
set_count(uint32_t const count)348 void Request::set_count(uint32_t const count) {
349 	bool const wasopen = is_open();
350 
351 	count_ = count;
352 
353 	// Cancel unneeded transfers. This should be more clever about which
354 	// transfers to cancel. Then again, this loop shouldn't execute during
355 	// normal play anyway
356 	while (count_ < transfers_.size())
357 		cancel_transfer(transfers_.size() - 1);
358 
359 	// Update the economy
360 	if (economy_) {
361 		if (wasopen && !is_open())
362 			economy_->remove_request(*this);
363 		else if (!wasopen && is_open())
364 			economy_->add_request(*this);
365 	}
366 }
367 
368 /**
369  * Sets whether a worker supply has to match exactly or if a can_act_as() comparison is good enough.
370  */
set_exact_match(bool match)371 void Request::set_exact_match(bool match) {
372 	exact_match_ = match;
373 }
374 
375 /**
376  * Change the time at which the first ware to be delivered is needed.
377  * Default is the gametime of the Request creation.
378  */
set_required_time(int32_t const time)379 void Request::set_required_time(int32_t const time) {
380 	required_time_ = time;
381 }
382 
383 /**
384  * Change the time between desired delivery of wares.
385  */
set_required_interval(int32_t const interval)386 void Request::set_required_interval(int32_t const interval) {
387 	required_interval_ = interval;
388 }
389 
390 /**
391  * Begin transfer of the requested ware from the given supply.
392  * This function does not take ownership of route, i.e. the caller is
393  * responsible for its deletion.
394  */
start_transfer(Game & game,Supply & supp)395 void Request::start_transfer(Game& game, Supply& supp) {
396 	assert(is_open());
397 
398 	::StreamWrite& ss = game.syncstream();
399 	ss.unsigned_8(SyncEntry::kStartTransfer);
400 	ss.unsigned_32(target().serial());
401 	ss.unsigned_32(supp.get_position(game)->serial());
402 
403 	Transfer* t;
404 	switch (get_type()) {
405 	case wwWORKER: {
406 		//  Begin the transfer of a soldier or worker.
407 		//  launch_worker() creates or starts the worker
408 		Worker& s = supp.launch_worker(game, *this);
409 		ss.unsigned_32(s.serial());
410 		t = new Transfer(game, *this, s);
411 		break;
412 	}
413 	case wwWARE: {
414 		//  Begin the transfer of n ware. The ware itself is passive.
415 		//  launch_ware() ensures the WareInstance is transported out of the
416 		//  warehouse. Once it's on the flag, the flag code will decide what to
417 		//  do with it.
418 		WareInstance& ware = supp.launch_ware(game, *this);
419 		ss.unsigned_32(ware.serial());
420 		t = new Transfer(game, *this, ware);
421 		break;
422 	}
423 	}
424 
425 	transfers_.push_back(t);
426 	if (!is_open())
427 		economy_->remove_request(*this);
428 }
429 
430 /**
431  * Callback from ware/worker code that the requested ware has arrived.
432  * This will call a callback function in the target, which is then responsible
433  * for removing and deleting the request.
434  */
transfer_finish(Game & game,Transfer & t)435 void Request::transfer_finish(Game& game, Transfer& t) {
436 	Worker* const w = t.worker_;
437 
438 	if (t.ware_)
439 		t.ware_->destroy(game);
440 
441 	t.worker_ = nullptr;
442 	t.ware_ = nullptr;
443 
444 	remove_transfer(find_transfer(t));
445 
446 	set_required_time(get_base_required_time(game, 1));
447 	--count_;
448 
449 	// the callback functions are likely to delete us,
450 	// therefore we musn't access member variables behind this
451 	// point
452 	(*callbackfn_)(game, *this, index_, w, target_);
453 }
454 
455 /**
456  * Callback from ware/worker code that the scheduled transfer has failed.
457  * The calling code has already dealt with the worker/ware.
458  *
459  * Re-open the request.
460  */
transfer_fail(Game &,Transfer & t)461 void Request::transfer_fail(Game&, Transfer& t) {
462 	bool const wasopen = is_open();
463 
464 	t.worker_ = nullptr;
465 	t.ware_ = nullptr;
466 
467 	remove_transfer(find_transfer(t));
468 
469 	if (!wasopen)
470 		economy_->add_request(*this);
471 }
472 
473 /// Cancel the transfer with the given index.
474 ///
475 /// \note This does *not* update whether the \ref Request is registered with
476 /// the \ref Economy or not.
cancel_transfer(uint32_t const idx)477 void Request::cancel_transfer(uint32_t const idx) {
478 	remove_transfer(idx);
479 }
480 
481 /**
482  * Remove and free the transfer with the given index.
483  * This does not update the Transfer's worker or ware, and it does not update
484  * whether the Request is registered with the Economy.
485  */
remove_transfer(uint32_t const idx)486 void Request::remove_transfer(uint32_t const idx) {
487 	Transfer* const t = transfers_[idx];
488 
489 	transfers_.erase(transfers_.begin() + idx);
490 
491 	delete t;
492 }
493 
494 /**
495  * Lookup a \ref Transfer in the transfers array.
496  * \throw wexception if the \ref Transfer is not registered with us.
497  */
find_transfer(Transfer & t)498 uint32_t Request::find_transfer(Transfer& t) {
499 	TransferList::const_iterator const it = std::find(transfers_.begin(), transfers_.end(), &t);
500 
501 	if (it == transfers_.end())
502 		throw wexception("Request::find_transfer(): not found");
503 
504 	return it - transfers_.begin();
505 }
506 }  // namespace Widelands
507