ypopezios has proposed merging lp:~widelands-dev/widelands/ship_scheduling_2 into lp:widelands.
Requested reviews: Widelands Developers (widelands-dev) For more details, see: https://code.launchpad.net/~widelands-dev/widelands/ship_scheduling_2/+merge/354019 See description of the branch: https://code.launchpad.net/~widelands-dev/widelands/ship_scheduling_2 -- Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/ship_scheduling_2 into lp:widelands.
=== modified file 'src/economy/fleet.cc' --- src/economy/fleet.cc 2018-04-07 16:59:00 +0000 +++ src/economy/fleet.cc 2018-08-30 06:13:48 +0000 @@ -628,8 +628,7 @@ } /** - * Act callback updates ship scheduling. All decisions about where transport ships - * are supposed to go are made via this function. + * Act callback updates ship scheduling of idle ships. * * @note Do not call this directly; instead, trigger it via @ref update */ @@ -637,230 +636,178 @@ act_pending_ = false; if (!active()) { - // If we are here, most likely act() was called by a port with waiting wares or an expedition - // ready - // although there are still no ships. We can't handle it now, so we reschedule the act() - schedule_act(game, 5000); // retry in the next time + // If we are here, most likely act() was called by a port with waiting wares or + // with an expedition ready, although there are still no ships. + // We can't handle it now, so we reschedule the act() + schedule_act(game, kFleetInterval); // retry in the next time act_pending_ = true; return; } molog("Fleet::act\n"); - // we need to calculate what ship is to be send to which port - // for this we will have temporary data structure with format - // <<ship,port>,score> - // where ship and port are not objects but positions in ports_ and ships_ - // this is to allow native hashing - std::map<std::pair<uint16_t, uint16_t>, uint16_t> scores; - - // so we will identify all pairs: idle ship : ports, and score all such - // pairs. We consider - // - count of wares onboard, first ware (oldest) is counted as 8 (prioritization) - // (counting wares for particular port only) - // - count wares waiting at the port/3 - // - distance between ship and a port (0-10 points, the closer the more points) - // - is another ship heading there right now? - - // at the end we must know if requrests of all ports asking for ship were addressed - // if any unsatisfied, we must schedule new run of this function - // when we send a ship there, the port is removed from list - std::list<uint16_t> waiting_ports; - - // this is just helper - first member of scores map - std::pair<uint16_t, uint16_t> mapping; // ship number, port number - - // first we go over ships - idle ones (=without destination) - // then over wares on these ships and create first ship-port - // pairs with score - for (uint16_t s = 0; s < ships_.size(); s += 1) { - if (ships_[s]->get_destination(game)) { - continue; - } - if (ships_[s]->get_ship_state() != Ship::ShipStates::kTransport) { - continue; // in expedition obviously - } - - for (uint16_t i = 0; i < ships_[s]->get_nritems(); i += 1) { - PortDock* dst = ships_[s]->items_[i].get_destination(game); - if (!dst) { - // if wares without destination on ship without destination - // such ship can be send to any port, and should be sent - // to some port, so we add 1 point to score for each port - for (uint16_t p = 0; p < ports_.size(); p += 1) { - mapping.first = s; - mapping.second = p; - scores[mapping] += 1; - } - continue; - } - - bool destination_found = false; // Just a functional check - for (uint16_t p = 0; p < ports_.size(); p += 1) { - if (ports_[p] == ships_[s]->items_[i].get_destination(game)) { - mapping.first = s; - mapping.second = p; - scores[mapping] += (i == 0) ? 8 : 1; - destination_found = true; - } - } - if (!destination_found) { - // Perhaps the throw here is too strong - // we can still remove it before stable release if it proves too much - // during my testing this situation never happened - throw wexception("A ware with destination that does not match any of player's" - " ports, ship %u, ware's destination: %u", - ships_[s]->serial(), - ships_[s]->items_[i].get_destination(game)->serial()); - } - } - } - - // now opposite aproach - we go over ports to find out those that have wares - // waiting for ship then find candidate ships to satisfy the requests - for (uint16_t p = 0; p < ports_.size(); p += 1) { - PortDock& pd = *ports_[p]; - if (!pd.get_need_ship()) { - continue; - } - - // general stategy is "one ship for port is enough", but sometimes - // amount of ware waiting for ship is too high - if (count_ships_heading_here(game, &pd) * 25 > pd.count_waiting()) { - continue; - } - - waiting_ports.push_back(p); - - // scoring and entering the pair into scores (or increasing existing - // score if the pair is already there) - for (uint16_t s = 0; s < ships_.size(); s += 1) { - - if (ships_[s]->get_destination(game)) { - continue; // already has destination - } - - if (ships_[s]->get_ship_state() != Ship::ShipStates::kTransport) { - continue; // in expedition obviously - } - - mapping.first = s; - mapping.second = p; - // following aproximately considers free capacity of a ship - scores[mapping] += ((ships_[s]->get_nritems() > 15) ? 1 : 3) + - std::min(ships_[s]->descr().get_capacity() - ships_[s]->get_nritems(), - ports_[p]->count_waiting()) / - 3; - } - } - - // Now adding score for distance - for (auto ship_port_relation : scores) { + // For each waiting port, try to find idle ships and send to it the closest one. + uint16_t waiting_ports = ports_.size(); + for (PortDock* p : ports_) { + if (p->get_need_ship() == 0) { + --waiting_ports; + continue; + } + + Ship* closest_ship = nullptr; + float shortest_dist = 10000; + bool waiting = true; + + for (Ship* s : ships_) { + if (s->get_destination(game)) { + if (s->get_destination(game) == p) { + waiting = false; + --waiting_ports; + break; + } + continue; // has already destination + } + if (s->get_ship_state() != Ship::ShipStates::kTransport) { + continue; // in expedition obviously + } + + // here we get distance ship->port + int16_t route_length = 10000; + + PortDock* cur_port = get_dock(game, s->get_position()); + if (cur_port) { // we are in a port + if (cur_port == p) { // same port + route_length = 0; + } else { // different port + Path tmp_path; + if (get_path(*cur_port, *p, tmp_path)) { // try to use precalculated path + route_length = tmp_path.get_nsteps(); + } + } + } + + if (route_length == 10000) { // above failed, so we calculate path "manually" + // most probably the ship is not in a port (should not happen frequently) + route_length = s->calculate_sea_route(game, *p); + } + + if (route_length < shortest_dist) { + shortest_dist = route_length; + closest_ship = s; + } + } + + if (waiting && closest_ship) { + --waiting_ports; + closest_ship->set_destination(p); + closest_ship->send_signal(game, "wakeup"); + } + } + + if (waiting_ports > 0) { + molog("... there are %u ports requesting ship(s) we cannot satisfy yet\n", waiting_ports); + schedule_act(game, kFleetInterval); // retry next time + act_pending_ = true; + } +} + +/** + * For the given three consecutive ports, decide if their path is favourable or not. + * \return true/false for yes/no + */ +bool Fleet::is_path_favourable(PortDock& start, PortDock& middle, PortDock& finish) { + if (&middle != &finish) { + Path path_start_to_finish; + Path path_middle_to_finish; + + assert(get_path(start, finish, path_start_to_finish)); + if (get_path(middle, finish, path_middle_to_finish)) { + if (path_middle_to_finish.get_nsteps() > path_start_to_finish.get_nsteps()) { + return false; + } + } + } + return true; // default +} + +/** + * For the given ship, go through all ports of this fleet + * and find the one with the best score. + * \return that port + * + * @note cur_port is never nullptr (we are always in a port), + * but that is kept in case the design changes in the future + */ +PortDock* Fleet::find_next_dest(Game& game, Ship& ship, PortDock* const cur_port) { + // uint32_t const max_capacity = ship.descr().get_capacity(); + PortDock* best_port = nullptr; + float best_score = 0; + + for (PortDock* p : ports_) { + if (p == cur_port) { + continue; // same port + } + + float score = 0; + WareInstance* ware; + Worker* worker; + + // score for wares/workers onboard that ship for that port + for (uint16_t i = 0; i < ship.get_nritems(); ++i) { + ShippingItem& si = ship.items_[i]; + if (si.get_destination(game) == p) { + si.get(game, &ware, &worker); + if (ware) { + score += 1; // TODO: increase by ware's importance + } else { // worker + score += 4; + } + } + } + + if (cur_port) { // we are in a port + // score for wares/workers waiting at that port + for (uint16_t i = 0; i < cur_port->count_waiting(); ++i) { + ShippingItem& si = cur_port->waiting_[i]; + if (si.get_destination(game) == p) { + si.get(game, &ware, &worker); + if (ware) { + score += 1; // TODO: increase by ware's importance + } else { // worker + score += 4; + } + } + } + } + + if (score == 0 && p->get_need_ship() == 0) { + continue; // empty ship to empty port + } // here we get distance ship->port - // possibilities are: - // - we are in port and it is the same as target port - // - we are in other port, then we use get_dock() function to fetch precalculated path - // - if above fails, we calculate path "manually" int16_t route_length = -1; - PortDock* current_portdock = - get_dock(game, ships_[ship_port_relation.first.first]->get_position()); - - if (current_portdock) { // we try to use precalculated paths of game - - // we are in the same portdock - if (current_portdock == ports_[ship_port_relation.first.second]) { - route_length = 0; - } else { // it is different portdock then - Path tmp_path; - if (get_path(*current_portdock, *ports_[ship_port_relation.first.second], tmp_path)) { - route_length = tmp_path.get_nsteps(); - } - } - } - - // most probably the ship is not in a portdock (should not happen frequently) - if (route_length == -1) { - route_length = ships_[ship_port_relation.first.first]->calculate_sea_route( - game, *ports_[ship_port_relation.first.second]); - } - - // now we have length of route, so we need to calculate score - int16_t score_for_distance = 0; - if (route_length < 3) { - score_for_distance = 10; - } else { - score_for_distance = 8 - route_length / 50; - } - // must not be negative - score_for_distance = (score_for_distance < 0) ? 0 : score_for_distance; - - scores[ship_port_relation.first] += score_for_distance; - } - - // looking for best scores and sending ships accordingly - uint16_t best_ship = 0; - uint16_t best_port = 0; - - // after sending a ship we will remove one or more items from scores - while (!scores.empty()) { - uint16_t best_score = 0; - - // searching for combination with highest score - for (const auto& combination : scores) { - if (combination.second > best_score) { - best_score = combination.second; - best_ship = combination.first.first; - best_port = combination.first.second; - } - } - if (best_score == 0) { - // this is check of correctnes of this algorithm, this should not happen - throw wexception("Fleet::act(): No port-destination pair selected or its score is zero"); - } - - // making sure the winner has no destination set - assert(!ships_[best_ship]->get_destination(game)); - - // now actual setting destination for "best ship" - ships_[best_ship]->set_destination(game, *ports_[best_port]); - molog("... ship %u sent to port %u, wares onboard: %2d, the port is asking for a ship: %s\n", - ships_[best_ship]->serial(), ports_[best_port]->serial(), - ships_[best_ship]->get_nritems(), (ports_[best_port]->get_need_ship()) ? "yes" : "no"); - - // pruning the scores table - // the ship that was just sent somewhere cannot be send elsewhere :) - for (auto it = scores.cbegin(); it != scores.cend();) { - - // decreasing score for target port as there was a ship just sent there - if (it->first.second == best_port) { - mapping.first = it->first.first; - mapping.second = it->first.second; - scores[mapping] /= 2; - // just make sure it is nonzero - scores[mapping] = (scores[mapping] == 0) ? 1 : scores[mapping]; - } - - // but removing all pairs where best ship is participating as it is not available anymore - // (because it was sent to "best port") - if (it->first.first == best_ship) { - scores.erase(it++); - } else { - ++it; - } - } - - // also removing the port from waiting_ports - waiting_ports.remove(best_port); - } - - if (!waiting_ports.empty()) { - molog("... there are %" PRIuS " ports requesting ship(s) we cannot satisfy yet\n", - waiting_ports.size()); - schedule_act(game, 5000); // retry next time - act_pending_ = true; - } + if (cur_port) { // we are in a port + Path tmp_path; + if (get_path(*cur_port, *p, tmp_path)) { // try to use precalculated path + route_length = tmp_path.get_nsteps(); + } + } + + if (route_length == -1) { // above failed, so we calculate path "manually" + // most probably the ship is not in a port (should not happen frequently) + route_length = ship.calculate_sea_route(game, *p); + } + + score = (score + 1) * (score + p->get_need_ship()); + score = score * (1 - route_length / (score + route_length)); + if (score > best_score) { + best_score = score; + best_port = p; + } + } + + return best_port; } void Fleet::log_general_info(const EditorGameBase& egbase) { === modified file 'src/economy/fleet.h' --- src/economy/fleet.h 2018-04-16 07:03:12 +0000 +++ src/economy/fleet.h 2018-08-30 06:13:48 +0000 @@ -46,6 +46,8 @@ DISALLOW_COPY_AND_ASSIGN(FleetDescr); }; +constexpr int32_t kFleetInterval = 5000; + /** * Manage all ships and ports of a player that are connected * by ocean. @@ -152,6 +154,8 @@ void save(EditorGameBase&, MapObjectSaver&, FileWrite&) override; static MapObject::Loader* load(EditorGameBase&, MapObjectLoader&, FileRead&); + bool is_path_favourable(PortDock& start, PortDock& middle, PortDock& finish); + PortDock* find_next_dest(Game&, Ship&, PortDock* const cur_port); }; } // namespace Widelands === modified file 'src/economy/portdock.cc' --- src/economy/portdock.cc 2018-04-16 07:03:12 +0000 +++ src/economy/portdock.cc 2018-08-30 06:13:48 +0000 @@ -34,6 +34,7 @@ #include "logic/game_data_error.h" #include "logic/map_objects/tribes/ship.h" #include "logic/map_objects/tribes/warehouse.h" +#include "logic/path.h" #include "logic/player.h" #include "logic/widelands_geometry_io.h" #include "map_io/map_object_loader.h" @@ -55,7 +56,7 @@ : PlayerImmovable(g_portdock_descr), fleet_(nullptr), warehouse_(wh), - need_ship_(false), + ships_coming_(0), expedition_ready_(false) { } @@ -116,6 +117,10 @@ return nullptr; } +uint32_t PortDock::get_need_ship() const { + return (waiting_.size() + (expedition_ready_ ? 20 : 0)) / (ships_coming_ + 1); +} + /** * Signal to the dock that it now belongs to the given economy. * @@ -289,36 +294,58 @@ // Destination might have vanished or be in another economy altogether. if (dst && dst->get_economy() == get_economy()) { - set_need_ship(game, true); + if (ships_coming_ <= 0) { + set_need_ship(game, true); + } } else { it->set_location(game, warehouse_); it->end_shipping(game); *it = waiting_.back(); waiting_.pop_back(); - - if (waiting_.empty()) - set_need_ship(game, false); - } -} - -/** - * A ship has arrived at the dock. Clear all items designated for this dock, - * and load the ship. + } +} + +/** + * Receive shipping item from unloading ship. + * Called by ship code. + */ +void PortDock::shipping_item_arrived(Game& game, ShippingItem& si) { + si.set_location(game, warehouse_); + si.end_shipping(game); +} + +/** + * Receive shipping item from departing ship. + * Called by ship code. + */ +void PortDock::shipping_item_returned(Game& game, ShippingItem& si) { + si.set_location(game, this); + waiting_.push_back(si); +} + +/** + * A ship changed destination and is now coming at the dock. Increase counter for need_ship. + */ +void PortDock::ship_coming(bool affirmative) { + if (affirmative) { + ++ships_coming_; + } else { + ships_coming_ = std::max(0, ships_coming_ - 1); // max used for compatibility with old savegames + } +} + +/** + * A ship has arrived at the dock. Set its next destination and load it accordingly. */ void PortDock::ship_arrived(Game& game, Ship& ship) { - std::vector<ShippingItem> items_brought_by_ship; - ship.withdraw_items(game, *this, items_brought_by_ship); - - for (ShippingItem& shipping_item : items_brought_by_ship) { - shipping_item.set_location(game, warehouse_); - shipping_item.end_shipping(game); - } + ship_coming(false); if (expedition_ready_) { assert(expedition_bootstrap_ != nullptr); // Only use an empty ship. if (ship.get_nritems() < 1) { + ship.set_destination(nullptr); // Load the ship std::vector<Worker*> workers; std::vector<WareInstance*> wares; @@ -337,46 +364,73 @@ // The expedition goods are now on the ship, so from now on it is independent from the port // and thus we switch the port to normal, so we could even start a new expedition, cancel_expedition(game); - return fleet_->update(game); - } - } - - if (ship.get_nritems() < ship.descr().get_capacity() && !waiting_.empty()) { - uint32_t nrload = - std::min<uint32_t>(waiting_.size(), ship.descr().get_capacity() - ship.get_nritems()); - - while (nrload--) { - // Check if the item has still a valid destination - if (waiting_.back().get_destination(game)) { - // Destination is valid, so we load the item onto the ship - ship.add_item(game, waiting_.back()); - } else { - // The item has no valid destination anymore, so we just carry it - // back in the warehouse - waiting_.back().set_location(game, warehouse_); - waiting_.back().end_shipping(game); - } - waiting_.pop_back(); - } - - if (waiting_.empty()) { - set_need_ship(game, false); - } - } - - fleet_->update(game); + fleet_->update(game); + return; + } + } + + // decide where the arrived ship will go next + PortDock* next_port = fleet_->find_next_dest(game, ship, this); + if (!next_port) { + ship.set_destination(next_port); + return; // no need to load anything + } + + // unload any wares/workers onboard the departing ship which are not favored by next dest + ship.unload_unfit_items(game, *this, *next_port); + + // then load the remaining capacity of the departing ship with relevant items + uint32_t rest_capacity = ship.descr().get_capacity() - ship.get_nritems(); + + // firstly load the items which go to chosen dest, while also checking for items with invalid dest + uint32_t dst = 0; + for (ShippingItem& si : waiting_) { + PortDock* itemdest = si.get_destination(game); + if (itemdest) { // valid dest + if (rest_capacity == 0) { + waiting_[dst++] = si; // keep the item here + } else { + if (itemdest == next_port) { // the item goes to chosen dest + ship.add_item(game, si); // load it + --rest_capacity; + } else { // different dest + waiting_[dst++] = si; // keep it here for now + } + } + } else { // invalid dest + // carry the item back in the warehouse + si.set_location(game, warehouse_); + si.end_shipping(game); + } + } + waiting_.resize(dst); + + if (rest_capacity > 0) { // there is still capacity + // load any items favored by the chosen dest + dst = 0; + for (ShippingItem& si : waiting_) { + if (rest_capacity == 0) { + waiting_[dst++] = si; // keep the item here + } else { + if (fleet_->is_path_favourable(*this, *next_port, *si.get_destination(game))) { + ship.add_item(game, si); + --rest_capacity; + } else { // item is not favored by the chosen dest + // spare it from getting trapped inside the wrong ship + waiting_[dst++] = si; + } + } + } + waiting_.resize(dst); + } + ship.set_destination(next_port); + + set_need_ship(game, !waiting_.empty()); } void PortDock::set_need_ship(Game& game, bool need) { - molog("set_need_ship(%s)\n", need ? "true" : "false"); - - if (need == need_ship_) - return; - - need_ship_ = need; - - if (fleet_) { - molog("... trigger fleet update\n"); + if (need && fleet_) { + molog("trigger fleet update\n"); fleet_->update(game); } } @@ -445,13 +499,13 @@ if (warehouse_) { Coords pos(warehouse_->get_position()); - molog("PortDock for warehouse %u (at %i,%i) in fleet %u, need_ship: %s, waiting: %" PRIuS + molog("PortDock for warehouse %u (at %i,%i) in fleet %u, expedition_ready: %s, waiting: %" PRIuS "\n", warehouse_->serial(), pos.x, pos.y, fleet_ ? fleet_->serial() : 0, - need_ship_ ? "true" : "false", waiting_.size()); + expedition_ready_ ? "true" : "false", waiting_.size()); } else { - molog("PortDock without a warehouse in fleet %u, need_ship: %s, waiting: %" PRIuS "\n", - fleet_ ? fleet_->serial() : 0, need_ship_ ? "true" : "false", waiting_.size()); + molog("PortDock without a warehouse in fleet %u, expedition_ready: %s, waiting: %" PRIuS "\n", + fleet_ ? fleet_->serial() : 0, expedition_ready_ ? "true" : "false", waiting_.size()); } for (ShippingItem& shipping_item : waiting_) { @@ -479,7 +533,7 @@ pd.set_position(egbase(), pd.dockpoints_[i]); } - pd.need_ship_ = fr.unsigned_8(); + pd.ships_coming_ = fr.unsigned_8(); waiting_.resize(fr.unsigned_32()); for (ShippingItem::Loader& shipping_loader : waiting_) { @@ -553,7 +607,7 @@ write_coords_32(&fw, coords); } - fw.unsigned_8(need_ship_); + fw.unsigned_8(ships_coming_); fw.unsigned_32(waiting_.size()); for (ShippingItem& shipping_item : waiting_) { === modified file 'src/economy/portdock.h' --- src/economy/portdock.h 2018-04-07 16:59:00 +0000 +++ src/economy/portdock.h 2018-08-30 06:13:48 +0000 @@ -85,9 +85,7 @@ return fleet_; } PortDock* get_dock(Flag& flag) const; - bool get_need_ship() const { - return need_ship_ || expedition_ready_; - } + uint32_t get_need_ship() const; void set_economy(Economy*) override; @@ -113,6 +111,9 @@ void add_shippingitem(Game&, Worker&); void update_shippingitem(Game&, Worker&); + void shipping_item_arrived(Game&, ShippingItem&); + void shipping_item_returned(Game&, ShippingItem&); + void ship_coming(bool affirmative); void ship_arrived(Game&, Ship&); void log_general_info(const EditorGameBase&) override; @@ -146,7 +147,7 @@ Warehouse* warehouse_; PositionList dockpoints_; std::vector<ShippingItem> waiting_; - bool need_ship_; + uint8_t ships_coming_; bool expedition_ready_; std::unique_ptr<ExpeditionBootstrap> expedition_bootstrap_; === 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-30 06:13:48 +0000 @@ -303,12 +303,22 @@ FCoords position = map.get_fcoords(get_position()); if (position.field->get_immovable() == dst) { - molog("ship_update: Arrived at dock %u\n", dst->serial()); - lastdock_ = dst; - destination_ = nullptr; - dst->ship_arrived(game, *this); - start_task_idle(game, descr().main_animation(), 250); - Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged)); + if (lastdock_ != dst) { + molog("ship_update: Arrived at dock %u\n", dst->serial()); + lastdock_ = dst; + } + if (withdraw_items(game, *dst)) { + schedule_act(game, kShipInterval); + return true; + } + + dst->ship_arrived(game, *this); // this should call set_destination + dst = get_destination(game); + if (dst) { + start_task_movetodock(game, *dst); + } else { + start_task_idle(game, descr().main_animation(), 250); + } return true; } @@ -484,7 +494,7 @@ } if (totalprob == 0) { - start_task_idle(game, descr().main_animation(), 1500); + start_task_idle(game, descr().main_animation(), kShipInterval); return; } @@ -496,13 +506,13 @@ } if (dir == 0 || dir > LAST_DIRECTION) { - start_task_idle(game, descr().main_animation(), 1500); + start_task_idle(game, descr().main_animation(), kShipInterval); return; } FCoords neighbour = map.get_neighbour(position, dir); if (!(neighbour.field->nodecaps() & MOVECAPS_SWIM)) { - start_task_idle(game, descr().main_animation(), 1500); + start_task_idle(game, descr().main_animation(), kShipInterval); return; } @@ -542,7 +552,7 @@ pgettext("ship", "Waiting"), _("Island Circumnavigated"), _("An expedition ship sailed around its island without any events."), "images/wui/ship/ship_explore_island_cw.png"); - return start_task_idle(game, descr().main_animation(), 1500); + return start_task_idle(game, descr().main_animation(), kShipInterval); } } // The ship is supposed to follow the coast as close as possible, therefore the check @@ -585,7 +595,7 @@ shipname_.c_str()); set_ship_state_and_notify( ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand); - start_task_idle(game, descr().main_animation(), 1500); + start_task_idle(game, descr().main_animation(), kShipInterval); return; } } else { // scouting towards a specific direction @@ -598,7 +608,7 @@ // coast reached set_ship_state_and_notify( ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand); - start_task_idle(game, descr().main_animation(), 1500); + start_task_idle(game, descr().main_animation(), kShipInterval); // Send a message to the player, that a new coast was reached send_message(game, /** TRANSLATORS: A ship has discovered land */ @@ -692,7 +702,7 @@ } expedition_.reset(nullptr); - return start_task_idle(game, descr().main_animation(), 1500); + return start_task_idle(game, descr().main_animation(), kShipInterval); } } FALLS_THROUGH; @@ -701,7 +711,7 @@ case ShipStates::kSinkRequest: case ShipStates::kSinkAnimation: { // wait for input - start_task_idle(game, descr().main_animation(), 1500); + start_task_idle(game, descr().main_animation(), kShipInterval); return; } } @@ -727,14 +737,17 @@ /** * Enter a new destination port for the ship. - * - * @note This is supposed to be called only from the scheduling code of @ref Fleet. + * Call this after un/loading the ship, for proper logging. */ -void Ship::set_destination(Game& game, PortDock& pd) { - molog("set_destination / sending to portdock %u (carrying %" PRIuS " items)\n", pd.serial(), - items_.size()); - destination_ = &pd; - send_signal(game, "wakeup"); +void Ship::set_destination(PortDock* pd) { + destination_ = pd; + if (pd) { + molog("set_destination / sending to portdock %u (carrying %" PRIuS " items)\n", pd->serial(), + items_.size()); + pd->ship_coming(true); + } else { + molog("set_destination / none\n"); + } Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged)); } @@ -745,14 +758,40 @@ items_.back().set_location(game, this); } -void Ship::withdraw_items(Game& game, PortDock& pd, std::vector<ShippingItem>& items) { - uint32_t dst = 0; - for (uint32_t src = 0; src < items_.size(); ++src) { - PortDock* destination = items_[src].get_destination(game); - if (!destination || destination == &pd) { - items.push_back(items_[src]); +/** + * Unload one item designated for given dock or for no dock. + * \return true if item unloaded. + */ +bool Ship::withdraw_items(Game& game, PortDock& pd) { + bool unloaded = false; + uint32_t dst = 0; + for (ShippingItem& si : items_) { + if (!unloaded) { + PortDock* itemdest = si.get_destination(game); + if (!itemdest || itemdest == &pd) { + pd.shipping_item_arrived(game, si); + unloaded = true; + continue; + } + } + + items_[dst++] = si; + } + items_.resize(dst); + return unloaded; +} + +/** + * Unload all items not favored by given next dest. + * Assert all items for current portdock have already been unloaded. + */ +void Ship::unload_unfit_items(Game& game, PortDock& here, PortDock& nextdest) { + uint32_t dst = 0; + for (ShippingItem& si : items_) { + if (fleet_->is_path_favourable(here, nextdest, *si.get_destination(game))) { + items_[dst++] = si; } else { - items_[dst++] = items_[src]; + here.shipping_item_returned(game, si); } } items_.resize(dst); @@ -811,7 +850,7 @@ // I (tiborb) failed to invoke this situation when testing so // I am not sure if following line behaves allright get_fleet()->update(game); - start_task_idle(game, descr().main_animation(), 5000); + start_task_idle(game, descr().main_animation(), kFleetInterval); } } @@ -969,8 +1008,12 @@ /// @note only called via player command void Ship::sink_ship(Game& game) { // Running colonization has the highest priority + a sink request is only valid once - if (!state_is_sinkable()) + if (!state_is_sinkable()) { return; + } + if (destination_.is_set()) { + destination_.get(game)->ship_coming(false); + } ship_state_ = ShipStates::kSinkRequest; // Make sure the ship is active and close possible open windows ship_wakeup(game); === 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-30 06:13:48 +0000 @@ -78,6 +78,8 @@ DISALLOW_COPY_AND_ASSIGN(ShipDescr); }; +constexpr int32_t kShipInterval = 1500; + /** * Ships belong to a player and to an economy. The usually are in a (unique) * fleet for a player, but only if they are on standard duty. Exploration ships @@ -104,7 +106,7 @@ return economy_; } void set_economy(Game&, Economy* e); - void set_destination(Game&, PortDock&); + void set_destination(PortDock*); void init_auto_task(Game&) override; @@ -126,8 +128,9 @@ return items_[idx]; } - void withdraw_items(Game& game, PortDock& pd, std::vector<ShippingItem>& items); - void add_item(Game&, const ShippingItem& item); + void add_item(Game&, const ShippingItem&); + bool withdraw_items(Game&, PortDock&); + void unload_unfit_items(Game&, PortDock& here, PortDock& nextdest); // A ship with task expedition can be in four states: kExpeditionWaiting, kExpeditionScouting, // kExpeditionPortspaceFound or kExpeditionColonizing in the first states, the owning player of
_______________________________________________ Mailing list: https://launchpad.net/~widelands-dev Post to : widelands-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~widelands-dev More help : https://help.launchpad.net/ListHelp