Merge lp:~widelands-dev/widelands/ship_scheduling_2 into lp:widelands

Proposed by ypopezios
Status: Superseded
Proposed branch: lp:~widelands-dev/widelands/ship_scheduling_2
Merge into: lp:widelands
Diff against target: 950 lines (+366/-314)
6 files modified
src/economy/fleet.cc (+163/-216)
src/economy/fleet.h (+4/-0)
src/economy/portdock.cc (+115/-61)
src/economy/portdock.h (+5/-4)
src/logic/map_objects/tribes/ship.cc (+73/-30)
src/logic/map_objects/tribes/ship.h (+6/-3)
To merge this branch: bzr merge lp:~widelands-dev/widelands/ship_scheduling_2
Reviewer Review Type Date Requested Status
Widelands Developers Pending
Review via email: mp+352335@code.launchpad.net

This proposal has been superseded by a proposal from 2018-08-30.

Description of the change

See description of the branch:
https://code.launchpad.net/~widelands-dev/widelands/ship_scheduling_2

Get windows builds and ask for testing - do not review yet

To post a comment you must log in.
Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 3754. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/412020649.
Appveyor build 3554. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_ship_scheduling_2-3554.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 3758. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/412187264.
Appveyor build 3558. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_ship_scheduling_2-3558.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 3855. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/422224908.
Appveyor build 3653. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_ship_scheduling_2-3653.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/economy/fleet.cc'
2--- src/economy/fleet.cc 2018-04-07 16:59:00 +0000
3+++ src/economy/fleet.cc 2018-08-29 19:19:29 +0000
4@@ -628,8 +628,7 @@
5 }
6
7 /**
8- * Act callback updates ship scheduling. All decisions about where transport ships
9- * are supposed to go are made via this function.
10+ * Act callback updates ship scheduling of idle ships.
11 *
12 * @note Do not call this directly; instead, trigger it via @ref update
13 */
14@@ -637,230 +636,178 @@
15 act_pending_ = false;
16
17 if (!active()) {
18- // If we are here, most likely act() was called by a port with waiting wares or an expedition
19- // ready
20- // although there are still no ships. We can't handle it now, so we reschedule the act()
21- schedule_act(game, 5000); // retry in the next time
22+ // If we are here, most likely act() was called by a port with waiting wares or
23+ // with an expedition ready, although there are still no ships.
24+ // We can't handle it now, so we reschedule the act()
25+ schedule_act(game, kFleetInterval); // retry in the next time
26 act_pending_ = true;
27 return;
28 }
29
30 molog("Fleet::act\n");
31
32- // we need to calculate what ship is to be send to which port
33- // for this we will have temporary data structure with format
34- // <<ship,port>,score>
35- // where ship and port are not objects but positions in ports_ and ships_
36- // this is to allow native hashing
37- std::map<std::pair<uint16_t, uint16_t>, uint16_t> scores;
38-
39- // so we will identify all pairs: idle ship : ports, and score all such
40- // pairs. We consider
41- // - count of wares onboard, first ware (oldest) is counted as 8 (prioritization)
42- // (counting wares for particular port only)
43- // - count wares waiting at the port/3
44- // - distance between ship and a port (0-10 points, the closer the more points)
45- // - is another ship heading there right now?
46-
47- // at the end we must know if requrests of all ports asking for ship were addressed
48- // if any unsatisfied, we must schedule new run of this function
49- // when we send a ship there, the port is removed from list
50- std::list<uint16_t> waiting_ports;
51-
52- // this is just helper - first member of scores map
53- std::pair<uint16_t, uint16_t> mapping; // ship number, port number
54-
55- // first we go over ships - idle ones (=without destination)
56- // then over wares on these ships and create first ship-port
57- // pairs with score
58- for (uint16_t s = 0; s < ships_.size(); s += 1) {
59- if (ships_[s]->get_destination(game)) {
60- continue;
61- }
62- if (ships_[s]->get_ship_state() != Ship::ShipStates::kTransport) {
63- continue; // in expedition obviously
64- }
65-
66- for (uint16_t i = 0; i < ships_[s]->get_nritems(); i += 1) {
67- PortDock* dst = ships_[s]->items_[i].get_destination(game);
68- if (!dst) {
69- // if wares without destination on ship without destination
70- // such ship can be send to any port, and should be sent
71- // to some port, so we add 1 point to score for each port
72- for (uint16_t p = 0; p < ports_.size(); p += 1) {
73- mapping.first = s;
74- mapping.second = p;
75- scores[mapping] += 1;
76- }
77- continue;
78- }
79-
80- bool destination_found = false; // Just a functional check
81- for (uint16_t p = 0; p < ports_.size(); p += 1) {
82- if (ports_[p] == ships_[s]->items_[i].get_destination(game)) {
83- mapping.first = s;
84- mapping.second = p;
85- scores[mapping] += (i == 0) ? 8 : 1;
86- destination_found = true;
87- }
88- }
89- if (!destination_found) {
90- // Perhaps the throw here is too strong
91- // we can still remove it before stable release if it proves too much
92- // during my testing this situation never happened
93- throw wexception("A ware with destination that does not match any of player's"
94- " ports, ship %u, ware's destination: %u",
95- ships_[s]->serial(),
96- ships_[s]->items_[i].get_destination(game)->serial());
97- }
98- }
99- }
100-
101- // now opposite aproach - we go over ports to find out those that have wares
102- // waiting for ship then find candidate ships to satisfy the requests
103- for (uint16_t p = 0; p < ports_.size(); p += 1) {
104- PortDock& pd = *ports_[p];
105- if (!pd.get_need_ship()) {
106- continue;
107- }
108-
109- // general stategy is "one ship for port is enough", but sometimes
110- // amount of ware waiting for ship is too high
111- if (count_ships_heading_here(game, &pd) * 25 > pd.count_waiting()) {
112- continue;
113- }
114-
115- waiting_ports.push_back(p);
116-
117- // scoring and entering the pair into scores (or increasing existing
118- // score if the pair is already there)
119- for (uint16_t s = 0; s < ships_.size(); s += 1) {
120-
121- if (ships_[s]->get_destination(game)) {
122- continue; // already has destination
123- }
124-
125- if (ships_[s]->get_ship_state() != Ship::ShipStates::kTransport) {
126- continue; // in expedition obviously
127- }
128-
129- mapping.first = s;
130- mapping.second = p;
131- // following aproximately considers free capacity of a ship
132- scores[mapping] += ((ships_[s]->get_nritems() > 15) ? 1 : 3) +
133- std::min(ships_[s]->descr().get_capacity() - ships_[s]->get_nritems(),
134- ports_[p]->count_waiting()) /
135- 3;
136- }
137- }
138-
139- // Now adding score for distance
140- for (auto ship_port_relation : scores) {
141+ // For each waiting port, try to find idle ships and send to it the closest one.
142+ uint16_t waiting_ports = ports_.size();
143+ for (PortDock* p : ports_) {
144+ if (p->get_need_ship() == 0) {
145+ --waiting_ports;
146+ continue;
147+ }
148+
149+ Ship* closest_ship = nullptr;
150+ float shortest_dist = 10000;
151+ bool waiting = true;
152+
153+ for (Ship* s : ships_) {
154+ if (s->get_destination(game)) {
155+ if (s->get_destination(game) == p) {
156+ waiting = false;
157+ --waiting_ports;
158+ break;
159+ }
160+ continue; // has already destination
161+ }
162+ if (s->get_ship_state() != Ship::ShipStates::kTransport) {
163+ continue; // in expedition obviously
164+ }
165+
166+ // here we get distance ship->port
167+ int16_t route_length = 10000;
168+
169+ PortDock* cur_port = get_dock(game, s->get_position());
170+ if (cur_port) { // we are in a port
171+ if (cur_port == p) { // same port
172+ route_length = 0;
173+ } else { // different port
174+ Path tmp_path;
175+ if (get_path(*cur_port, *p, tmp_path)) { // try to use precalculated path
176+ route_length = tmp_path.get_nsteps();
177+ }
178+ }
179+ }
180+
181+ if (route_length == 10000) { // above failed, so we calculate path "manually"
182+ // most probably the ship is not in a port (should not happen frequently)
183+ route_length = s->calculate_sea_route(game, *p);
184+ }
185+
186+ if (route_length < shortest_dist) {
187+ shortest_dist = route_length;
188+ closest_ship = s;
189+ }
190+ }
191+
192+ if (waiting && closest_ship) {
193+ --waiting_ports;
194+ closest_ship->set_destination(p);
195+ closest_ship->send_signal(game, "wakeup");
196+ }
197+ }
198+
199+ if (waiting_ports > 0) {
200+ molog("... there are %u ports requesting ship(s) we cannot satisfy yet\n", waiting_ports);
201+ schedule_act(game, kFleetInterval); // retry next time
202+ act_pending_ = true;
203+ }
204+}
205+
206+/**
207+ * For the given three consecutive ports, decide if their path is favourable or not.
208+ * \return true/false for yes/no
209+ */
210+bool Fleet::is_path_favourable(PortDock& start, PortDock& middle, PortDock& finish) {
211+ if (&middle != &finish) {
212+ Path path_start_to_finish;
213+ Path path_middle_to_finish;
214+
215+ assert(get_path(start, finish, path_start_to_finish));
216+ if (get_path(middle, finish, path_middle_to_finish)) {
217+ if (path_middle_to_finish.get_nsteps() > path_start_to_finish.get_nsteps()) {
218+ return false;
219+ }
220+ }
221+ }
222+ return true; // default
223+}
224+
225+/**
226+ * For the given ship, go through all ports of this fleet
227+ * and find the one with the best score.
228+ * \return that port
229+ *
230+ * @note cur_port is never nullptr (we are always in a port),
231+ * but that is kept in case the design changes in the future
232+ */
233+PortDock* Fleet::find_next_dest(Game& game, Ship& ship, PortDock* const cur_port) {
234+ // uint32_t const max_capacity = ship.descr().get_capacity();
235+ PortDock* best_port = nullptr;
236+ float best_score = 0;
237+
238+ for (PortDock* p : ports_) {
239+ if (p == cur_port) {
240+ continue; // same port
241+ }
242+
243+ float score = 0;
244+ WareInstance* ware;
245+ Worker* worker;
246+
247+ // score for wares/workers onboard that ship for that port
248+ for (uint16_t i = 0; i < ship.get_nritems(); ++i) {
249+ ShippingItem& si = ship.items_[i];
250+ if (si.get_destination(game) == p) {
251+ si.get(game, &ware, &worker);
252+ if (ware) {
253+ score += 1; // TODO: increase by ware's importance
254+ } else { // worker
255+ score += 4;
256+ }
257+ }
258+ }
259+
260+ if (cur_port) { // we are in a port
261+ // score for wares/workers waiting at that port
262+ for (uint16_t i = 0; i < cur_port->count_waiting(); ++i) {
263+ ShippingItem& si = cur_port->waiting_[i];
264+ if (si.get_destination(game) == p) {
265+ si.get(game, &ware, &worker);
266+ if (ware) {
267+ score += 1; // TODO: increase by ware's importance
268+ } else { // worker
269+ score += 4;
270+ }
271+ }
272+ }
273+ }
274+
275+ if (score == 0 && p->get_need_ship() == 0) {
276+ continue; // empty ship to empty port
277+ }
278
279 // here we get distance ship->port
280- // possibilities are:
281- // - we are in port and it is the same as target port
282- // - we are in other port, then we use get_dock() function to fetch precalculated path
283- // - if above fails, we calculate path "manually"
284 int16_t route_length = -1;
285
286- PortDock* current_portdock =
287- get_dock(game, ships_[ship_port_relation.first.first]->get_position());
288-
289- if (current_portdock) { // we try to use precalculated paths of game
290-
291- // we are in the same portdock
292- if (current_portdock == ports_[ship_port_relation.first.second]) {
293- route_length = 0;
294- } else { // it is different portdock then
295- Path tmp_path;
296- if (get_path(*current_portdock, *ports_[ship_port_relation.first.second], tmp_path)) {
297- route_length = tmp_path.get_nsteps();
298- }
299- }
300- }
301-
302- // most probably the ship is not in a portdock (should not happen frequently)
303- if (route_length == -1) {
304- route_length = ships_[ship_port_relation.first.first]->calculate_sea_route(
305- game, *ports_[ship_port_relation.first.second]);
306- }
307-
308- // now we have length of route, so we need to calculate score
309- int16_t score_for_distance = 0;
310- if (route_length < 3) {
311- score_for_distance = 10;
312- } else {
313- score_for_distance = 8 - route_length / 50;
314- }
315- // must not be negative
316- score_for_distance = (score_for_distance < 0) ? 0 : score_for_distance;
317-
318- scores[ship_port_relation.first] += score_for_distance;
319- }
320-
321- // looking for best scores and sending ships accordingly
322- uint16_t best_ship = 0;
323- uint16_t best_port = 0;
324-
325- // after sending a ship we will remove one or more items from scores
326- while (!scores.empty()) {
327- uint16_t best_score = 0;
328-
329- // searching for combination with highest score
330- for (const auto& combination : scores) {
331- if (combination.second > best_score) {
332- best_score = combination.second;
333- best_ship = combination.first.first;
334- best_port = combination.first.second;
335- }
336- }
337- if (best_score == 0) {
338- // this is check of correctnes of this algorithm, this should not happen
339- throw wexception("Fleet::act(): No port-destination pair selected or its score is zero");
340- }
341-
342- // making sure the winner has no destination set
343- assert(!ships_[best_ship]->get_destination(game));
344-
345- // now actual setting destination for "best ship"
346- ships_[best_ship]->set_destination(game, *ports_[best_port]);
347- molog("... ship %u sent to port %u, wares onboard: %2d, the port is asking for a ship: %s\n",
348- ships_[best_ship]->serial(), ports_[best_port]->serial(),
349- ships_[best_ship]->get_nritems(), (ports_[best_port]->get_need_ship()) ? "yes" : "no");
350-
351- // pruning the scores table
352- // the ship that was just sent somewhere cannot be send elsewhere :)
353- for (auto it = scores.cbegin(); it != scores.cend();) {
354-
355- // decreasing score for target port as there was a ship just sent there
356- if (it->first.second == best_port) {
357- mapping.first = it->first.first;
358- mapping.second = it->first.second;
359- scores[mapping] /= 2;
360- // just make sure it is nonzero
361- scores[mapping] = (scores[mapping] == 0) ? 1 : scores[mapping];
362- }
363-
364- // but removing all pairs where best ship is participating as it is not available anymore
365- // (because it was sent to "best port")
366- if (it->first.first == best_ship) {
367- scores.erase(it++);
368- } else {
369- ++it;
370- }
371- }
372-
373- // also removing the port from waiting_ports
374- waiting_ports.remove(best_port);
375- }
376-
377- if (!waiting_ports.empty()) {
378- molog("... there are %" PRIuS " ports requesting ship(s) we cannot satisfy yet\n",
379- waiting_ports.size());
380- schedule_act(game, 5000); // retry next time
381- act_pending_ = true;
382- }
383+ if (cur_port) { // we are in a port
384+ Path tmp_path;
385+ if (get_path(*cur_port, *p, tmp_path)) { // try to use precalculated path
386+ route_length = tmp_path.get_nsteps();
387+ }
388+ }
389+
390+ if (route_length == -1) { // above failed, so we calculate path "manually"
391+ // most probably the ship is not in a port (should not happen frequently)
392+ route_length = ship.calculate_sea_route(game, *p);
393+ }
394+
395+ score = (score + 1) * (score + p->get_need_ship());
396+ score = score * (1 - route_length / (score + route_length));
397+ if (score > best_score) {
398+ best_score = score;
399+ best_port = p;
400+ }
401+ }
402+
403+ return best_port;
404 }
405
406 void Fleet::log_general_info(const EditorGameBase& egbase) {
407
408=== modified file 'src/economy/fleet.h'
409--- src/economy/fleet.h 2018-04-16 07:03:12 +0000
410+++ src/economy/fleet.h 2018-08-29 19:19:29 +0000
411@@ -46,6 +46,8 @@
412 DISALLOW_COPY_AND_ASSIGN(FleetDescr);
413 };
414
415+constexpr int32_t kFleetInterval = 5000;
416+
417 /**
418 * Manage all ships and ports of a player that are connected
419 * by ocean.
420@@ -152,6 +154,8 @@
421 void save(EditorGameBase&, MapObjectSaver&, FileWrite&) override;
422
423 static MapObject::Loader* load(EditorGameBase&, MapObjectLoader&, FileRead&);
424+ bool is_path_favourable(PortDock& start, PortDock& middle, PortDock& finish);
425+ PortDock* find_next_dest(Game&, Ship&, PortDock* const cur_port);
426 };
427
428 } // namespace Widelands
429
430=== modified file 'src/economy/portdock.cc'
431--- src/economy/portdock.cc 2018-04-16 07:03:12 +0000
432+++ src/economy/portdock.cc 2018-08-29 19:19:29 +0000
433@@ -34,6 +34,7 @@
434 #include "logic/game_data_error.h"
435 #include "logic/map_objects/tribes/ship.h"
436 #include "logic/map_objects/tribes/warehouse.h"
437+#include "logic/path.h"
438 #include "logic/player.h"
439 #include "logic/widelands_geometry_io.h"
440 #include "map_io/map_object_loader.h"
441@@ -55,7 +56,7 @@
442 : PlayerImmovable(g_portdock_descr),
443 fleet_(nullptr),
444 warehouse_(wh),
445- need_ship_(false),
446+ ships_coming_(0),
447 expedition_ready_(false) {
448 }
449
450@@ -116,6 +117,10 @@
451 return nullptr;
452 }
453
454+uint32_t PortDock::get_need_ship() const {
455+ return (waiting_.size() + (expedition_ready_ ? 20 : 0)) / (ships_coming_ + 1);
456+}
457+
458 /**
459 * Signal to the dock that it now belongs to the given economy.
460 *
461@@ -289,36 +294,58 @@
462
463 // Destination might have vanished or be in another economy altogether.
464 if (dst && dst->get_economy() == get_economy()) {
465- set_need_ship(game, true);
466+ if (ships_coming_ <= 0) {
467+ set_need_ship(game, true);
468+ }
469 } else {
470 it->set_location(game, warehouse_);
471 it->end_shipping(game);
472 *it = waiting_.back();
473 waiting_.pop_back();
474-
475- if (waiting_.empty())
476- set_need_ship(game, false);
477- }
478-}
479-
480-/**
481- * A ship has arrived at the dock. Clear all items designated for this dock,
482- * and load the ship.
483+ }
484+}
485+
486+/**
487+ * Receive shipping item from unloading ship.
488+ * Called by ship code.
489+ */
490+void PortDock::shipping_item_arrived(Game& game, ShippingItem& si) {
491+ si.set_location(game, warehouse_);
492+ si.end_shipping(game);
493+}
494+
495+/**
496+ * Receive shipping item from departing ship.
497+ * Called by ship code.
498+ */
499+void PortDock::shipping_item_returned(Game& game, ShippingItem& si) {
500+ si.set_location(game, this);
501+ waiting_.push_back(si);
502+}
503+
504+/**
505+ * A ship changed destination and is now coming at the dock. Increase counter for need_ship.
506+ */
507+void PortDock::ship_coming(bool affirmative) {
508+ if (affirmative) {
509+ ++ships_coming_;
510+ } else {
511+ ships_coming_ = std::max(0, ships_coming_ - 1); // max used for compatibility with old savegames
512+ }
513+}
514+
515+/**
516+ * A ship has arrived at the dock. Set its next destination and load it accordingly.
517 */
518 void PortDock::ship_arrived(Game& game, Ship& ship) {
519- std::vector<ShippingItem> items_brought_by_ship;
520- ship.withdraw_items(game, *this, items_brought_by_ship);
521-
522- for (ShippingItem& shipping_item : items_brought_by_ship) {
523- shipping_item.set_location(game, warehouse_);
524- shipping_item.end_shipping(game);
525- }
526+ ship_coming(false);
527
528 if (expedition_ready_) {
529 assert(expedition_bootstrap_ != nullptr);
530
531 // Only use an empty ship.
532 if (ship.get_nritems() < 1) {
533+ ship.set_destination(nullptr);
534 // Load the ship
535 std::vector<Worker*> workers;
536 std::vector<WareInstance*> wares;
537@@ -337,46 +364,73 @@
538 // The expedition goods are now on the ship, so from now on it is independent from the port
539 // and thus we switch the port to normal, so we could even start a new expedition,
540 cancel_expedition(game);
541- return fleet_->update(game);
542- }
543- }
544-
545- if (ship.get_nritems() < ship.descr().get_capacity() && !waiting_.empty()) {
546- uint32_t nrload =
547- std::min<uint32_t>(waiting_.size(), ship.descr().get_capacity() - ship.get_nritems());
548-
549- while (nrload--) {
550- // Check if the item has still a valid destination
551- if (waiting_.back().get_destination(game)) {
552- // Destination is valid, so we load the item onto the ship
553- ship.add_item(game, waiting_.back());
554- } else {
555- // The item has no valid destination anymore, so we just carry it
556- // back in the warehouse
557- waiting_.back().set_location(game, warehouse_);
558- waiting_.back().end_shipping(game);
559- }
560- waiting_.pop_back();
561- }
562-
563- if (waiting_.empty()) {
564- set_need_ship(game, false);
565- }
566- }
567-
568- fleet_->update(game);
569+ fleet_->update(game);
570+ return;
571+ }
572+ }
573+
574+ // decide where the arrived ship will go next
575+ PortDock* next_port = fleet_->find_next_dest(game, ship, this);
576+ if (!next_port) {
577+ ship.set_destination(next_port);
578+ return; // no need to load anything
579+ }
580+
581+ // unload any wares/workers onboard the departing ship which are not favored by next dest
582+ ship.unload_unfit_items(game, *this, *next_port);
583+
584+ // then load the remaining capacity of the departing ship with relevant items
585+ uint32_t rest_capacity = ship.descr().get_capacity() - ship.get_nritems();
586+
587+ // firstly load the items which go to chosen dest, while also checking for items with invalid dest
588+ uint32_t dst = 0;
589+ for (ShippingItem& si : waiting_) {
590+ PortDock* itemdest = si.get_destination(game);
591+ if (itemdest) { // valid dest
592+ if (rest_capacity == 0) {
593+ waiting_[dst++] = si; // keep the item here
594+ } else {
595+ if (itemdest == next_port) { // the item goes to chosen dest
596+ ship.add_item(game, si); // load it
597+ --rest_capacity;
598+ } else { // different dest
599+ waiting_[dst++] = si; // keep it here for now
600+ }
601+ }
602+ } else { // invalid dest
603+ // carry the item back in the warehouse
604+ si.set_location(game, warehouse_);
605+ si.end_shipping(game);
606+ }
607+ }
608+ waiting_.resize(dst);
609+
610+ if (rest_capacity > 0) { // there is still capacity
611+ // load any items favored by the chosen dest
612+ dst = 0;
613+ for (ShippingItem& si : waiting_) {
614+ if (rest_capacity == 0) {
615+ waiting_[dst++] = si; // keep the item here
616+ } else {
617+ if (fleet_->is_path_favourable(*this, *next_port, *si.get_destination(game))) {
618+ ship.add_item(game, si);
619+ --rest_capacity;
620+ } else { // item is not favored by the chosen dest
621+ // spare it from getting trapped inside the wrong ship
622+ waiting_[dst++] = si;
623+ }
624+ }
625+ }
626+ waiting_.resize(dst);
627+ }
628+ ship.set_destination(next_port);
629+
630+ set_need_ship(game, !waiting_.empty());
631 }
632
633 void PortDock::set_need_ship(Game& game, bool need) {
634- molog("set_need_ship(%s)\n", need ? "true" : "false");
635-
636- if (need == need_ship_)
637- return;
638-
639- need_ship_ = need;
640-
641- if (fleet_) {
642- molog("... trigger fleet update\n");
643+ if (need && fleet_) {
644+ molog("trigger fleet update\n");
645 fleet_->update(game);
646 }
647 }
648@@ -445,13 +499,13 @@
649
650 if (warehouse_) {
651 Coords pos(warehouse_->get_position());
652- molog("PortDock for warehouse %u (at %i,%i) in fleet %u, need_ship: %s, waiting: %" PRIuS
653+ molog("PortDock for warehouse %u (at %i,%i) in fleet %u, expedition_ready: %s, waiting: %" PRIuS
654 "\n",
655 warehouse_->serial(), pos.x, pos.y, fleet_ ? fleet_->serial() : 0,
656- need_ship_ ? "true" : "false", waiting_.size());
657+ expedition_ready_ ? "true" : "false", waiting_.size());
658 } else {
659- molog("PortDock without a warehouse in fleet %u, need_ship: %s, waiting: %" PRIuS "\n",
660- fleet_ ? fleet_->serial() : 0, need_ship_ ? "true" : "false", waiting_.size());
661+ molog("PortDock without a warehouse in fleet %u, expedition_ready: %s, waiting: %" PRIuS "\n",
662+ fleet_ ? fleet_->serial() : 0, expedition_ready_ ? "true" : "false", waiting_.size());
663 }
664
665 for (ShippingItem& shipping_item : waiting_) {
666@@ -479,7 +533,7 @@
667 pd.set_position(egbase(), pd.dockpoints_[i]);
668 }
669
670- pd.need_ship_ = fr.unsigned_8();
671+ pd.ships_coming_ = fr.unsigned_8();
672
673 waiting_.resize(fr.unsigned_32());
674 for (ShippingItem::Loader& shipping_loader : waiting_) {
675@@ -553,7 +607,7 @@
676 write_coords_32(&fw, coords);
677 }
678
679- fw.unsigned_8(need_ship_);
680+ fw.unsigned_8(ships_coming_);
681
682 fw.unsigned_32(waiting_.size());
683 for (ShippingItem& shipping_item : waiting_) {
684
685=== modified file 'src/economy/portdock.h'
686--- src/economy/portdock.h 2018-04-07 16:59:00 +0000
687+++ src/economy/portdock.h 2018-08-29 19:19:29 +0000
688@@ -85,9 +85,7 @@
689 return fleet_;
690 }
691 PortDock* get_dock(Flag& flag) const;
692- bool get_need_ship() const {
693- return need_ship_ || expedition_ready_;
694- }
695+ uint32_t get_need_ship() const;
696
697 void set_economy(Economy*) override;
698
699@@ -113,6 +111,9 @@
700 void add_shippingitem(Game&, Worker&);
701 void update_shippingitem(Game&, Worker&);
702
703+ void shipping_item_arrived(Game&, ShippingItem&);
704+ void shipping_item_returned(Game&, ShippingItem&);
705+ void ship_coming(bool affirmative);
706 void ship_arrived(Game&, Ship&);
707
708 void log_general_info(const EditorGameBase&) override;
709@@ -146,7 +147,7 @@
710 Warehouse* warehouse_;
711 PositionList dockpoints_;
712 std::vector<ShippingItem> waiting_;
713- bool need_ship_;
714+ uint8_t ships_coming_;
715 bool expedition_ready_;
716
717 std::unique_ptr<ExpeditionBootstrap> expedition_bootstrap_;
718
719=== modified file 'src/logic/map_objects/tribes/ship.cc'
720--- src/logic/map_objects/tribes/ship.cc 2018-07-08 15:16:16 +0000
721+++ src/logic/map_objects/tribes/ship.cc 2018-08-29 19:19:29 +0000
722@@ -303,12 +303,22 @@
723
724 FCoords position = map.get_fcoords(get_position());
725 if (position.field->get_immovable() == dst) {
726- molog("ship_update: Arrived at dock %u\n", dst->serial());
727- lastdock_ = dst;
728- destination_ = nullptr;
729- dst->ship_arrived(game, *this);
730- start_task_idle(game, descr().main_animation(), 250);
731- Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged));
732+ if (lastdock_ != dst) {
733+ molog("ship_update: Arrived at dock %u\n", dst->serial());
734+ lastdock_ = dst;
735+ }
736+ if (withdraw_items(game, *dst)) {
737+ schedule_act(game, kShipInterval);
738+ return true;
739+ }
740+
741+ dst->ship_arrived(game, *this); // this should call set_destination
742+ dst = get_destination(game);
743+ if (dst) {
744+ start_task_movetodock(game, *dst);
745+ } else {
746+ start_task_idle(game, descr().main_animation(), 250);
747+ }
748 return true;
749 }
750
751@@ -484,7 +494,7 @@
752 }
753
754 if (totalprob == 0) {
755- start_task_idle(game, descr().main_animation(), 1500);
756+ start_task_idle(game, descr().main_animation(), kShipInterval);
757 return;
758 }
759
760@@ -496,13 +506,13 @@
761 }
762
763 if (dir == 0 || dir > LAST_DIRECTION) {
764- start_task_idle(game, descr().main_animation(), 1500);
765+ start_task_idle(game, descr().main_animation(), kShipInterval);
766 return;
767 }
768
769 FCoords neighbour = map.get_neighbour(position, dir);
770 if (!(neighbour.field->nodecaps() & MOVECAPS_SWIM)) {
771- start_task_idle(game, descr().main_animation(), 1500);
772+ start_task_idle(game, descr().main_animation(), kShipInterval);
773 return;
774 }
775
776@@ -542,7 +552,7 @@
777 pgettext("ship", "Waiting"), _("Island Circumnavigated"),
778 _("An expedition ship sailed around its island without any events."),
779 "images/wui/ship/ship_explore_island_cw.png");
780- return start_task_idle(game, descr().main_animation(), 1500);
781+ return start_task_idle(game, descr().main_animation(), kShipInterval);
782 }
783 }
784 // The ship is supposed to follow the coast as close as possible, therefore the check
785@@ -585,7 +595,7 @@
786 shipname_.c_str());
787 set_ship_state_and_notify(
788 ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand);
789- start_task_idle(game, descr().main_animation(), 1500);
790+ start_task_idle(game, descr().main_animation(), kShipInterval);
791 return;
792 }
793 } else { // scouting towards a specific direction
794@@ -598,7 +608,7 @@
795 // coast reached
796 set_ship_state_and_notify(
797 ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand);
798- start_task_idle(game, descr().main_animation(), 1500);
799+ start_task_idle(game, descr().main_animation(), kShipInterval);
800 // Send a message to the player, that a new coast was reached
801 send_message(game,
802 /** TRANSLATORS: A ship has discovered land */
803@@ -692,7 +702,7 @@
804 }
805
806 expedition_.reset(nullptr);
807- return start_task_idle(game, descr().main_animation(), 1500);
808+ return start_task_idle(game, descr().main_animation(), kShipInterval);
809 }
810 }
811 FALLS_THROUGH;
812@@ -701,7 +711,7 @@
813 case ShipStates::kSinkRequest:
814 case ShipStates::kSinkAnimation: {
815 // wait for input
816- start_task_idle(game, descr().main_animation(), 1500);
817+ start_task_idle(game, descr().main_animation(), kShipInterval);
818 return;
819 }
820 }
821@@ -727,14 +737,17 @@
822
823 /**
824 * Enter a new destination port for the ship.
825- *
826- * @note This is supposed to be called only from the scheduling code of @ref Fleet.
827+ * Call this after un/loading the ship, for proper logging.
828 */
829-void Ship::set_destination(Game& game, PortDock& pd) {
830- molog("set_destination / sending to portdock %u (carrying %" PRIuS " items)\n", pd.serial(),
831- items_.size());
832- destination_ = &pd;
833- send_signal(game, "wakeup");
834+void Ship::set_destination(PortDock* pd) {
835+ destination_ = pd;
836+ if (pd) {
837+ molog("set_destination / sending to portdock %u (carrying %" PRIuS " items)\n", pd->serial(),
838+ items_.size());
839+ pd->ship_coming(true);
840+ } else {
841+ molog("set_destination / none\n");
842+ }
843 Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged));
844 }
845
846@@ -745,14 +758,40 @@
847 items_.back().set_location(game, this);
848 }
849
850-void Ship::withdraw_items(Game& game, PortDock& pd, std::vector<ShippingItem>& items) {
851- uint32_t dst = 0;
852- for (uint32_t src = 0; src < items_.size(); ++src) {
853- PortDock* destination = items_[src].get_destination(game);
854- if (!destination || destination == &pd) {
855- items.push_back(items_[src]);
856+/**
857+ * Unload one item designated for given dock or for no dock.
858+ * \return true if item unloaded.
859+ */
860+bool Ship::withdraw_items(Game& game, PortDock& pd) {
861+ bool unloaded = false;
862+ uint32_t dst = 0;
863+ for (ShippingItem& si : items_) {
864+ if (!unloaded) {
865+ PortDock* itemdest = si.get_destination(game);
866+ if (!itemdest || itemdest == &pd) {
867+ pd.shipping_item_arrived(game, si);
868+ unloaded = true;
869+ continue;
870+ }
871+ }
872+
873+ items_[dst++] = si;
874+ }
875+ items_.resize(dst);
876+ return unloaded;
877+}
878+
879+/**
880+ * Unload all items not favored by given next dest.
881+ * Assert all items for current portdock have already been unloaded.
882+ */
883+void Ship::unload_unfit_items(Game& game, PortDock& here, PortDock& nextdest) {
884+ uint32_t dst = 0;
885+ for (ShippingItem& si : items_) {
886+ if (fleet_->is_path_favourable(here, nextdest, *si.get_destination(game))) {
887+ items_[dst++] = si;
888 } else {
889- items_[dst++] = items_[src];
890+ here.shipping_item_returned(game, si);
891 }
892 }
893 items_.resize(dst);
894@@ -811,7 +850,7 @@
895 // I (tiborb) failed to invoke this situation when testing so
896 // I am not sure if following line behaves allright
897 get_fleet()->update(game);
898- start_task_idle(game, descr().main_animation(), 5000);
899+ start_task_idle(game, descr().main_animation(), kFleetInterval);
900 }
901 }
902
903@@ -969,8 +1008,12 @@
904 /// @note only called via player command
905 void Ship::sink_ship(Game& game) {
906 // Running colonization has the highest priority + a sink request is only valid once
907- if (!state_is_sinkable())
908+ if (!state_is_sinkable()) {
909 return;
910+ }
911+ if (destination_.is_set()) {
912+ destination_.get(game)->ship_coming(false);
913+ }
914 ship_state_ = ShipStates::kSinkRequest;
915 // Make sure the ship is active and close possible open windows
916 ship_wakeup(game);
917
918=== modified file 'src/logic/map_objects/tribes/ship.h'
919--- src/logic/map_objects/tribes/ship.h 2018-07-12 05:44:15 +0000
920+++ src/logic/map_objects/tribes/ship.h 2018-08-29 19:19:29 +0000
921@@ -78,6 +78,8 @@
922 DISALLOW_COPY_AND_ASSIGN(ShipDescr);
923 };
924
925+constexpr int32_t kShipInterval = 1500;
926+
927 /**
928 * Ships belong to a player and to an economy. The usually are in a (unique)
929 * fleet for a player, but only if they are on standard duty. Exploration ships
930@@ -104,7 +106,7 @@
931 return economy_;
932 }
933 void set_economy(Game&, Economy* e);
934- void set_destination(Game&, PortDock&);
935+ void set_destination(PortDock*);
936
937 void init_auto_task(Game&) override;
938
939@@ -126,8 +128,9 @@
940 return items_[idx];
941 }
942
943- void withdraw_items(Game& game, PortDock& pd, std::vector<ShippingItem>& items);
944- void add_item(Game&, const ShippingItem& item);
945+ void add_item(Game&, const ShippingItem&);
946+ bool withdraw_items(Game&, PortDock&);
947+ void unload_unfit_items(Game&, PortDock& here, PortDock& nextdest);
948
949 // A ship with task expedition can be in four states: kExpeditionWaiting, kExpeditionScouting,
950 // kExpeditionPortspaceFound or kExpeditionColonizing in the first states, the owning player of

Subscribers

People subscribed via source and target branches

to status/vote changes: