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
=== modified file 'src/economy/fleet.cc'
--- src/economy/fleet.cc 2018-04-07 16:59:00 +0000
+++ src/economy/fleet.cc 2018-08-29 19:19:29 +0000
@@ -628,8 +628,7 @@
628}628}
629629
630/**630/**
631 * Act callback updates ship scheduling. All decisions about where transport ships631 * Act callback updates ship scheduling of idle ships.
632 * are supposed to go are made via this function.
633 *632 *
634 * @note Do not call this directly; instead, trigger it via @ref update633 * @note Do not call this directly; instead, trigger it via @ref update
635 */634 */
@@ -637,230 +636,178 @@
637 act_pending_ = false;636 act_pending_ = false;
638637
639 if (!active()) {638 if (!active()) {
640 // If we are here, most likely act() was called by a port with waiting wares or an expedition639 // If we are here, most likely act() was called by a port with waiting wares or
641 // ready640 // with an expedition ready, although there are still no ships.
642 // although there are still no ships. We can't handle it now, so we reschedule the act()641 // We can't handle it now, so we reschedule the act()
643 schedule_act(game, 5000); // retry in the next time642 schedule_act(game, kFleetInterval); // retry in the next time
644 act_pending_ = true;643 act_pending_ = true;
645 return;644 return;
646 }645 }
647646
648 molog("Fleet::act\n");647 molog("Fleet::act\n");
649648
650 // we need to calculate what ship is to be send to which port649 // For each waiting port, try to find idle ships and send to it the closest one.
651 // for this we will have temporary data structure with format650 uint16_t waiting_ports = ports_.size();
652 // <<ship,port>,score>651 for (PortDock* p : ports_) {
653 // where ship and port are not objects but positions in ports_ and ships_652 if (p->get_need_ship() == 0) {
654 // this is to allow native hashing653 --waiting_ports;
655 std::map<std::pair<uint16_t, uint16_t>, uint16_t> scores;654 continue;
656655 }
657 // so we will identify all pairs: idle ship : ports, and score all such656
658 // pairs. We consider657 Ship* closest_ship = nullptr;
659 // - count of wares onboard, first ware (oldest) is counted as 8 (prioritization)658 float shortest_dist = 10000;
660 // (counting wares for particular port only)659 bool waiting = true;
661 // - count wares waiting at the port/3660
662 // - distance between ship and a port (0-10 points, the closer the more points)661 for (Ship* s : ships_) {
663 // - is another ship heading there right now?662 if (s->get_destination(game)) {
664663 if (s->get_destination(game) == p) {
665 // at the end we must know if requrests of all ports asking for ship were addressed664 waiting = false;
666 // if any unsatisfied, we must schedule new run of this function665 --waiting_ports;
667 // when we send a ship there, the port is removed from list666 break;
668 std::list<uint16_t> waiting_ports;667 }
669668 continue; // has already destination
670 // this is just helper - first member of scores map669 }
671 std::pair<uint16_t, uint16_t> mapping; // ship number, port number670 if (s->get_ship_state() != Ship::ShipStates::kTransport) {
672671 continue; // in expedition obviously
673 // first we go over ships - idle ones (=without destination)672 }
674 // then over wares on these ships and create first ship-port673
675 // pairs with score674 // here we get distance ship->port
676 for (uint16_t s = 0; s < ships_.size(); s += 1) {675 int16_t route_length = 10000;
677 if (ships_[s]->get_destination(game)) {676
678 continue;677 PortDock* cur_port = get_dock(game, s->get_position());
679 }678 if (cur_port) { // we are in a port
680 if (ships_[s]->get_ship_state() != Ship::ShipStates::kTransport) {679 if (cur_port == p) { // same port
681 continue; // in expedition obviously680 route_length = 0;
682 }681 } else { // different port
683682 Path tmp_path;
684 for (uint16_t i = 0; i < ships_[s]->get_nritems(); i += 1) {683 if (get_path(*cur_port, *p, tmp_path)) { // try to use precalculated path
685 PortDock* dst = ships_[s]->items_[i].get_destination(game);684 route_length = tmp_path.get_nsteps();
686 if (!dst) {685 }
687 // if wares without destination on ship without destination686 }
688 // such ship can be send to any port, and should be sent687 }
689 // to some port, so we add 1 point to score for each port688
690 for (uint16_t p = 0; p < ports_.size(); p += 1) {689 if (route_length == 10000) { // above failed, so we calculate path "manually"
691 mapping.first = s;690 // most probably the ship is not in a port (should not happen frequently)
692 mapping.second = p;691 route_length = s->calculate_sea_route(game, *p);
693 scores[mapping] += 1;692 }
694 }693
695 continue;694 if (route_length < shortest_dist) {
696 }695 shortest_dist = route_length;
697696 closest_ship = s;
698 bool destination_found = false; // Just a functional check697 }
699 for (uint16_t p = 0; p < ports_.size(); p += 1) {698 }
700 if (ports_[p] == ships_[s]->items_[i].get_destination(game)) {699
701 mapping.first = s;700 if (waiting && closest_ship) {
702 mapping.second = p;701 --waiting_ports;
703 scores[mapping] += (i == 0) ? 8 : 1;702 closest_ship->set_destination(p);
704 destination_found = true;703 closest_ship->send_signal(game, "wakeup");
705 }704 }
706 }705 }
707 if (!destination_found) {706
708 // Perhaps the throw here is too strong707 if (waiting_ports > 0) {
709 // we can still remove it before stable release if it proves too much708 molog("... there are %u ports requesting ship(s) we cannot satisfy yet\n", waiting_ports);
710 // during my testing this situation never happened709 schedule_act(game, kFleetInterval); // retry next time
711 throw wexception("A ware with destination that does not match any of player's"710 act_pending_ = true;
712 " ports, ship %u, ware's destination: %u",711 }
713 ships_[s]->serial(),712}
714 ships_[s]->items_[i].get_destination(game)->serial());713
715 }714/**
716 }715 * For the given three consecutive ports, decide if their path is favourable or not.
717 }716 * \return true/false for yes/no
718717 */
719 // now opposite aproach - we go over ports to find out those that have wares718bool Fleet::is_path_favourable(PortDock& start, PortDock& middle, PortDock& finish) {
720 // waiting for ship then find candidate ships to satisfy the requests719 if (&middle != &finish) {
721 for (uint16_t p = 0; p < ports_.size(); p += 1) {720 Path path_start_to_finish;
722 PortDock& pd = *ports_[p];721 Path path_middle_to_finish;
723 if (!pd.get_need_ship()) {722
724 continue;723 assert(get_path(start, finish, path_start_to_finish));
725 }724 if (get_path(middle, finish, path_middle_to_finish)) {
726725 if (path_middle_to_finish.get_nsteps() > path_start_to_finish.get_nsteps()) {
727 // general stategy is "one ship for port is enough", but sometimes726 return false;
728 // amount of ware waiting for ship is too high727 }
729 if (count_ships_heading_here(game, &pd) * 25 > pd.count_waiting()) {728 }
730 continue;729 }
731 }730 return true; // default
732731}
733 waiting_ports.push_back(p);732
734733/**
735 // scoring and entering the pair into scores (or increasing existing734 * For the given ship, go through all ports of this fleet
736 // score if the pair is already there)735 * and find the one with the best score.
737 for (uint16_t s = 0; s < ships_.size(); s += 1) {736 * \return that port
738737 *
739 if (ships_[s]->get_destination(game)) {738 * @note cur_port is never nullptr (we are always in a port),
740 continue; // already has destination739 * but that is kept in case the design changes in the future
741 }740 */
742741PortDock* Fleet::find_next_dest(Game& game, Ship& ship, PortDock* const cur_port) {
743 if (ships_[s]->get_ship_state() != Ship::ShipStates::kTransport) {742 // uint32_t const max_capacity = ship.descr().get_capacity();
744 continue; // in expedition obviously743 PortDock* best_port = nullptr;
745 }744 float best_score = 0;
746745
747 mapping.first = s;746 for (PortDock* p : ports_) {
748 mapping.second = p;747 if (p == cur_port) {
749 // following aproximately considers free capacity of a ship748 continue; // same port
750 scores[mapping] += ((ships_[s]->get_nritems() > 15) ? 1 : 3) +749 }
751 std::min(ships_[s]->descr().get_capacity() - ships_[s]->get_nritems(),750
752 ports_[p]->count_waiting()) /751 float score = 0;
753 3;752 WareInstance* ware;
754 }753 Worker* worker;
755 }754
756755 // score for wares/workers onboard that ship for that port
757 // Now adding score for distance756 for (uint16_t i = 0; i < ship.get_nritems(); ++i) {
758 for (auto ship_port_relation : scores) {757 ShippingItem& si = ship.items_[i];
758 if (si.get_destination(game) == p) {
759 si.get(game, &ware, &worker);
760 if (ware) {
761 score += 1; // TODO: increase by ware's importance
762 } else { // worker
763 score += 4;
764 }
765 }
766 }
767
768 if (cur_port) { // we are in a port
769 // score for wares/workers waiting at that port
770 for (uint16_t i = 0; i < cur_port->count_waiting(); ++i) {
771 ShippingItem& si = cur_port->waiting_[i];
772 if (si.get_destination(game) == p) {
773 si.get(game, &ware, &worker);
774 if (ware) {
775 score += 1; // TODO: increase by ware's importance
776 } else { // worker
777 score += 4;
778 }
779 }
780 }
781 }
782
783 if (score == 0 && p->get_need_ship() == 0) {
784 continue; // empty ship to empty port
785 }
759786
760 // here we get distance ship->port787 // here we get distance ship->port
761 // possibilities are:
762 // - we are in port and it is the same as target port
763 // - we are in other port, then we use get_dock() function to fetch precalculated path
764 // - if above fails, we calculate path "manually"
765 int16_t route_length = -1;788 int16_t route_length = -1;
766789
767 PortDock* current_portdock =790 if (cur_port) { // we are in a port
768 get_dock(game, ships_[ship_port_relation.first.first]->get_position());791 Path tmp_path;
769792 if (get_path(*cur_port, *p, tmp_path)) { // try to use precalculated path
770 if (current_portdock) { // we try to use precalculated paths of game793 route_length = tmp_path.get_nsteps();
771794 }
772 // we are in the same portdock795 }
773 if (current_portdock == ports_[ship_port_relation.first.second]) {796
774 route_length = 0;797 if (route_length == -1) { // above failed, so we calculate path "manually"
775 } else { // it is different portdock then798 // most probably the ship is not in a port (should not happen frequently)
776 Path tmp_path;799 route_length = ship.calculate_sea_route(game, *p);
777 if (get_path(*current_portdock, *ports_[ship_port_relation.first.second], tmp_path)) {800 }
778 route_length = tmp_path.get_nsteps();801
779 }802 score = (score + 1) * (score + p->get_need_ship());
780 }803 score = score * (1 - route_length / (score + route_length));
781 }804 if (score > best_score) {
782805 best_score = score;
783 // most probably the ship is not in a portdock (should not happen frequently)806 best_port = p;
784 if (route_length == -1) {807 }
785 route_length = ships_[ship_port_relation.first.first]->calculate_sea_route(808 }
786 game, *ports_[ship_port_relation.first.second]);809
787 }810 return best_port;
788
789 // now we have length of route, so we need to calculate score
790 int16_t score_for_distance = 0;
791 if (route_length < 3) {
792 score_for_distance = 10;
793 } else {
794 score_for_distance = 8 - route_length / 50;
795 }
796 // must not be negative
797 score_for_distance = (score_for_distance < 0) ? 0 : score_for_distance;
798
799 scores[ship_port_relation.first] += score_for_distance;
800 }
801
802 // looking for best scores and sending ships accordingly
803 uint16_t best_ship = 0;
804 uint16_t best_port = 0;
805
806 // after sending a ship we will remove one or more items from scores
807 while (!scores.empty()) {
808 uint16_t best_score = 0;
809
810 // searching for combination with highest score
811 for (const auto& combination : scores) {
812 if (combination.second > best_score) {
813 best_score = combination.second;
814 best_ship = combination.first.first;
815 best_port = combination.first.second;
816 }
817 }
818 if (best_score == 0) {
819 // this is check of correctnes of this algorithm, this should not happen
820 throw wexception("Fleet::act(): No port-destination pair selected or its score is zero");
821 }
822
823 // making sure the winner has no destination set
824 assert(!ships_[best_ship]->get_destination(game));
825
826 // now actual setting destination for "best ship"
827 ships_[best_ship]->set_destination(game, *ports_[best_port]);
828 molog("... ship %u sent to port %u, wares onboard: %2d, the port is asking for a ship: %s\n",
829 ships_[best_ship]->serial(), ports_[best_port]->serial(),
830 ships_[best_ship]->get_nritems(), (ports_[best_port]->get_need_ship()) ? "yes" : "no");
831
832 // pruning the scores table
833 // the ship that was just sent somewhere cannot be send elsewhere :)
834 for (auto it = scores.cbegin(); it != scores.cend();) {
835
836 // decreasing score for target port as there was a ship just sent there
837 if (it->first.second == best_port) {
838 mapping.first = it->first.first;
839 mapping.second = it->first.second;
840 scores[mapping] /= 2;
841 // just make sure it is nonzero
842 scores[mapping] = (scores[mapping] == 0) ? 1 : scores[mapping];
843 }
844
845 // but removing all pairs where best ship is participating as it is not available anymore
846 // (because it was sent to "best port")
847 if (it->first.first == best_ship) {
848 scores.erase(it++);
849 } else {
850 ++it;
851 }
852 }
853
854 // also removing the port from waiting_ports
855 waiting_ports.remove(best_port);
856 }
857
858 if (!waiting_ports.empty()) {
859 molog("... there are %" PRIuS " ports requesting ship(s) we cannot satisfy yet\n",
860 waiting_ports.size());
861 schedule_act(game, 5000); // retry next time
862 act_pending_ = true;
863 }
864}811}
865812
866void Fleet::log_general_info(const EditorGameBase& egbase) {813void Fleet::log_general_info(const EditorGameBase& egbase) {
867814
=== modified file 'src/economy/fleet.h'
--- src/economy/fleet.h 2018-04-16 07:03:12 +0000
+++ src/economy/fleet.h 2018-08-29 19:19:29 +0000
@@ -46,6 +46,8 @@
46 DISALLOW_COPY_AND_ASSIGN(FleetDescr);46 DISALLOW_COPY_AND_ASSIGN(FleetDescr);
47};47};
4848
49constexpr int32_t kFleetInterval = 5000;
50
49/**51/**
50 * Manage all ships and ports of a player that are connected52 * Manage all ships and ports of a player that are connected
51 * by ocean.53 * by ocean.
@@ -152,6 +154,8 @@
152 void save(EditorGameBase&, MapObjectSaver&, FileWrite&) override;154 void save(EditorGameBase&, MapObjectSaver&, FileWrite&) override;
153155
154 static MapObject::Loader* load(EditorGameBase&, MapObjectLoader&, FileRead&);156 static MapObject::Loader* load(EditorGameBase&, MapObjectLoader&, FileRead&);
157 bool is_path_favourable(PortDock& start, PortDock& middle, PortDock& finish);
158 PortDock* find_next_dest(Game&, Ship&, PortDock* const cur_port);
155};159};
156160
157} // namespace Widelands161} // namespace Widelands
158162
=== modified file 'src/economy/portdock.cc'
--- src/economy/portdock.cc 2018-04-16 07:03:12 +0000
+++ src/economy/portdock.cc 2018-08-29 19:19:29 +0000
@@ -34,6 +34,7 @@
34#include "logic/game_data_error.h"34#include "logic/game_data_error.h"
35#include "logic/map_objects/tribes/ship.h"35#include "logic/map_objects/tribes/ship.h"
36#include "logic/map_objects/tribes/warehouse.h"36#include "logic/map_objects/tribes/warehouse.h"
37#include "logic/path.h"
37#include "logic/player.h"38#include "logic/player.h"
38#include "logic/widelands_geometry_io.h"39#include "logic/widelands_geometry_io.h"
39#include "map_io/map_object_loader.h"40#include "map_io/map_object_loader.h"
@@ -55,7 +56,7 @@
55 : PlayerImmovable(g_portdock_descr),56 : PlayerImmovable(g_portdock_descr),
56 fleet_(nullptr),57 fleet_(nullptr),
57 warehouse_(wh),58 warehouse_(wh),
58 need_ship_(false),59 ships_coming_(0),
59 expedition_ready_(false) {60 expedition_ready_(false) {
60}61}
6162
@@ -116,6 +117,10 @@
116 return nullptr;117 return nullptr;
117}118}
118119
120uint32_t PortDock::get_need_ship() const {
121 return (waiting_.size() + (expedition_ready_ ? 20 : 0)) / (ships_coming_ + 1);
122}
123
119/**124/**
120 * Signal to the dock that it now belongs to the given economy.125 * Signal to the dock that it now belongs to the given economy.
121 *126 *
@@ -289,36 +294,58 @@
289294
290 // Destination might have vanished or be in another economy altogether.295 // Destination might have vanished or be in another economy altogether.
291 if (dst && dst->get_economy() == get_economy()) {296 if (dst && dst->get_economy() == get_economy()) {
292 set_need_ship(game, true);297 if (ships_coming_ <= 0) {
298 set_need_ship(game, true);
299 }
293 } else {300 } else {
294 it->set_location(game, warehouse_);301 it->set_location(game, warehouse_);
295 it->end_shipping(game);302 it->end_shipping(game);
296 *it = waiting_.back();303 *it = waiting_.back();
297 waiting_.pop_back();304 waiting_.pop_back();
298305 }
299 if (waiting_.empty())306}
300 set_need_ship(game, false);307
301 }308/**
302}309 * Receive shipping item from unloading ship.
303310 * Called by ship code.
304/**311 */
305 * A ship has arrived at the dock. Clear all items designated for this dock,312void PortDock::shipping_item_arrived(Game& game, ShippingItem& si) {
306 * and load the ship.313 si.set_location(game, warehouse_);
314 si.end_shipping(game);
315}
316
317/**
318 * Receive shipping item from departing ship.
319 * Called by ship code.
320 */
321void PortDock::shipping_item_returned(Game& game, ShippingItem& si) {
322 si.set_location(game, this);
323 waiting_.push_back(si);
324}
325
326/**
327 * A ship changed destination and is now coming at the dock. Increase counter for need_ship.
328 */
329void PortDock::ship_coming(bool affirmative) {
330 if (affirmative) {
331 ++ships_coming_;
332 } else {
333 ships_coming_ = std::max(0, ships_coming_ - 1); // max used for compatibility with old savegames
334 }
335}
336
337/**
338 * A ship has arrived at the dock. Set its next destination and load it accordingly.
307 */339 */
308void PortDock::ship_arrived(Game& game, Ship& ship) {340void PortDock::ship_arrived(Game& game, Ship& ship) {
309 std::vector<ShippingItem> items_brought_by_ship;341 ship_coming(false);
310 ship.withdraw_items(game, *this, items_brought_by_ship);
311
312 for (ShippingItem& shipping_item : items_brought_by_ship) {
313 shipping_item.set_location(game, warehouse_);
314 shipping_item.end_shipping(game);
315 }
316342
317 if (expedition_ready_) {343 if (expedition_ready_) {
318 assert(expedition_bootstrap_ != nullptr);344 assert(expedition_bootstrap_ != nullptr);
319345
320 // Only use an empty ship.346 // Only use an empty ship.
321 if (ship.get_nritems() < 1) {347 if (ship.get_nritems() < 1) {
348 ship.set_destination(nullptr);
322 // Load the ship349 // Load the ship
323 std::vector<Worker*> workers;350 std::vector<Worker*> workers;
324 std::vector<WareInstance*> wares;351 std::vector<WareInstance*> wares;
@@ -337,46 +364,73 @@
337 // The expedition goods are now on the ship, so from now on it is independent from the port364 // The expedition goods are now on the ship, so from now on it is independent from the port
338 // and thus we switch the port to normal, so we could even start a new expedition,365 // and thus we switch the port to normal, so we could even start a new expedition,
339 cancel_expedition(game);366 cancel_expedition(game);
340 return fleet_->update(game);367 fleet_->update(game);
341 }368 return;
342 }369 }
343370 }
344 if (ship.get_nritems() < ship.descr().get_capacity() && !waiting_.empty()) {371
345 uint32_t nrload =372 // decide where the arrived ship will go next
346 std::min<uint32_t>(waiting_.size(), ship.descr().get_capacity() - ship.get_nritems());373 PortDock* next_port = fleet_->find_next_dest(game, ship, this);
347374 if (!next_port) {
348 while (nrload--) {375 ship.set_destination(next_port);
349 // Check if the item has still a valid destination376 return; // no need to load anything
350 if (waiting_.back().get_destination(game)) {377 }
351 // Destination is valid, so we load the item onto the ship378
352 ship.add_item(game, waiting_.back());379 // unload any wares/workers onboard the departing ship which are not favored by next dest
353 } else {380 ship.unload_unfit_items(game, *this, *next_port);
354 // The item has no valid destination anymore, so we just carry it381
355 // back in the warehouse382 // then load the remaining capacity of the departing ship with relevant items
356 waiting_.back().set_location(game, warehouse_);383 uint32_t rest_capacity = ship.descr().get_capacity() - ship.get_nritems();
357 waiting_.back().end_shipping(game);384
358 }385 // firstly load the items which go to chosen dest, while also checking for items with invalid dest
359 waiting_.pop_back();386 uint32_t dst = 0;
360 }387 for (ShippingItem& si : waiting_) {
361388 PortDock* itemdest = si.get_destination(game);
362 if (waiting_.empty()) {389 if (itemdest) { // valid dest
363 set_need_ship(game, false);390 if (rest_capacity == 0) {
364 }391 waiting_[dst++] = si; // keep the item here
365 }392 } else {
366393 if (itemdest == next_port) { // the item goes to chosen dest
367 fleet_->update(game);394 ship.add_item(game, si); // load it
395 --rest_capacity;
396 } else { // different dest
397 waiting_[dst++] = si; // keep it here for now
398 }
399 }
400 } else { // invalid dest
401 // carry the item back in the warehouse
402 si.set_location(game, warehouse_);
403 si.end_shipping(game);
404 }
405 }
406 waiting_.resize(dst);
407
408 if (rest_capacity > 0) { // there is still capacity
409 // load any items favored by the chosen dest
410 dst = 0;
411 for (ShippingItem& si : waiting_) {
412 if (rest_capacity == 0) {
413 waiting_[dst++] = si; // keep the item here
414 } else {
415 if (fleet_->is_path_favourable(*this, *next_port, *si.get_destination(game))) {
416 ship.add_item(game, si);
417 --rest_capacity;
418 } else { // item is not favored by the chosen dest
419 // spare it from getting trapped inside the wrong ship
420 waiting_[dst++] = si;
421 }
422 }
423 }
424 waiting_.resize(dst);
425 }
426 ship.set_destination(next_port);
427
428 set_need_ship(game, !waiting_.empty());
368}429}
369430
370void PortDock::set_need_ship(Game& game, bool need) {431void PortDock::set_need_ship(Game& game, bool need) {
371 molog("set_need_ship(%s)\n", need ? "true" : "false");432 if (need && fleet_) {
372433 molog("trigger fleet update\n");
373 if (need == need_ship_)
374 return;
375
376 need_ship_ = need;
377
378 if (fleet_) {
379 molog("... trigger fleet update\n");
380 fleet_->update(game);434 fleet_->update(game);
381 }435 }
382}436}
@@ -445,13 +499,13 @@
445499
446 if (warehouse_) {500 if (warehouse_) {
447 Coords pos(warehouse_->get_position());501 Coords pos(warehouse_->get_position());
448 molog("PortDock for warehouse %u (at %i,%i) in fleet %u, need_ship: %s, waiting: %" PRIuS502 molog("PortDock for warehouse %u (at %i,%i) in fleet %u, expedition_ready: %s, waiting: %" PRIuS
449 "\n",503 "\n",
450 warehouse_->serial(), pos.x, pos.y, fleet_ ? fleet_->serial() : 0,504 warehouse_->serial(), pos.x, pos.y, fleet_ ? fleet_->serial() : 0,
451 need_ship_ ? "true" : "false", waiting_.size());505 expedition_ready_ ? "true" : "false", waiting_.size());
452 } else {506 } else {
453 molog("PortDock without a warehouse in fleet %u, need_ship: %s, waiting: %" PRIuS "\n",507 molog("PortDock without a warehouse in fleet %u, expedition_ready: %s, waiting: %" PRIuS "\n",
454 fleet_ ? fleet_->serial() : 0, need_ship_ ? "true" : "false", waiting_.size());508 fleet_ ? fleet_->serial() : 0, expedition_ready_ ? "true" : "false", waiting_.size());
455 }509 }
456510
457 for (ShippingItem& shipping_item : waiting_) {511 for (ShippingItem& shipping_item : waiting_) {
@@ -479,7 +533,7 @@
479 pd.set_position(egbase(), pd.dockpoints_[i]);533 pd.set_position(egbase(), pd.dockpoints_[i]);
480 }534 }
481535
482 pd.need_ship_ = fr.unsigned_8();536 pd.ships_coming_ = fr.unsigned_8();
483537
484 waiting_.resize(fr.unsigned_32());538 waiting_.resize(fr.unsigned_32());
485 for (ShippingItem::Loader& shipping_loader : waiting_) {539 for (ShippingItem::Loader& shipping_loader : waiting_) {
@@ -553,7 +607,7 @@
553 write_coords_32(&fw, coords);607 write_coords_32(&fw, coords);
554 }608 }
555609
556 fw.unsigned_8(need_ship_);610 fw.unsigned_8(ships_coming_);
557611
558 fw.unsigned_32(waiting_.size());612 fw.unsigned_32(waiting_.size());
559 for (ShippingItem& shipping_item : waiting_) {613 for (ShippingItem& shipping_item : waiting_) {
560614
=== modified file 'src/economy/portdock.h'
--- src/economy/portdock.h 2018-04-07 16:59:00 +0000
+++ src/economy/portdock.h 2018-08-29 19:19:29 +0000
@@ -85,9 +85,7 @@
85 return fleet_;85 return fleet_;
86 }86 }
87 PortDock* get_dock(Flag& flag) const;87 PortDock* get_dock(Flag& flag) const;
88 bool get_need_ship() const {88 uint32_t get_need_ship() const;
89 return need_ship_ || expedition_ready_;
90 }
9189
92 void set_economy(Economy*) override;90 void set_economy(Economy*) override;
9391
@@ -113,6 +111,9 @@
113 void add_shippingitem(Game&, Worker&);111 void add_shippingitem(Game&, Worker&);
114 void update_shippingitem(Game&, Worker&);112 void update_shippingitem(Game&, Worker&);
115113
114 void shipping_item_arrived(Game&, ShippingItem&);
115 void shipping_item_returned(Game&, ShippingItem&);
116 void ship_coming(bool affirmative);
116 void ship_arrived(Game&, Ship&);117 void ship_arrived(Game&, Ship&);
117118
118 void log_general_info(const EditorGameBase&) override;119 void log_general_info(const EditorGameBase&) override;
@@ -146,7 +147,7 @@
146 Warehouse* warehouse_;147 Warehouse* warehouse_;
147 PositionList dockpoints_;148 PositionList dockpoints_;
148 std::vector<ShippingItem> waiting_;149 std::vector<ShippingItem> waiting_;
149 bool need_ship_;150 uint8_t ships_coming_;
150 bool expedition_ready_;151 bool expedition_ready_;
151152
152 std::unique_ptr<ExpeditionBootstrap> expedition_bootstrap_;153 std::unique_ptr<ExpeditionBootstrap> expedition_bootstrap_;
153154
=== modified file 'src/logic/map_objects/tribes/ship.cc'
--- src/logic/map_objects/tribes/ship.cc 2018-07-08 15:16:16 +0000
+++ src/logic/map_objects/tribes/ship.cc 2018-08-29 19:19:29 +0000
@@ -303,12 +303,22 @@
303303
304 FCoords position = map.get_fcoords(get_position());304 FCoords position = map.get_fcoords(get_position());
305 if (position.field->get_immovable() == dst) {305 if (position.field->get_immovable() == dst) {
306 molog("ship_update: Arrived at dock %u\n", dst->serial());306 if (lastdock_ != dst) {
307 lastdock_ = dst;307 molog("ship_update: Arrived at dock %u\n", dst->serial());
308 destination_ = nullptr;308 lastdock_ = dst;
309 dst->ship_arrived(game, *this);309 }
310 start_task_idle(game, descr().main_animation(), 250);310 if (withdraw_items(game, *dst)) {
311 Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged));311 schedule_act(game, kShipInterval);
312 return true;
313 }
314
315 dst->ship_arrived(game, *this); // this should call set_destination
316 dst = get_destination(game);
317 if (dst) {
318 start_task_movetodock(game, *dst);
319 } else {
320 start_task_idle(game, descr().main_animation(), 250);
321 }
312 return true;322 return true;
313 }323 }
314324
@@ -484,7 +494,7 @@
484 }494 }
485495
486 if (totalprob == 0) {496 if (totalprob == 0) {
487 start_task_idle(game, descr().main_animation(), 1500);497 start_task_idle(game, descr().main_animation(), kShipInterval);
488 return;498 return;
489 }499 }
490500
@@ -496,13 +506,13 @@
496 }506 }
497507
498 if (dir == 0 || dir > LAST_DIRECTION) {508 if (dir == 0 || dir > LAST_DIRECTION) {
499 start_task_idle(game, descr().main_animation(), 1500);509 start_task_idle(game, descr().main_animation(), kShipInterval);
500 return;510 return;
501 }511 }
502512
503 FCoords neighbour = map.get_neighbour(position, dir);513 FCoords neighbour = map.get_neighbour(position, dir);
504 if (!(neighbour.field->nodecaps() & MOVECAPS_SWIM)) {514 if (!(neighbour.field->nodecaps() & MOVECAPS_SWIM)) {
505 start_task_idle(game, descr().main_animation(), 1500);515 start_task_idle(game, descr().main_animation(), kShipInterval);
506 return;516 return;
507 }517 }
508518
@@ -542,7 +552,7 @@
542 pgettext("ship", "Waiting"), _("Island Circumnavigated"),552 pgettext("ship", "Waiting"), _("Island Circumnavigated"),
543 _("An expedition ship sailed around its island without any events."),553 _("An expedition ship sailed around its island without any events."),
544 "images/wui/ship/ship_explore_island_cw.png");554 "images/wui/ship/ship_explore_island_cw.png");
545 return start_task_idle(game, descr().main_animation(), 1500);555 return start_task_idle(game, descr().main_animation(), kShipInterval);
546 }556 }
547 }557 }
548 // The ship is supposed to follow the coast as close as possible, therefore the check558 // The ship is supposed to follow the coast as close as possible, therefore the check
@@ -585,7 +595,7 @@
585 shipname_.c_str());595 shipname_.c_str());
586 set_ship_state_and_notify(596 set_ship_state_and_notify(
587 ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand);597 ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand);
588 start_task_idle(game, descr().main_animation(), 1500);598 start_task_idle(game, descr().main_animation(), kShipInterval);
589 return;599 return;
590 }600 }
591 } else { // scouting towards a specific direction601 } else { // scouting towards a specific direction
@@ -598,7 +608,7 @@
598 // coast reached608 // coast reached
599 set_ship_state_and_notify(609 set_ship_state_and_notify(
600 ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand);610 ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand);
601 start_task_idle(game, descr().main_animation(), 1500);611 start_task_idle(game, descr().main_animation(), kShipInterval);
602 // Send a message to the player, that a new coast was reached612 // Send a message to the player, that a new coast was reached
603 send_message(game,613 send_message(game,
604 /** TRANSLATORS: A ship has discovered land */614 /** TRANSLATORS: A ship has discovered land */
@@ -692,7 +702,7 @@
692 }702 }
693703
694 expedition_.reset(nullptr);704 expedition_.reset(nullptr);
695 return start_task_idle(game, descr().main_animation(), 1500);705 return start_task_idle(game, descr().main_animation(), kShipInterval);
696 }706 }
697 }707 }
698 FALLS_THROUGH;708 FALLS_THROUGH;
@@ -701,7 +711,7 @@
701 case ShipStates::kSinkRequest:711 case ShipStates::kSinkRequest:
702 case ShipStates::kSinkAnimation: {712 case ShipStates::kSinkAnimation: {
703 // wait for input713 // wait for input
704 start_task_idle(game, descr().main_animation(), 1500);714 start_task_idle(game, descr().main_animation(), kShipInterval);
705 return;715 return;
706 }716 }
707 }717 }
@@ -727,14 +737,17 @@
727737
728/**738/**
729 * Enter a new destination port for the ship.739 * Enter a new destination port for the ship.
730 *740 * Call this after un/loading the ship, for proper logging.
731 * @note This is supposed to be called only from the scheduling code of @ref Fleet.
732 */741 */
733void Ship::set_destination(Game& game, PortDock& pd) {742void Ship::set_destination(PortDock* pd) {
734 molog("set_destination / sending to portdock %u (carrying %" PRIuS " items)\n", pd.serial(),743 destination_ = pd;
735 items_.size());744 if (pd) {
736 destination_ = &pd;745 molog("set_destination / sending to portdock %u (carrying %" PRIuS " items)\n", pd->serial(),
737 send_signal(game, "wakeup");746 items_.size());
747 pd->ship_coming(true);
748 } else {
749 molog("set_destination / none\n");
750 }
738 Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged));751 Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged));
739}752}
740753
@@ -745,14 +758,40 @@
745 items_.back().set_location(game, this);758 items_.back().set_location(game, this);
746}759}
747760
748void Ship::withdraw_items(Game& game, PortDock& pd, std::vector<ShippingItem>& items) {761/**
749 uint32_t dst = 0;762 * Unload one item designated for given dock or for no dock.
750 for (uint32_t src = 0; src < items_.size(); ++src) {763 * \return true if item unloaded.
751 PortDock* destination = items_[src].get_destination(game);764 */
752 if (!destination || destination == &pd) {765bool Ship::withdraw_items(Game& game, PortDock& pd) {
753 items.push_back(items_[src]);766 bool unloaded = false;
767 uint32_t dst = 0;
768 for (ShippingItem& si : items_) {
769 if (!unloaded) {
770 PortDock* itemdest = si.get_destination(game);
771 if (!itemdest || itemdest == &pd) {
772 pd.shipping_item_arrived(game, si);
773 unloaded = true;
774 continue;
775 }
776 }
777
778 items_[dst++] = si;
779 }
780 items_.resize(dst);
781 return unloaded;
782}
783
784/**
785 * Unload all items not favored by given next dest.
786 * Assert all items for current portdock have already been unloaded.
787 */
788void Ship::unload_unfit_items(Game& game, PortDock& here, PortDock& nextdest) {
789 uint32_t dst = 0;
790 for (ShippingItem& si : items_) {
791 if (fleet_->is_path_favourable(here, nextdest, *si.get_destination(game))) {
792 items_[dst++] = si;
754 } else {793 } else {
755 items_[dst++] = items_[src];794 here.shipping_item_returned(game, si);
756 }795 }
757 }796 }
758 items_.resize(dst);797 items_.resize(dst);
@@ -811,7 +850,7 @@
811 // I (tiborb) failed to invoke this situation when testing so850 // I (tiborb) failed to invoke this situation when testing so
812 // I am not sure if following line behaves allright851 // I am not sure if following line behaves allright
813 get_fleet()->update(game);852 get_fleet()->update(game);
814 start_task_idle(game, descr().main_animation(), 5000);853 start_task_idle(game, descr().main_animation(), kFleetInterval);
815 }854 }
816}855}
817856
@@ -969,8 +1008,12 @@
969/// @note only called via player command1008/// @note only called via player command
970void Ship::sink_ship(Game& game) {1009void Ship::sink_ship(Game& game) {
971 // Running colonization has the highest priority + a sink request is only valid once1010 // Running colonization has the highest priority + a sink request is only valid once
972 if (!state_is_sinkable())1011 if (!state_is_sinkable()) {
973 return;1012 return;
1013 }
1014 if (destination_.is_set()) {
1015 destination_.get(game)->ship_coming(false);
1016 }
974 ship_state_ = ShipStates::kSinkRequest;1017 ship_state_ = ShipStates::kSinkRequest;
975 // Make sure the ship is active and close possible open windows1018 // Make sure the ship is active and close possible open windows
976 ship_wakeup(game);1019 ship_wakeup(game);
9771020
=== modified file 'src/logic/map_objects/tribes/ship.h'
--- src/logic/map_objects/tribes/ship.h 2018-07-12 05:44:15 +0000
+++ src/logic/map_objects/tribes/ship.h 2018-08-29 19:19:29 +0000
@@ -78,6 +78,8 @@
78 DISALLOW_COPY_AND_ASSIGN(ShipDescr);78 DISALLOW_COPY_AND_ASSIGN(ShipDescr);
79};79};
8080
81constexpr int32_t kShipInterval = 1500;
82
81/**83/**
82 * Ships belong to a player and to an economy. The usually are in a (unique)84 * Ships belong to a player and to an economy. The usually are in a (unique)
83 * fleet for a player, but only if they are on standard duty. Exploration ships85 * fleet for a player, but only if they are on standard duty. Exploration ships
@@ -104,7 +106,7 @@
104 return economy_;106 return economy_;
105 }107 }
106 void set_economy(Game&, Economy* e);108 void set_economy(Game&, Economy* e);
107 void set_destination(Game&, PortDock&);109 void set_destination(PortDock*);
108110
109 void init_auto_task(Game&) override;111 void init_auto_task(Game&) override;
110112
@@ -126,8 +128,9 @@
126 return items_[idx];128 return items_[idx];
127 }129 }
128130
129 void withdraw_items(Game& game, PortDock& pd, std::vector<ShippingItem>& items);131 void add_item(Game&, const ShippingItem&);
130 void add_item(Game&, const ShippingItem& item);132 bool withdraw_items(Game&, PortDock&);
133 void unload_unfit_items(Game&, PortDock& here, PortDock& nextdest);
131134
132 // A ship with task expedition can be in four states: kExpeditionWaiting, kExpeditionScouting,135 // A ship with task expedition can be in four states: kExpeditionWaiting, kExpeditionScouting,
133 // kExpeditionPortspaceFound or kExpeditionColonizing in the first states, the owning player of136 // kExpeditionPortspaceFound or kExpeditionColonizing in the first states, the owning player of

Subscribers

People subscribed via source and target branches

to status/vote changes: