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