1 /*
2 * Copyright (C) 2004-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/ware_instance.h"
21
22 #include <memory>
23
24 #include "base/macros.h"
25 #include "base/wexception.h"
26 #include "economy/economy.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/transfer.h"
32 #include "io/fileread.h"
33 #include "io/filewrite.h"
34 #include "logic/game.h"
35 #include "logic/map_objects/tribes/ship.h"
36 #include "logic/map_objects/tribes/tribe_descr.h"
37 #include "logic/map_objects/tribes/warehouse.h"
38 #include "logic/map_objects/tribes/worker.h"
39 #include "map_io/map_object_loader.h"
40 #include "map_io/map_object_saver.h"
41
42 namespace Widelands {
43
44 /**
45 * Whenever a WareInstance is idle, it issues an IdleWareSupply.
46 */
47 // TODO(unknown): This maybe shouldn't be here.
48 struct IdleWareSupply : public Supply {
49 explicit IdleWareSupply(WareInstance&);
50 ~IdleWareSupply() override;
51
52 void set_economy(Economy*);
53
54 // implementation of Supply
55 PlayerImmovable* get_position(Game&) override;
56 bool is_active() const override;
57 SupplyProviders provider_type(Game*) const override;
58 bool has_storage() const override;
59 void get_ware_type(WareWorker& type, DescriptionIndex& ware) const override;
60 void send_to_storage(Game&, Warehouse* wh) override;
61
62 uint32_t nr_supplies(const Game&, const Request&) const override;
63 WareInstance& launch_ware(Game&, const Request&) override;
64 Worker& launch_worker(Game&, const Request&) override;
65
66 private:
67 WareInstance& ware_;
68 Economy* economy_;
69 };
70
71 /**
72 * Initialize the Supply and update the economy.
73 */
IdleWareSupply(WareInstance & ware)74 IdleWareSupply::IdleWareSupply(WareInstance& ware) : ware_(ware), economy_(nullptr) {
75 set_economy(ware.get_economy());
76 }
77
78 /**
79 * Cleanup.
80 */
~IdleWareSupply()81 IdleWareSupply::~IdleWareSupply() {
82 set_economy(nullptr);
83 }
84
85 /**
86 * Add/remove self from economies as necessary.
87 */
set_economy(Economy * const e)88 void IdleWareSupply::set_economy(Economy* const e) {
89 if (economy_ != e) {
90 if (economy_)
91 economy_->remove_supply(*this);
92 if ((economy_ = e))
93 economy_->add_supply(*this);
94 }
95 }
96
97 /**
98 * Figure out the player immovable that this ware belongs to.
99 */
get_position(Game & game)100 PlayerImmovable* IdleWareSupply::get_position(Game& game) {
101 MapObject* const loc = ware_.get_location(game);
102
103 if (upcast(PlayerImmovable, playerimmovable, loc))
104 return playerimmovable;
105
106 if (upcast(Worker, worker, loc))
107 return worker->get_location(game);
108
109 if (upcast(Ship, ship, loc)) {
110 if (PortDock* pd = ship->get_destination()) {
111 return pd;
112 }
113 return ship->get_fleet()->get_arbitrary_dock();
114 }
115
116 return nullptr;
117 }
118
is_active() const119 bool IdleWareSupply::is_active() const {
120 return true;
121 }
122
provider_type(Game * game) const123 SupplyProviders IdleWareSupply::provider_type(Game* game) const {
124 MapObject* const loc = ware_.get_location(*game);
125 if (is_a(Ship, loc)) {
126 return SupplyProviders::kShip;
127 }
128 if (upcast(Worker, worker, loc)) {
129 if (worker->is_shipping()) {
130 return SupplyProviders::kShip;
131 }
132 }
133 return SupplyProviders::kFlagOrRoad;
134 }
135
has_storage() const136 bool IdleWareSupply::has_storage() const {
137 return ware_.is_moving();
138 }
139
get_ware_type(WareWorker & type,DescriptionIndex & ware) const140 void IdleWareSupply::get_ware_type(WareWorker& type, DescriptionIndex& ware) const {
141 type = wwWARE;
142 ware = ware_.descr_index();
143 }
144
nr_supplies(const Game &,const Request & req) const145 uint32_t IdleWareSupply::nr_supplies(const Game&, const Request& req) const {
146 if (req.get_type() == wwWARE && req.get_index() == ware_.descr_index())
147 return 1;
148
149 return 0;
150 }
151
152 /**
153 * The ware is already "launched", so we only need to return it.
154 */
launch_ware(Game &,const Request & req)155 WareInstance& IdleWareSupply::launch_ware(Game&, const Request& req) {
156 if (req.get_type() != wwWARE)
157 throw wexception("IdleWareSupply::launch_ware : called for non-ware request");
158 if (req.get_index() != ware_.descr_index())
159 throw wexception("IdleWareSupply: ware(%u) (type = %i) requested for %i", ware_.serial(),
160 ware_.descr_index(), req.get_index());
161
162 return ware_;
163 }
164
launch_worker(Game &,const Request &)165 Worker& IdleWareSupply::launch_worker(Game&, const Request&) {
166 throw wexception("IdleWareSupply::launch_worker makes no sense");
167 }
168
send_to_storage(Game & game,Warehouse * wh)169 void IdleWareSupply::send_to_storage(Game& game, Warehouse* wh) {
170 assert(!has_storage());
171
172 Transfer* t = new Transfer(game, ware_);
173 t->set_destination(*wh);
174 ware_.set_transfer(game, *t);
175 }
176
177 /*************************************************************************/
178 /* Ware Instance Implementation */
179 /*************************************************************************/
WareInstance(DescriptionIndex const i,const WareDescr * const ware_descr)180 WareInstance::WareInstance(DescriptionIndex const i, const WareDescr* const ware_descr)
181 : MapObject(ware_descr),
182 economy_(nullptr),
183 descr_index_(i),
184 supply_(nullptr),
185 transfer_(nullptr) {
186 }
187
~WareInstance()188 WareInstance::~WareInstance() {
189 if (supply_) {
190 FORMAT_WARNINGS_OFF
191 molog("Ware %u still has supply %p\n", descr_index_, supply_);
192 FORMAT_WARNINGS_ON
193 delete supply_;
194 }
195 }
196
init(EditorGameBase & egbase)197 bool WareInstance::init(EditorGameBase& egbase) {
198 return MapObject::init(egbase);
199 }
200
cleanup(EditorGameBase & egbase)201 void WareInstance::cleanup(EditorGameBase& egbase) {
202 // Unlink from our current location, if necessary
203 if (upcast(Flag, flag, location_.get(egbase)))
204 flag->remove_ware(egbase, this);
205
206 delete supply_;
207 supply_ = nullptr;
208
209 cancel_moving();
210 set_location(egbase, nullptr);
211
212 MapObject::cleanup(egbase);
213 }
214
215 /**
216 * Ware accounting
217 */
set_economy(Economy * const e)218 void WareInstance::set_economy(Economy* const e) {
219 assert(!e || e->type() == wwWARE);
220 if (descr_index_ == INVALID_INDEX || economy_ == e)
221 return;
222
223 if (economy_)
224 economy_->remove_wares_or_workers(descr_index_, 1);
225
226 economy_ = e;
227 if (supply_)
228 supply_->set_economy(e);
229
230 if (economy_)
231 economy_->add_wares_or_workers(descr_index_, 1);
232 }
233
234 /**
235 * Change the current location.
236 * Once you've assigned a ware to its new location, you usually have to call
237 * \ref update() as well.
238 */
set_location(EditorGameBase & egbase,MapObject * const location)239 void WareInstance::set_location(EditorGameBase& egbase, MapObject* const location) {
240 MapObject* const oldlocation = location_.get(egbase);
241
242 if (oldlocation == location)
243 return;
244
245 location_ = location;
246
247 if (location) {
248 Economy* eco = nullptr;
249
250 if (upcast(Flag const, flag, location))
251 eco = flag->get_economy(wwWARE);
252 else if (upcast(Worker const, worker, location))
253 eco = worker->get_economy(wwWARE);
254 else if (upcast(PortDock const, portdock, location))
255 eco = portdock->get_economy(wwWARE);
256 else if (upcast(Ship const, ship, location))
257 eco = ship->get_economy(wwWARE);
258 else
259 throw wexception("WareInstance delivered to bad location %u", location->serial());
260
261 if (oldlocation && get_economy()) {
262 if (get_economy() != eco)
263 throw wexception("WareInstance::set_location() implies change of economy");
264 } else {
265 set_economy(eco);
266 }
267 } else {
268 set_economy(nullptr);
269 }
270 }
271
272 /**
273 * Handle delayed updates.
274 */
act(Game & game,uint32_t)275 void WareInstance::act(Game& game, uint32_t) {
276 update(game);
277 }
278
279 /**
280 * Performs the state updates necessary for the current location:
281 * - if it's a building, acknowledge the Request or incorporate into warehouse
282 * - if it's a flag and we have no request, start the return to warehouse timer
283 * and issue a Supply
284 *
285 * \note \ref update() may result in the deletion of this object.
286 * \note It is important that this function is idempotent, i.e. calling
287 * \ref update() twice in a row should have the same effect as calling
288 * it only once, \em unless the instance is deleted as a side-effect of
289 * \ref update().
290 */
update(Game & game)291 void WareInstance::update(Game& game) {
292 if (!descr_) // Upsy, we're not even initialized. Happens on load
293 return;
294
295 MapObject* const loc = location_.get(game);
296
297 // Reset our state if we're not on location or outside an economy
298 if (!get_economy()) {
299 cancel_moving();
300 return;
301 }
302
303 if (!loc) {
304 // Before dying, output as much information as we can.
305 log_general_info(game);
306
307 // If our location gets lost, our owner is supposed to destroy us
308 throw wexception("WARE(%u): WareInstance::update has no location\n", serial());
309 }
310
311 // Update whether we have a Supply or not
312 if (!transfer_ || !transfer_->get_request()) {
313 if (!supply_) {
314 supply_ = new IdleWareSupply(*this);
315 }
316 } else {
317 delete supply_;
318 supply_ = nullptr;
319 }
320
321 // Deal with transfers
322 if (transfer_) {
323 upcast(PlayerImmovable, location, loc);
324
325 if (!location) {
326 return; // wait
327 }
328
329 bool success;
330 PlayerImmovable* const nextstep = transfer_->get_next_step(location, success);
331 transfer_nextstep_ = nextstep;
332
333 if (!nextstep) {
334 if (upcast(Flag, flag, location))
335 flag->call_carrier(game, *this, nullptr);
336
337 Transfer* const t = transfer_;
338
339 transfer_ = nullptr;
340 transfer_nextstep_ = nullptr;
341
342 if (success) {
343 t->has_finished();
344 return;
345 } else {
346 t->has_failed();
347
348 cancel_moving();
349 update(game);
350 return;
351 }
352 }
353
354 if (upcast(Flag, flag, location)) {
355 flag->call_carrier(game, *this, dynamic_cast<Building const*>(nextstep) &&
356 &nextstep->base_flag() != location ?
357 &nextstep->base_flag() :
358 nextstep);
359 } else if (upcast(PortDock, pd, location)) {
360 pd->update_shippingitem(game, *this);
361 } else {
362 throw wexception(
363 "Ware_Instance::update in bad type of PlayerImmovable %u", location->serial());
364 }
365 }
366 }
367
368 /**
369 * Called by a worker when it carries the ware into the given building.
370 */
enter_building(Game & game,Building & building)371 void WareInstance::enter_building(Game& game, Building& building) {
372 if (transfer_) {
373 if (transfer_->get_destination(game) == &building) {
374 Transfer* t = transfer_;
375
376 transfer_ = nullptr;
377 transfer_nextstep_ = nullptr;
378
379 t->has_finished();
380 return;
381 }
382
383 bool success;
384 PlayerImmovable* const nextstep = transfer_->get_next_step(&building, success);
385 transfer_nextstep_ = nextstep;
386
387 if (success) {
388 assert(nextstep);
389
390 if (upcast(PortDock, pd, nextstep)) {
391 pd->add_shippingitem(game, *this);
392 return;
393 }
394
395 // There are some situations where we might end up in a warehouse
396 // as part of a requested route, and we need to move out of it
397 // again, e.g.:
398 // - we were requested just when we were being carried into the
399 // warehouse
400 // - we were carried into a harbour/warehouse to be
401 // shipped across the sea, but a better, land-based route has been
402 // found
403 if (upcast(Warehouse, warehouse, &building)) {
404 warehouse->do_launch_ware(game, *this);
405 return;
406 }
407
408 throw wexception(
409 "MO(%u): ware(%s): do not know how to move from building %u (%s at (%u,%u)) "
410 "to %u (%s) -> not a warehouse!",
411 serial(), descr_->name().c_str(), building.serial(), building.descr().name().c_str(),
412 building.get_position().x, building.get_position().y, nextstep->serial(),
413 nextstep->descr().name().c_str());
414 } else {
415 Transfer* t = transfer_;
416
417 transfer_ = nullptr;
418 transfer_nextstep_ = nullptr;
419
420 t->has_failed();
421 cancel_moving();
422
423 if (is_a(Warehouse, &building)) {
424 building.receive_ware(game, descr_index_);
425 remove(game);
426 } else {
427 update(game);
428 }
429 return;
430 }
431 } else {
432 // We don't have a transfer, so just enter the building
433 building.receive_ware(game, descr_index_);
434 remove(game);
435 }
436 }
437
438 /**
439 * Set ware state so that it follows the given transfer.
440 *
441 * \param t the new transfer (non-zero; use \ref cancel_transfer to stop a
442 * transfer).
443 */
set_transfer(Game & game,Transfer & t)444 void WareInstance::set_transfer(Game& game, Transfer& t) {
445 transfer_nextstep_ = nullptr;
446
447 // Reset current transfer
448 if (transfer_) {
449 transfer_->has_failed();
450 transfer_ = nullptr;
451 }
452
453 // Set transfer state
454 transfer_ = &t;
455
456 delete supply_;
457 supply_ = nullptr;
458
459 // Schedule an update.
460 // Do not update immediately, because update() could try to reference
461 // the Transfer object in a way that is not valid yet (note that this
462 // function is called in the Transfer constructor before the Transfer
463 // is linked to the corresponding Request).
464 schedule_act(game, 1, 0);
465 }
466
467 /**
468 * The transfer has been cancelled, just stop moving.
469 */
cancel_transfer(Game & game)470 void WareInstance::cancel_transfer(Game& game) {
471 transfer_ = nullptr;
472 transfer_nextstep_ = nullptr;
473
474 update(game);
475 }
476
477 /**
478 * We are moving when there's a transfer, it's that simple.
479 */
is_moving() const480 bool WareInstance::is_moving() const {
481 return transfer_;
482 }
483
484 /**
485 * Call this function if movement + potential request need to be cancelled for
486 * whatever reason.
487 */
cancel_moving()488 void WareInstance::cancel_moving() {
489 molog("cancel_moving\n");
490
491 if (transfer_) {
492 transfer_->has_failed();
493 transfer_ = nullptr;
494 transfer_nextstep_ = nullptr;
495 }
496 }
497
498 /**
499 * Return the next flag we should be moving to, or the final target if the route
500 * has been completed successfully.
501 */
get_next_move_step(Game & game)502 PlayerImmovable* WareInstance::get_next_move_step(Game& game) {
503 return transfer_ ? dynamic_cast<PlayerImmovable*>(transfer_nextstep_.get(game)) : nullptr;
504 }
505
log_general_info(const EditorGameBase & egbase) const506 void WareInstance::log_general_info(const EditorGameBase& egbase) const {
507 MapObject::log_general_info(egbase);
508
509 molog("Ware: %s\n", descr().name().c_str());
510 molog("Location: %u\n", location_.serial());
511 }
512
513 /*
514 ==============================
515
516 Load/save support
517
518 ==============================
519 */
520
521 constexpr uint8_t kCurrentPacketVersion = 2;
522
load(FileRead & fr)523 void WareInstance::Loader::load(FileRead& fr) {
524 MapObject::Loader::load(fr);
525
526 WareInstance& ware = get<WareInstance>();
527 location_ = fr.unsigned_32();
528 transfer_nextstep_ = fr.unsigned_32();
529 if (fr.unsigned_8()) {
530 ware.transfer_ = new Transfer(dynamic_cast<Game&>(egbase()), ware);
531 ware.transfer_->read(fr, transfer_);
532 }
533 }
534
load_pointers()535 void WareInstance::Loader::load_pointers() {
536 MapObject::Loader::load_pointers();
537
538 WareInstance& ware = get<WareInstance>();
539
540 // There is a race condition where a ware may lose its location and be scheduled
541 // for removal via the update callback, but the game is saved just before the
542 // removal. This is why we allow a null location on load.
543 if (location_)
544 ware.set_location(egbase(), &mol().get<MapObject>(location_));
545 if (transfer_nextstep_)
546 ware.transfer_nextstep_ = &mol().get<MapObject>(transfer_nextstep_);
547 if (ware.transfer_)
548 ware.transfer_->read_pointers(mol(), transfer_);
549 }
550
load_finish()551 void WareInstance::Loader::load_finish() {
552 MapObject::Loader::load_finish();
553
554 WareInstance& ware = get<WareInstance>();
555 if (!ware.transfer_ || !ware.transfer_->get_request()) {
556 if (!ware.supply_)
557 ware.supply_ = new IdleWareSupply(ware);
558 }
559 }
560
save(EditorGameBase & egbase,MapObjectSaver & mos,FileWrite & fw)561 void WareInstance::save(EditorGameBase& egbase, MapObjectSaver& mos, FileWrite& fw) {
562 fw.unsigned_8(HeaderWareInstance);
563 fw.unsigned_8(kCurrentPacketVersion);
564 fw.c_string(descr().name());
565
566 MapObject::save(egbase, mos, fw);
567
568 fw.unsigned_32(mos.get_object_file_index_or_zero(location_.get(egbase)));
569 fw.unsigned_32(mos.get_object_file_index_or_zero(transfer_nextstep_.get(egbase)));
570 if (transfer_) {
571 fw.unsigned_8(1);
572 transfer_->write(mos, fw);
573 } else {
574 fw.unsigned_8(0);
575 }
576 }
577
load(EditorGameBase & egbase,MapObjectLoader & mol,FileRead & fr,const TribesLegacyLookupTable & lookup_table)578 MapObject::Loader* WareInstance::load(EditorGameBase& egbase,
579 MapObjectLoader& mol,
580 FileRead& fr,
581 const TribesLegacyLookupTable& lookup_table) {
582 try {
583 uint8_t packet_version = fr.unsigned_8();
584
585 // Some maps may contain ware info, so we need compatibility here.
586 if (1 <= packet_version && packet_version <= kCurrentPacketVersion) {
587 if (packet_version == 1) {
588 fr.c_string(); // Consume tribe name
589 }
590 const std::string warename = lookup_table.lookup_ware(fr.c_string());
591
592 DescriptionIndex wareindex = egbase.tribes().ware_index(warename);
593 const WareDescr* descr = egbase.tribes().get_ware_descr(wareindex);
594
595 std::unique_ptr<Loader> loader(new Loader);
596 loader->init(egbase, mol, *new WareInstance(wareindex, descr));
597 loader->load(fr);
598
599 return loader.release();
600 } else {
601 throw UnhandledVersionError("WareInstance", packet_version, kCurrentPacketVersion);
602 }
603 } catch (const std::exception& e) {
604 throw wexception("WareInstance: %s", e.what());
605 }
606 NEVER_HERE();
607 }
608 } // namespace Widelands
609