Merge lp:~widelands-dev/widelands/ship_scheduling_2 into lp:widelands
- ship_scheduling_2
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~widelands-dev/widelands/ship_scheduling_2 |
Merge into: | lp:widelands |
Diff against target: |
1132 lines (+417/-335) 8 files modified
src/economy/fleet.cc (+195/-219) src/economy/fleet.h (+6/-1) src/economy/portdock.cc (+125/-71) src/economy/portdock.h (+9/-7) src/economy/shippingitem.cc (+1/-1) src/economy/shippingitem.h (+1/-1) src/logic/map_objects/tribes/ship.cc (+74/-32) src/logic/map_objects/tribes/ship.h (+6/-3) |
To merge this branch: | bzr merge lp:~widelands-dev/widelands/ship_scheduling_2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
GunChleoc | Needs Fixing | ||
Review via email: mp+354019@code.launchpad.net |
This proposal supersedes a proposal from 2018-08-04.
This proposal has been superseded by a proposal from 2018-09-21.
Commit message
Improved ware/worker routing algorithm for ships and portdocks.
Description of the change
See description of the branch:
https:/
bunnybot (widelandsofficial) wrote : Posted in a previous version of this proposal | # |
bunnybot (widelandsofficial) wrote : Posted in a previous version of this proposal | # |
Continuous integration builds have changed state:
Travis build 3758. State: errored. Details: https:/
Appveyor build 3558. State: success. Details: https:/
bunnybot (widelandsofficial) wrote : Posted in a previous version of this proposal | # |
Continuous integration builds have changed state:
Travis build 3855. State: errored. Details: https:/
Appveyor build 3653. State: failed. Details: https:/
ypopezios (ypopezios) wrote : | # |
AppVeyor seems broken. Otherwise, this branch is now mature and big enough. Not going to add anything more into it, so as to keep it easier for reviewers. If people test it, it can make it into build 20.
hessenfarmer (stephan-lutz) wrote : | # |
Reason for appveyor not working anymore currently is that the glbinding package has been upgraded in the repo of MSYS from 2.1.4 to 3.0.2.1 which seems to have issues with our code now.
GunChleoc (gunchleoc) wrote : | # |
I have pushed a commit with a code review and some small tweaks. Please have a look at the NOCOM comments.
Not tested yet.
ypopezios (ypopezios) wrote : | # |
Added inline comments concerning the NOCOM comments.
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 3961. State: errored. Details: https:/
Appveyor build 3759. State: success. Details: https:/
GunChleoc (gunchleoc) wrote : | # |
I had actually programmed a really nice endless loop with those iterators... all fixed now. I have also improved the savegame compatibility, so it's only calculated once during game loading.
Thanks for this great feature! :)
@bunnybot merge
GunChleoc (gunchleoc) wrote : | # |
One of the tests in our test suite has an endless loop in it:
test/maps/
This already happened with older versions of this branch, so I don't think that it's anything I did.
bunnybot (widelandsofficial) wrote : | # |
Refusing to merge, since Travis is not green. Use @bunnybot merge force for merging anyways.
Travis build 3977. State: errored. Details: https:/
Preview Diff
1 | === modified file 'src/economy/fleet.cc' | |||
2 | --- src/economy/fleet.cc 2018-09-05 06:42:21 +0000 | |||
3 | +++ src/economy/fleet.cc 2018-09-21 19:37:16 +0000 | |||
4 | @@ -300,7 +300,7 @@ | |||
5 | 300 | * | 300 | * |
6 | 301 | * @return true if successful, or false if the docks are not actually part of the fleet. | 301 | * @return true if successful, or false if the docks are not actually part of the fleet. |
7 | 302 | */ | 302 | */ |
9 | 303 | bool Fleet::get_path(PortDock& start, PortDock& end, Path& path) { | 303 | bool Fleet::get_path(const PortDock& start, const PortDock& end, Path& path) { |
10 | 304 | uint32_t startidx = std::find(ports_.begin(), ports_.end(), &start) - ports_.begin(); | 304 | uint32_t startidx = std::find(ports_.begin(), ports_.end(), &start) - ports_.begin(); |
11 | 305 | uint32_t endidx = std::find(ports_.begin(), ports_.end(), &end) - ports_.begin(); | 305 | uint32_t endidx = std::find(ports_.begin(), ports_.end(), &end) - ports_.begin(); |
12 | 306 | 306 | ||
13 | @@ -628,8 +628,7 @@ | |||
14 | 628 | } | 628 | } |
15 | 629 | 629 | ||
16 | 630 | /** | 630 | /** |
19 | 631 | * Act callback updates ship scheduling. All decisions about where transport ships | 631 | * Act callback updates ship scheduling of idle ships. |
18 | 632 | * are supposed to go are made via this function. | ||
20 | 633 | * | 632 | * |
21 | 634 | * @note Do not call this directly; instead, trigger it via @ref update | 633 | * @note Do not call this directly; instead, trigger it via @ref update |
22 | 635 | */ | 634 | */ |
23 | @@ -637,230 +636,207 @@ | |||
24 | 637 | act_pending_ = false; | 636 | act_pending_ = false; |
25 | 638 | 637 | ||
26 | 639 | if (!active()) { | 638 | if (!active()) { |
31 | 640 | // If we are here, most likely act() was called by a port with waiting wares or an expedition | 639 | // If we are here, most likely act() was called by a port with waiting wares or |
32 | 641 | // ready | 640 | // with an expedition ready, although there are still no ships. |
33 | 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() |
34 | 643 | schedule_act(game, 5000); // retry in the next time | 642 | schedule_act(game, kFleetInterval); // retry in the next time |
35 | 644 | act_pending_ = true; | 643 | act_pending_ = true; |
36 | 645 | return; | 644 | return; |
37 | 646 | } | 645 | } |
38 | 647 | 646 | ||
39 | 648 | molog("Fleet::act\n"); | 647 | molog("Fleet::act\n"); |
40 | 649 | 648 | ||
253 | 650 | // we need to calculate what ship is to be send to which port | 649 | // For each waiting port, try to find idle ships and send to it the closest one. |
254 | 651 | // for this we will have temporary data structure with format | 650 | uint16_t waiting_ports = ports_.size(); |
255 | 652 | // <<ship,port>,score> | 651 | for (PortDock* p : ports_) { |
256 | 653 | // where ship and port are not objects but positions in ports_ and ships_ | 652 | if (p->get_need_ship() == 0) { |
257 | 654 | // this is to allow native hashing | 653 | --waiting_ports; |
258 | 655 | std::map<std::pair<uint16_t, uint16_t>, uint16_t> scores; | 654 | continue; |
259 | 656 | 655 | } | |
260 | 657 | // so we will identify all pairs: idle ship : ports, and score all such | 656 | |
261 | 658 | // pairs. We consider | 657 | Ship* closest_ship = nullptr; |
262 | 659 | // - count of wares onboard, first ware (oldest) is counted as 8 (prioritization) | 658 | uint32_t shortest_dist = kRouteNotCalculated; |
263 | 660 | // (counting wares for particular port only) | 659 | bool waiting = true; |
264 | 661 | // - count wares waiting at the port/3 | 660 | |
265 | 662 | // - distance between ship and a port (0-10 points, the closer the more points) | 661 | for (Ship* s : ships_) { |
266 | 663 | // - is another ship heading there right now? | 662 | if (s->get_destination(game)) { |
267 | 664 | 663 | if (s->get_destination(game) == p) { | |
268 | 665 | // at the end we must know if requrests of all ports asking for ship were addressed | 664 | waiting = false; |
269 | 666 | // if any unsatisfied, we must schedule new run of this function | 665 | --waiting_ports; |
270 | 667 | // when we send a ship there, the port is removed from list | 666 | break; |
271 | 668 | std::list<uint16_t> waiting_ports; | 667 | } |
272 | 669 | 668 | continue; // The ship already has a destination | |
273 | 670 | // this is just helper - first member of scores map | 669 | } |
274 | 671 | std::pair<uint16_t, uint16_t> mapping; // ship number, port number | 670 | if (s->get_ship_state() != Ship::ShipStates::kTransport) { |
275 | 672 | 671 | continue; // Ship is not available, e.g. in expedition | |
276 | 673 | // first we go over ships - idle ones (=without destination) | 672 | } |
277 | 674 | // then over wares on these ships and create first ship-port | 673 | |
278 | 675 | // pairs with score | 674 | // Here we get distance ship->port |
279 | 676 | for (uint16_t s = 0; s < ships_.size(); s += 1) { | 675 | uint32_t route_length = kRouteNotCalculated; |
280 | 677 | if (ships_[s]->get_destination(game)) { | 676 | |
281 | 678 | continue; | 677 | // Get precalculated distance for ships available at ports |
282 | 679 | } | 678 | { |
283 | 680 | if (ships_[s]->get_ship_state() != Ship::ShipStates::kTransport) { | 679 | PortDock* cur_port = get_dock(game, s->get_position()); |
284 | 681 | continue; // in expedition obviously | 680 | if (cur_port) { // Ship is at a port |
285 | 682 | } | 681 | if (cur_port == p) { // Same port |
286 | 683 | 682 | route_length = 0; | |
287 | 684 | for (uint16_t i = 0; i < ships_[s]->get_nritems(); i += 1) { | 683 | } else { // Different port |
288 | 685 | PortDock* dst = ships_[s]->items_[i].get_destination(game); | 684 | Path precalculated_path; |
289 | 686 | if (!dst) { | 685 | if (get_path(*cur_port, *p, precalculated_path)) { |
290 | 687 | // if wares without destination on ship without destination | 686 | route_length = precalculated_path.get_nsteps(); |
291 | 688 | // such ship can be send to any port, and should be sent | 687 | } |
292 | 689 | // to some port, so we add 1 point to score for each port | 688 | } |
293 | 690 | for (uint16_t p = 0; p < ports_.size(); p += 1) { | 689 | } |
294 | 691 | mapping.first = s; | 690 | } |
295 | 692 | mapping.second = p; | 691 | |
296 | 693 | scores[mapping] += 1; | 692 | // Get distance for ships available but not at a port (should not happen frequently) |
297 | 694 | } | 693 | if (route_length == kRouteNotCalculated) { |
298 | 695 | continue; | 694 | route_length = s->calculate_sea_route(game, *p); |
299 | 696 | } | 695 | } |
300 | 697 | 696 | ||
301 | 698 | bool destination_found = false; // Just a functional check | 697 | if (route_length < shortest_dist) { |
302 | 699 | for (uint16_t p = 0; p < ports_.size(); p += 1) { | 698 | shortest_dist = route_length; |
303 | 700 | if (ports_[p] == ships_[s]->items_[i].get_destination(game)) { | 699 | closest_ship = s; |
304 | 701 | mapping.first = s; | 700 | } |
305 | 702 | mapping.second = p; | 701 | } |
306 | 703 | scores[mapping] += (i == 0) ? 8 : 1; | 702 | |
307 | 704 | destination_found = true; | 703 | if (waiting && closest_ship) { |
308 | 705 | } | 704 | --waiting_ports; |
309 | 706 | } | 705 | closest_ship->set_destination(p); |
310 | 707 | if (!destination_found) { | 706 | closest_ship->send_signal(game, "wakeup"); |
311 | 708 | // Perhaps the throw here is too strong | 707 | } |
312 | 709 | // we can still remove it before stable release if it proves too much | 708 | } |
313 | 710 | // during my testing this situation never happened | 709 | |
314 | 711 | throw wexception("A ware with destination that does not match any of player's" | 710 | if (waiting_ports > 0) { |
315 | 712 | " ports, ship %u, ware's destination: %u", | 711 | molog("... there are %u ports requesting ship(s) we cannot satisfy yet\n", waiting_ports); |
316 | 713 | ships_[s]->serial(), | 712 | schedule_act(game, kFleetInterval); // retry next time |
105 | 714 | ships_[s]->items_[i].get_destination(game)->serial()); | ||
106 | 715 | } | ||
107 | 716 | } | ||
108 | 717 | } | ||
109 | 718 | |||
110 | 719 | // now opposite aproach - we go over ports to find out those that have wares | ||
111 | 720 | // waiting for ship then find candidate ships to satisfy the requests | ||
112 | 721 | for (uint16_t p = 0; p < ports_.size(); p += 1) { | ||
113 | 722 | PortDock& pd = *ports_[p]; | ||
114 | 723 | if (!pd.get_need_ship()) { | ||
115 | 724 | continue; | ||
116 | 725 | } | ||
117 | 726 | |||
118 | 727 | // general stategy is "one ship for port is enough", but sometimes | ||
119 | 728 | // amount of ware waiting for ship is too high | ||
120 | 729 | if (count_ships_heading_here(game, &pd) * 25 > pd.count_waiting()) { | ||
121 | 730 | continue; | ||
122 | 731 | } | ||
123 | 732 | |||
124 | 733 | waiting_ports.push_back(p); | ||
125 | 734 | |||
126 | 735 | // scoring and entering the pair into scores (or increasing existing | ||
127 | 736 | // score if the pair is already there) | ||
128 | 737 | for (uint16_t s = 0; s < ships_.size(); s += 1) { | ||
129 | 738 | |||
130 | 739 | if (ships_[s]->get_destination(game)) { | ||
131 | 740 | continue; // already has destination | ||
132 | 741 | } | ||
133 | 742 | |||
134 | 743 | if (ships_[s]->get_ship_state() != Ship::ShipStates::kTransport) { | ||
135 | 744 | continue; // in expedition obviously | ||
136 | 745 | } | ||
137 | 746 | |||
138 | 747 | mapping.first = s; | ||
139 | 748 | mapping.second = p; | ||
140 | 749 | // following aproximately considers free capacity of a ship | ||
141 | 750 | scores[mapping] += ((ships_[s]->get_nritems() > 15) ? 1 : 3) + | ||
142 | 751 | std::min(ships_[s]->descr().get_capacity() - ships_[s]->get_nritems(), | ||
143 | 752 | ports_[p]->count_waiting()) / | ||
144 | 753 | 3; | ||
145 | 754 | } | ||
146 | 755 | } | ||
147 | 756 | |||
148 | 757 | // Now adding score for distance | ||
149 | 758 | for (auto ship_port_relation : scores) { | ||
150 | 759 | |||
151 | 760 | // here we get distance ship->port | ||
152 | 761 | // possibilities are: | ||
153 | 762 | // - we are in port and it is the same as target port | ||
154 | 763 | // - we are in other port, then we use get_dock() function to fetch precalculated path | ||
155 | 764 | // - if above fails, we calculate path "manually" | ||
156 | 765 | int16_t route_length = -1; | ||
157 | 766 | |||
158 | 767 | PortDock* current_portdock = | ||
159 | 768 | get_dock(game, ships_[ship_port_relation.first.first]->get_position()); | ||
160 | 769 | |||
161 | 770 | if (current_portdock) { // we try to use precalculated paths of game | ||
162 | 771 | |||
163 | 772 | // we are in the same portdock | ||
164 | 773 | if (current_portdock == ports_[ship_port_relation.first.second]) { | ||
165 | 774 | route_length = 0; | ||
166 | 775 | } else { // it is different portdock then | ||
167 | 776 | Path tmp_path; | ||
168 | 777 | if (get_path(*current_portdock, *ports_[ship_port_relation.first.second], tmp_path)) { | ||
169 | 778 | route_length = tmp_path.get_nsteps(); | ||
170 | 779 | } | ||
171 | 780 | } | ||
172 | 781 | } | ||
173 | 782 | |||
174 | 783 | // most probably the ship is not in a portdock (should not happen frequently) | ||
175 | 784 | if (route_length == -1) { | ||
176 | 785 | route_length = ships_[ship_port_relation.first.first]->calculate_sea_route( | ||
177 | 786 | game, *ports_[ship_port_relation.first.second]); | ||
178 | 787 | } | ||
179 | 788 | |||
180 | 789 | // now we have length of route, so we need to calculate score | ||
181 | 790 | int16_t score_for_distance = 0; | ||
182 | 791 | if (route_length < 3) { | ||
183 | 792 | score_for_distance = 10; | ||
184 | 793 | } else { | ||
185 | 794 | score_for_distance = 8 - route_length / 50; | ||
186 | 795 | } | ||
187 | 796 | // must not be negative | ||
188 | 797 | score_for_distance = (score_for_distance < 0) ? 0 : score_for_distance; | ||
189 | 798 | |||
190 | 799 | scores[ship_port_relation.first] += score_for_distance; | ||
191 | 800 | } | ||
192 | 801 | |||
193 | 802 | // looking for best scores and sending ships accordingly | ||
194 | 803 | uint16_t best_ship = 0; | ||
195 | 804 | uint16_t best_port = 0; | ||
196 | 805 | |||
197 | 806 | // after sending a ship we will remove one or more items from scores | ||
198 | 807 | while (!scores.empty()) { | ||
199 | 808 | uint16_t best_score = 0; | ||
200 | 809 | |||
201 | 810 | // searching for combination with highest score | ||
202 | 811 | for (const auto& combination : scores) { | ||
203 | 812 | if (combination.second > best_score) { | ||
204 | 813 | best_score = combination.second; | ||
205 | 814 | best_ship = combination.first.first; | ||
206 | 815 | best_port = combination.first.second; | ||
207 | 816 | } | ||
208 | 817 | } | ||
209 | 818 | if (best_score == 0) { | ||
210 | 819 | // this is check of correctnes of this algorithm, this should not happen | ||
211 | 820 | throw wexception("Fleet::act(): No port-destination pair selected or its score is zero"); | ||
212 | 821 | } | ||
213 | 822 | |||
214 | 823 | // making sure the winner has no destination set | ||
215 | 824 | assert(!ships_[best_ship]->get_destination(game)); | ||
216 | 825 | |||
217 | 826 | // now actual setting destination for "best ship" | ||
218 | 827 | ships_[best_ship]->set_destination(game, *ports_[best_port]); | ||
219 | 828 | molog("... ship %u sent to port %u, wares onboard: %2d, the port is asking for a ship: %s\n", | ||
220 | 829 | ships_[best_ship]->serial(), ports_[best_port]->serial(), | ||
221 | 830 | ships_[best_ship]->get_nritems(), (ports_[best_port]->get_need_ship()) ? "yes" : "no"); | ||
222 | 831 | |||
223 | 832 | // pruning the scores table | ||
224 | 833 | // the ship that was just sent somewhere cannot be send elsewhere :) | ||
225 | 834 | for (auto it = scores.cbegin(); it != scores.cend();) { | ||
226 | 835 | |||
227 | 836 | // decreasing score for target port as there was a ship just sent there | ||
228 | 837 | if (it->first.second == best_port) { | ||
229 | 838 | mapping.first = it->first.first; | ||
230 | 839 | mapping.second = it->first.second; | ||
231 | 840 | scores[mapping] /= 2; | ||
232 | 841 | // just make sure it is nonzero | ||
233 | 842 | scores[mapping] = (scores[mapping] == 0) ? 1 : scores[mapping]; | ||
234 | 843 | } | ||
235 | 844 | |||
236 | 845 | // but removing all pairs where best ship is participating as it is not available anymore | ||
237 | 846 | // (because it was sent to "best port") | ||
238 | 847 | if (it->first.first == best_ship) { | ||
239 | 848 | scores.erase(it++); | ||
240 | 849 | } else { | ||
241 | 850 | ++it; | ||
242 | 851 | } | ||
243 | 852 | } | ||
244 | 853 | |||
245 | 854 | // also removing the port from waiting_ports | ||
246 | 855 | waiting_ports.remove(best_port); | ||
247 | 856 | } | ||
248 | 857 | |||
249 | 858 | if (!waiting_ports.empty()) { | ||
250 | 859 | molog("... there are %" PRIuS " ports requesting ship(s) we cannot satisfy yet\n", | ||
251 | 860 | waiting_ports.size()); | ||
252 | 861 | schedule_act(game, 5000); // retry next time | ||
317 | 862 | act_pending_ = true; | 713 | act_pending_ = true; |
318 | 863 | } | 714 | } |
319 | 715 | |||
320 | 716 | // Deal with edge-case of losing destination before reaching it | ||
321 | 717 | for (Ship* s : ships_) { | ||
322 | 718 | if (s->get_destination(game)) { | ||
323 | 719 | continue; // The ship has a destination | ||
324 | 720 | } | ||
325 | 721 | if (s->get_ship_state() != Ship::ShipStates::kTransport) { | ||
326 | 722 | continue; // Ship is not available, e.g. in expedition | ||
327 | 723 | } | ||
328 | 724 | if (s->items_.empty()) { | ||
329 | 725 | continue; // No pending wares/workers | ||
330 | 726 | } | ||
331 | 727 | |||
332 | 728 | // Send ship to the closest port | ||
333 | 729 | PortDock* closest_port = nullptr; | ||
334 | 730 | uint32_t shortest_dist = kRouteNotCalculated; | ||
335 | 731 | |||
336 | 732 | for (PortDock* p : ports_) { | ||
337 | 733 | uint32_t route_length = s->calculate_sea_route(game, *p); | ||
338 | 734 | if (route_length < shortest_dist) { | ||
339 | 735 | shortest_dist = route_length; | ||
340 | 736 | closest_port = p; | ||
341 | 737 | } | ||
342 | 738 | } | ||
343 | 739 | |||
344 | 740 | if (closest_port) { | ||
345 | 741 | s->set_destination(closest_port); | ||
346 | 742 | s->send_signal(game, "wakeup"); | ||
347 | 743 | } | ||
348 | 744 | } | ||
349 | 745 | } | ||
350 | 746 | |||
351 | 747 | /** | ||
352 | 748 | * For the given three consecutive ports, decide if their path is favourable or not. | ||
353 | 749 | * \return true if the path from start to finish >= the path from middle to finish | ||
354 | 750 | */ | ||
355 | 751 | bool Fleet::is_path_favourable(const PortDock& start, const PortDock& middle, const PortDock& finish) { | ||
356 | 752 | if (&middle != &finish) { | ||
357 | 753 | Path path_start_to_finish; | ||
358 | 754 | Path path_middle_to_finish; | ||
359 | 755 | #ifndef NDEBUG | ||
360 | 756 | assert(get_path(start, finish, path_start_to_finish)); | ||
361 | 757 | #else | ||
362 | 758 | get_path(start, finish, path_start_to_finish); | ||
363 | 759 | #endif | ||
364 | 760 | if (get_path(middle, finish, path_middle_to_finish)) { | ||
365 | 761 | if (path_middle_to_finish.get_nsteps() > path_start_to_finish.get_nsteps()) { | ||
366 | 762 | return false; | ||
367 | 763 | } | ||
368 | 764 | } | ||
369 | 765 | } | ||
370 | 766 | return true; // default | ||
371 | 767 | } | ||
372 | 768 | |||
373 | 769 | /** | ||
374 | 770 | * For the given ship, go through all ports of this fleet | ||
375 | 771 | * and find the one with the best score. | ||
376 | 772 | * \return that port | ||
377 | 773 | */ | ||
378 | 774 | PortDock* Fleet::find_next_dest(Game& game, const Ship& ship, const PortDock& from_port) { | ||
379 | 775 | PortDock* best_port = nullptr; | ||
380 | 776 | float best_score = 0.0f; | ||
381 | 777 | |||
382 | 778 | for (PortDock* p : ports_) { | ||
383 | 779 | if (p == &from_port) { | ||
384 | 780 | continue; // same port | ||
385 | 781 | } | ||
386 | 782 | |||
387 | 783 | float score = 0.0f; | ||
388 | 784 | WareInstance* ware; | ||
389 | 785 | Worker* worker; | ||
390 | 786 | |||
391 | 787 | // Score for wares/workers onboard that ship for that port | ||
392 | 788 | for (const ShippingItem& si : ship.items_) { | ||
393 | 789 | if (si.get_destination(game) == p) { | ||
394 | 790 | si.get(game, &ware, &worker); | ||
395 | 791 | if (ware) { | ||
396 | 792 | score += 1; // TODO(ypopezios): increase by ware's importance | ||
397 | 793 | } else { // worker | ||
398 | 794 | score += 4; | ||
399 | 795 | } | ||
400 | 796 | } | ||
401 | 797 | } | ||
402 | 798 | |||
403 | 799 | // Score for wares/workers waiting at that port | ||
404 | 800 | for (const ShippingItem& si : from_port.waiting_) { | ||
405 | 801 | if (si.get_destination(game) == p) { | ||
406 | 802 | si.get(game, &ware, &worker); | ||
407 | 803 | if (ware) { | ||
408 | 804 | score += 1; // TODO(ypopezios): increase by ware's importance | ||
409 | 805 | } else { // worker | ||
410 | 806 | score += 4; | ||
411 | 807 | } | ||
412 | 808 | } | ||
413 | 809 | } | ||
414 | 810 | |||
415 | 811 | if (score == 0.0f && p->get_need_ship() == 0) { | ||
416 | 812 | continue; // empty ship to empty port | ||
417 | 813 | } | ||
418 | 814 | |||
419 | 815 | // Here we get distance ship->port | ||
420 | 816 | uint32_t route_length = kRouteNotCalculated; | ||
421 | 817 | |||
422 | 818 | // Get precalculated distance if the ship is at a port | ||
423 | 819 | { | ||
424 | 820 | Path precalculated_path; | ||
425 | 821 | if (get_path(from_port, *p, precalculated_path)) { // try to use precalculated path | ||
426 | 822 | route_length = precalculated_path.get_nsteps(); | ||
427 | 823 | } | ||
428 | 824 | } | ||
429 | 825 | |||
430 | 826 | // Get distance for when the ship is not at a port (should not happen frequently) | ||
431 | 827 | if (route_length == kRouteNotCalculated) { | ||
432 | 828 | route_length = ship.calculate_sea_route(game, *p); | ||
433 | 829 | } | ||
434 | 830 | |||
435 | 831 | score = (score + 1.0f) * (score + p->get_need_ship()); | ||
436 | 832 | score = score * (1.0f - route_length / (score + route_length)); | ||
437 | 833 | if (score > best_score) { | ||
438 | 834 | best_score = score; | ||
439 | 835 | best_port = p; | ||
440 | 836 | } | ||
441 | 837 | } | ||
442 | 838 | |||
443 | 839 | return best_port; | ||
444 | 864 | } | 840 | } |
445 | 865 | 841 | ||
446 | 866 | void Fleet::log_general_info(const EditorGameBase& egbase) const { | 842 | void Fleet::log_general_info(const EditorGameBase& egbase) const { |
447 | 867 | 843 | ||
448 | === modified file 'src/economy/fleet.h' | |||
449 | --- src/economy/fleet.h 2018-09-04 15:48:47 +0000 | |||
450 | +++ src/economy/fleet.h 2018-09-21 19:37:16 +0000 | |||
451 | @@ -46,6 +46,9 @@ | |||
452 | 46 | DISALLOW_COPY_AND_ASSIGN(FleetDescr); | 46 | DISALLOW_COPY_AND_ASSIGN(FleetDescr); |
453 | 47 | }; | 47 | }; |
454 | 48 | 48 | ||
455 | 49 | constexpr int32_t kFleetInterval = 5000; | ||
456 | 50 | constexpr uint32_t kRouteNotCalculated = std::numeric_limits<uint32_t>::max(); | ||
457 | 51 | |||
458 | 49 | /** | 52 | /** |
459 | 50 | * Manage all ships and ports of a player that are connected | 53 | * Manage all ships and ports of a player that are connected |
460 | 51 | * by ocean. | 54 | * by ocean. |
461 | @@ -96,7 +99,7 @@ | |||
462 | 96 | 99 | ||
463 | 97 | void log_general_info(const EditorGameBase&) const override; | 100 | void log_general_info(const EditorGameBase&) const override; |
464 | 98 | 101 | ||
466 | 99 | bool get_path(PortDock& start, PortDock& end, Path& path); | 102 | bool get_path(const PortDock& start, const PortDock& end, Path& path); |
467 | 100 | void add_neighbours(PortDock& pd, std::vector<RoutingNodeNeighbour>& neighbours); | 103 | void add_neighbours(PortDock& pd, std::vector<RoutingNodeNeighbour>& neighbours); |
468 | 101 | 104 | ||
469 | 102 | uint32_t count_ships() const; | 105 | uint32_t count_ships() const; |
470 | @@ -152,6 +155,8 @@ | |||
471 | 152 | void save(EditorGameBase&, MapObjectSaver&, FileWrite&) override; | 155 | void save(EditorGameBase&, MapObjectSaver&, FileWrite&) override; |
472 | 153 | 156 | ||
473 | 154 | static MapObject::Loader* load(EditorGameBase&, MapObjectLoader&, FileRead&); | 157 | static MapObject::Loader* load(EditorGameBase&, MapObjectLoader&, FileRead&); |
474 | 158 | bool is_path_favourable(const PortDock& start, const PortDock& middle, const PortDock& finish); | ||
475 | 159 | PortDock* find_next_dest(Game&, const Ship&, const PortDock& from_port); | ||
476 | 155 | }; | 160 | }; |
477 | 156 | 161 | ||
478 | 157 | } // namespace Widelands | 162 | } // namespace Widelands |
479 | 158 | 163 | ||
480 | === modified file 'src/economy/portdock.cc' | |||
481 | --- src/economy/portdock.cc 2018-09-04 15:48:47 +0000 | |||
482 | +++ src/economy/portdock.cc 2018-09-21 19:37:16 +0000 | |||
483 | @@ -34,6 +34,7 @@ | |||
484 | 34 | #include "logic/game_data_error.h" | 34 | #include "logic/game_data_error.h" |
485 | 35 | #include "logic/map_objects/tribes/ship.h" | 35 | #include "logic/map_objects/tribes/ship.h" |
486 | 36 | #include "logic/map_objects/tribes/warehouse.h" | 36 | #include "logic/map_objects/tribes/warehouse.h" |
487 | 37 | #include "logic/path.h" | ||
488 | 37 | #include "logic/player.h" | 38 | #include "logic/player.h" |
489 | 38 | #include "logic/widelands_geometry_io.h" | 39 | #include "logic/widelands_geometry_io.h" |
490 | 39 | #include "map_io/map_object_loader.h" | 40 | #include "map_io/map_object_loader.h" |
491 | @@ -55,7 +56,7 @@ | |||
492 | 55 | : PlayerImmovable(g_portdock_descr), | 56 | : PlayerImmovable(g_portdock_descr), |
493 | 56 | fleet_(nullptr), | 57 | fleet_(nullptr), |
494 | 57 | warehouse_(wh), | 58 | warehouse_(wh), |
496 | 58 | need_ship_(false), | 59 | ships_coming_(0), |
497 | 59 | expedition_ready_(false) { | 60 | expedition_ready_(false) { |
498 | 60 | } | 61 | } |
499 | 61 | 62 | ||
500 | @@ -116,6 +117,10 @@ | |||
501 | 116 | return nullptr; | 117 | return nullptr; |
502 | 117 | } | 118 | } |
503 | 118 | 119 | ||
504 | 120 | uint32_t PortDock::get_need_ship() const { | ||
505 | 121 | return (waiting_.size() + (expedition_ready_ ? 20 : 0)) / (ships_coming_ + 1); | ||
506 | 122 | } | ||
507 | 123 | |||
508 | 119 | /** | 124 | /** |
509 | 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. |
510 | 121 | * | 126 | * |
511 | @@ -247,7 +252,7 @@ | |||
512 | 247 | * its route. | 252 | * its route. |
513 | 248 | */ | 253 | */ |
514 | 249 | void PortDock::update_shippingitem(Game& game, WareInstance& ware) { | 254 | void PortDock::update_shippingitem(Game& game, WareInstance& ware) { |
516 | 250 | for (std::vector<ShippingItem>::iterator item_iter = waiting_.begin(); | 255 | for (auto item_iter = waiting_.begin(); |
517 | 251 | item_iter != waiting_.end(); ++item_iter) { | 256 | item_iter != waiting_.end(); ++item_iter) { |
518 | 252 | 257 | ||
519 | 253 | if (item_iter->object_.serial() == ware.serial()) { | 258 | if (item_iter->object_.serial() == ware.serial()) { |
520 | @@ -271,7 +276,7 @@ | |||
521 | 271 | * updated its route. | 276 | * updated its route. |
522 | 272 | */ | 277 | */ |
523 | 273 | void PortDock::update_shippingitem(Game& game, Worker& worker) { | 278 | void PortDock::update_shippingitem(Game& game, Worker& worker) { |
525 | 274 | for (std::vector<ShippingItem>::iterator item_iter = waiting_.begin(); | 279 | for (auto item_iter = waiting_.begin(); |
526 | 275 | item_iter != waiting_.end(); ++item_iter) { | 280 | item_iter != waiting_.end(); ++item_iter) { |
527 | 276 | 281 | ||
528 | 277 | if (item_iter->object_.serial() == worker.serial()) { | 282 | if (item_iter->object_.serial() == worker.serial()) { |
529 | @@ -281,44 +286,66 @@ | |||
530 | 281 | } | 286 | } |
531 | 282 | } | 287 | } |
532 | 283 | 288 | ||
534 | 284 | void PortDock::update_shippingitem(Game& game, std::vector<ShippingItem>::iterator it) { | 289 | void PortDock::update_shippingitem(Game& game, std::list<ShippingItem>::iterator it) { |
535 | 285 | it->update_destination(game, *this); | 290 | it->update_destination(game, *this); |
536 | 286 | 291 | ||
538 | 287 | PortDock* dst = it->get_destination(game); | 292 | const PortDock* dst = it->get_destination(game); |
539 | 288 | assert(dst != this); | 293 | assert(dst != this); |
540 | 289 | 294 | ||
541 | 290 | // Destination might have vanished or be in another economy altogether. | 295 | // Destination might have vanished or be in another economy altogether. |
542 | 291 | if (dst && dst->get_economy() == get_economy()) { | 296 | if (dst && dst->get_economy() == get_economy()) { |
544 | 292 | set_need_ship(game, true); | 297 | if (ships_coming_ <= 0) { |
545 | 298 | set_need_ship(game, true); | ||
546 | 299 | } | ||
547 | 293 | } else { | 300 | } else { |
548 | 294 | it->set_location(game, warehouse_); | 301 | it->set_location(game, warehouse_); |
549 | 295 | it->end_shipping(game); | 302 | it->end_shipping(game); |
550 | 296 | *it = waiting_.back(); | 303 | *it = waiting_.back(); |
551 | 297 | waiting_.pop_back(); | 304 | waiting_.pop_back(); |
561 | 298 | 305 | } | |
562 | 299 | if (waiting_.empty()) | 306 | } |
563 | 300 | set_need_ship(game, false); | 307 | |
564 | 301 | } | 308 | /** |
565 | 302 | } | 309 | * Receive shipping item from unloading ship. |
566 | 303 | 310 | * Called by ship code. | |
567 | 304 | /** | 311 | */ |
568 | 305 | * A ship has arrived at the dock. Clear all items designated for this dock, | 312 | void PortDock::shipping_item_arrived(Game& game, ShippingItem& si) { |
569 | 306 | * and load the ship. | 313 | si.set_location(game, warehouse_); |
570 | 314 | si.end_shipping(game); | ||
571 | 315 | } | ||
572 | 316 | |||
573 | 317 | /** | ||
574 | 318 | * Receive shipping item from departing ship. | ||
575 | 319 | * Called by ship code. | ||
576 | 320 | */ | ||
577 | 321 | void PortDock::shipping_item_returned(Game& game, ShippingItem& si) { | ||
578 | 322 | si.set_location(game, this); | ||
579 | 323 | waiting_.push_back(si); | ||
580 | 324 | } | ||
581 | 325 | |||
582 | 326 | /** | ||
583 | 327 | * A ship changed destination and is now coming to the dock. Increase counter for need_ship. | ||
584 | 328 | */ | ||
585 | 329 | void PortDock::ship_coming(bool affirmative) { | ||
586 | 330 | if (affirmative) { | ||
587 | 331 | ++ships_coming_; | ||
588 | 332 | } else { | ||
589 | 333 | --ships_coming_; | ||
590 | 334 | } | ||
591 | 335 | } | ||
592 | 336 | |||
593 | 337 | /** | ||
594 | 338 | * A ship has arrived at the dock. Set its next destination and load it accordingly. | ||
595 | 307 | */ | 339 | */ |
596 | 308 | void PortDock::ship_arrived(Game& game, Ship& ship) { | 340 | void PortDock::ship_arrived(Game& game, Ship& ship) { |
604 | 309 | std::vector<ShippingItem> items_brought_by_ship; | 341 | ship_coming(false); |
598 | 310 | ship.withdraw_items(game, *this, items_brought_by_ship); | ||
599 | 311 | |||
600 | 312 | for (ShippingItem& shipping_item : items_brought_by_ship) { | ||
601 | 313 | shipping_item.set_location(game, warehouse_); | ||
602 | 314 | shipping_item.end_shipping(game); | ||
603 | 315 | } | ||
605 | 316 | 342 | ||
606 | 317 | if (expedition_ready_) { | 343 | if (expedition_ready_) { |
607 | 318 | assert(expedition_bootstrap_ != nullptr); | 344 | assert(expedition_bootstrap_ != nullptr); |
608 | 319 | 345 | ||
609 | 320 | // Only use an empty ship. | 346 | // Only use an empty ship. |
610 | 321 | if (ship.get_nritems() < 1) { | 347 | if (ship.get_nritems() < 1) { |
611 | 348 | ship.set_destination(nullptr); | ||
612 | 322 | // Load the ship | 349 | // Load the ship |
613 | 323 | std::vector<Worker*> workers; | 350 | std::vector<Worker*> workers; |
614 | 324 | std::vector<WareInstance*> wares; | 351 | std::vector<WareInstance*> wares; |
615 | @@ -337,46 +364,61 @@ | |||
616 | 337 | // The expedition goods are now on the ship, so from now on it is independent from the port | 364 | // The expedition goods are now on the ship, so from now on it is independent from the port |
617 | 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, |
618 | 339 | cancel_expedition(game); | 366 | cancel_expedition(game); |
647 | 340 | return fleet_->update(game); | 367 | fleet_->update(game); |
648 | 341 | } | 368 | return; |
649 | 342 | } | 369 | } |
650 | 343 | 370 | } | |
651 | 344 | if (ship.get_nritems() < ship.descr().get_capacity() && !waiting_.empty()) { | 371 | |
652 | 345 | uint32_t nrload = | 372 | // Decide where the arrived ship will go next |
653 | 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); |
654 | 347 | 374 | if (!next_port) { | |
655 | 348 | while (nrload--) { | 375 | ship.set_destination(next_port); |
656 | 349 | // Check if the item has still a valid destination | 376 | return; // no need to load anything |
657 | 350 | if (waiting_.back().get_destination(game)) { | 377 | } |
658 | 351 | // Destination is valid, so we load the item onto the ship | 378 | |
659 | 352 | ship.add_item(game, waiting_.back()); | 379 | // Unload any wares/workers onboard the departing ship which are not favored by next dest |
660 | 353 | } else { | 380 | ship.unload_unfit_items(game, *this, *next_port); |
661 | 354 | // The item has no valid destination anymore, so we just carry it | 381 | |
662 | 355 | // back in the warehouse | 382 | // Then load the remaining capacity of the departing ship with relevant items |
663 | 356 | waiting_.back().set_location(game, warehouse_); | 383 | uint32_t remaining_capacity = ship.descr().get_capacity() - ship.get_nritems(); |
664 | 357 | waiting_.back().end_shipping(game); | 384 | |
665 | 358 | } | 385 | // Firstly load the items which go to chosen destination, while also checking for items with invalid destination |
666 | 359 | waiting_.pop_back(); | 386 | for (auto si_it = waiting_.begin(); si_it != waiting_.end(); ++si_it) { |
667 | 360 | } | 387 | const PortDock* itemdest = si_it->get_destination(game); |
668 | 361 | 388 | if (itemdest) { | |
669 | 362 | if (waiting_.empty()) { | 389 | // Valid destination. Only load the item if it matches the ship's destination and the ship still has room for it |
670 | 363 | set_need_ship(game, false); | 390 | if (remaining_capacity > 0 && itemdest == next_port) { |
671 | 364 | } | 391 | ship.add_item(game, *si_it); // load it |
672 | 365 | } | 392 | si_it = waiting_.erase(si_it); |
673 | 366 | 393 | --remaining_capacity; | |
674 | 367 | fleet_->update(game); | 394 | } |
675 | 395 | } else { | ||
676 | 396 | // Invalid destination. Carry the item back into the warehouse | ||
677 | 397 | si_it->set_location(game, warehouse_); | ||
678 | 398 | si_it->end_shipping(game); | ||
679 | 399 | si_it = waiting_.erase(si_it); | ||
680 | 400 | } | ||
681 | 401 | } | ||
682 | 402 | |||
683 | 403 | if (remaining_capacity > 0) { | ||
684 | 404 | // There is still come capacity left. Load any items favored by the chosen destination on the ship. | ||
685 | 405 | for (auto si_it = waiting_.begin(); si_it != waiting_.end() && remaining_capacity > 0; ++si_it) { | ||
686 | 406 | assert(si_it->get_destination(game) != nullptr); | ||
687 | 407 | if (fleet_->is_path_favourable(*this, *next_port, *si_it->get_destination(game))) { | ||
688 | 408 | ship.add_item(game, *si_it); | ||
689 | 409 | si_it = waiting_.erase(si_it); | ||
690 | 410 | --remaining_capacity; | ||
691 | 411 | } | ||
692 | 412 | } | ||
693 | 413 | } | ||
694 | 414 | |||
695 | 415 | ship.set_destination(next_port); | ||
696 | 416 | set_need_ship(game, !waiting_.empty()); | ||
697 | 368 | } | 417 | } |
698 | 369 | 418 | ||
699 | 370 | void PortDock::set_need_ship(Game& game, bool need) { | 419 | void PortDock::set_need_ship(Game& game, bool need) { |
709 | 371 | molog("set_need_ship(%s)\n", need ? "true" : "false"); | 420 | if (need && fleet_) { |
710 | 372 | 421 | molog("trigger fleet update\n"); | |
702 | 373 | if (need == need_ship_) | ||
703 | 374 | return; | ||
704 | 375 | |||
705 | 376 | need_ship_ = need; | ||
706 | 377 | |||
707 | 378 | if (fleet_) { | ||
708 | 379 | molog("... trigger fleet update\n"); | ||
711 | 380 | fleet_->update(game); | 422 | fleet_->update(game); |
712 | 381 | } | 423 | } |
713 | 382 | } | 424 | } |
714 | @@ -445,13 +487,13 @@ | |||
715 | 445 | 487 | ||
716 | 446 | if (warehouse_) { | 488 | if (warehouse_) { |
717 | 447 | Coords pos(warehouse_->get_position()); | 489 | Coords pos(warehouse_->get_position()); |
719 | 448 | molog("PortDock for warehouse %u (at %i,%i) in fleet %u, need_ship: %s, waiting: %" PRIuS | 490 | molog("PortDock for warehouse %u (at %i,%i) in fleet %u, expedition_ready: %s, waiting: %" PRIuS |
720 | 449 | "\n", | 491 | "\n", |
721 | 450 | warehouse_->serial(), pos.x, pos.y, fleet_ ? fleet_->serial() : 0, | 492 | warehouse_->serial(), pos.x, pos.y, fleet_ ? fleet_->serial() : 0, |
723 | 451 | need_ship_ ? "true" : "false", waiting_.size()); | 493 | expedition_ready_ ? "true" : "false", waiting_.size()); |
724 | 452 | } else { | 494 | } else { |
727 | 453 | molog("PortDock without a warehouse in fleet %u, need_ship: %s, waiting: %" PRIuS "\n", | 495 | molog("PortDock without a warehouse in fleet %u, expedition_ready: %s, waiting: %" PRIuS "\n", |
728 | 454 | fleet_ ? fleet_->serial() : 0, need_ship_ ? "true" : "false", waiting_.size()); | 496 | fleet_ ? fleet_->serial() : 0, expedition_ready_ ? "true" : "false", waiting_.size()); |
729 | 455 | } | 497 | } |
730 | 456 | 498 | ||
731 | 457 | for (const ShippingItem& shipping_item : waiting_) { | 499 | for (const ShippingItem& shipping_item : waiting_) { |
732 | @@ -460,12 +502,12 @@ | |||
733 | 460 | } | 502 | } |
734 | 461 | } | 503 | } |
735 | 462 | 504 | ||
737 | 463 | constexpr uint8_t kCurrentPacketVersion = 3; | 505 | constexpr uint8_t kCurrentPacketVersion = 4; |
738 | 464 | 506 | ||
739 | 465 | PortDock::Loader::Loader() : warehouse_(0) { | 507 | PortDock::Loader::Loader() : warehouse_(0) { |
740 | 466 | } | 508 | } |
741 | 467 | 509 | ||
743 | 468 | void PortDock::Loader::load(FileRead& fr) { | 510 | void PortDock::Loader::load(FileRead& fr, uint8_t packet_version) { |
744 | 469 | PlayerImmovable::Loader::load(fr); | 511 | PlayerImmovable::Loader::load(fr); |
745 | 470 | 512 | ||
746 | 471 | PortDock& pd = get<PortDock>(); | 513 | PortDock& pd = get<PortDock>(); |
747 | @@ -479,7 +521,18 @@ | |||
748 | 479 | pd.set_position(egbase(), pd.dockpoints_[i]); | 521 | pd.set_position(egbase(), pd.dockpoints_[i]); |
749 | 480 | } | 522 | } |
750 | 481 | 523 | ||
752 | 482 | pd.need_ship_ = fr.unsigned_8(); | 524 | pd.ships_coming_ = fr.unsigned_8(); |
753 | 525 | |||
754 | 526 | // TODO(GunChleoc): Savegame compatibility Build 20 | ||
755 | 527 | if (packet_version < 4) { | ||
756 | 528 | pd.ships_coming_ = 0; | ||
757 | 529 | for (const Serial ship_serial : pd.owner().ships()) { | ||
758 | 530 | Ship* ship = dynamic_cast<Ship*>(egbase().objects().get_object(ship_serial)); | ||
759 | 531 | if (ship->get_destination(egbase())->serial() == pd.serial()) { | ||
760 | 532 | ++pd.ships_coming_; | ||
761 | 533 | } | ||
762 | 534 | } | ||
763 | 535 | } | ||
764 | 483 | 536 | ||
765 | 484 | waiting_.resize(fr.unsigned_32()); | 537 | waiting_.resize(fr.unsigned_32()); |
766 | 485 | for (ShippingItem::Loader& shipping_loader : waiting_) { | 538 | for (ShippingItem::Loader& shipping_loader : waiting_) { |
767 | @@ -499,10 +552,10 @@ | |||
768 | 499 | PortDock& pd = get<PortDock>(); | 552 | PortDock& pd = get<PortDock>(); |
769 | 500 | pd.warehouse_ = &mol().get<Warehouse>(warehouse_); | 553 | pd.warehouse_ = &mol().get<Warehouse>(warehouse_); |
770 | 501 | 554 | ||
771 | 502 | pd.waiting_.resize(waiting_.size()); | ||
772 | 503 | for (uint32_t i = 0; i < waiting_.size(); ++i) { | 555 | for (uint32_t i = 0; i < waiting_.size(); ++i) { |
774 | 504 | pd.waiting_[i] = waiting_[i].get(mol()); | 556 | pd.waiting_.push_back(waiting_[i].get(mol())); |
775 | 505 | } | 557 | } |
776 | 558 | assert(pd.waiting_.size() == waiting_.size()); | ||
777 | 506 | } | 559 | } |
778 | 507 | 560 | ||
779 | 508 | void PortDock::Loader::load_finish() { | 561 | void PortDock::Loader::load_finish() { |
780 | @@ -527,10 +580,11 @@ | |||
781 | 527 | try { | 580 | try { |
782 | 528 | // The header has been peeled away by the caller | 581 | // The header has been peeled away by the caller |
783 | 529 | 582 | ||
784 | 583 | // TODO(GunChleoc): Savegame compatibility Build 20 | ||
785 | 530 | uint8_t const packet_version = fr.unsigned_8(); | 584 | uint8_t const packet_version = fr.unsigned_8(); |
787 | 531 | if (packet_version == kCurrentPacketVersion) { | 585 | if (packet_version >= 3 && packet_version <= kCurrentPacketVersion) { |
788 | 532 | loader->init(egbase, mol, *new PortDock(nullptr)); | 586 | loader->init(egbase, mol, *new PortDock(nullptr)); |
790 | 533 | loader->load(fr); | 587 | loader->load(fr, packet_version); |
791 | 534 | } else { | 588 | } else { |
792 | 535 | throw UnhandledVersionError("PortDock", packet_version, kCurrentPacketVersion); | 589 | throw UnhandledVersionError("PortDock", packet_version, kCurrentPacketVersion); |
793 | 536 | } | 590 | } |
794 | @@ -553,7 +607,7 @@ | |||
795 | 553 | write_coords_32(&fw, coords); | 607 | write_coords_32(&fw, coords); |
796 | 554 | } | 608 | } |
797 | 555 | 609 | ||
799 | 556 | fw.unsigned_8(need_ship_); | 610 | fw.unsigned_8(ships_coming_); |
800 | 557 | 611 | ||
801 | 558 | fw.unsigned_32(waiting_.size()); | 612 | fw.unsigned_32(waiting_.size()); |
802 | 559 | for (ShippingItem& shipping_item : waiting_) { | 613 | for (ShippingItem& shipping_item : waiting_) { |
803 | 560 | 614 | ||
804 | === modified file 'src/economy/portdock.h' | |||
805 | --- src/economy/portdock.h 2018-09-04 15:48:47 +0000 | |||
806 | +++ src/economy/portdock.h 2018-09-21 19:37:16 +0000 | |||
807 | @@ -20,6 +20,7 @@ | |||
808 | 20 | #ifndef WL_ECONOMY_PORTDOCK_H | 20 | #ifndef WL_ECONOMY_PORTDOCK_H |
809 | 21 | #define WL_ECONOMY_PORTDOCK_H | 21 | #define WL_ECONOMY_PORTDOCK_H |
810 | 22 | 22 | ||
811 | 23 | #include <list> | ||
812 | 23 | #include <memory> | 24 | #include <memory> |
813 | 24 | 25 | ||
814 | 25 | #include "base/macros.h" | 26 | #include "base/macros.h" |
815 | @@ -85,9 +86,7 @@ | |||
816 | 85 | return fleet_; | 86 | return fleet_; |
817 | 86 | } | 87 | } |
818 | 87 | PortDock* get_dock(Flag& flag) const; | 88 | PortDock* get_dock(Flag& flag) const; |
822 | 88 | bool get_need_ship() const { | 89 | uint32_t get_need_ship() const; |
820 | 89 | return need_ship_ || expedition_ready_; | ||
821 | 90 | } | ||
823 | 91 | 90 | ||
824 | 92 | void set_economy(Economy*) override; | 91 | void set_economy(Economy*) override; |
825 | 93 | 92 | ||
826 | @@ -113,6 +112,9 @@ | |||
827 | 113 | void add_shippingitem(Game&, Worker&); | 112 | void add_shippingitem(Game&, Worker&); |
828 | 114 | void update_shippingitem(Game&, Worker&); | 113 | void update_shippingitem(Game&, Worker&); |
829 | 115 | 114 | ||
830 | 115 | void shipping_item_arrived(Game&, ShippingItem&); | ||
831 | 116 | void shipping_item_returned(Game&, ShippingItem&); | ||
832 | 117 | void ship_coming(bool affirmative); | ||
833 | 116 | void ship_arrived(Game&, Ship&); | 118 | void ship_arrived(Game&, Ship&); |
834 | 117 | 119 | ||
835 | 118 | void log_general_info(const EditorGameBase&) const override; | 120 | void log_general_info(const EditorGameBase&) const override; |
836 | @@ -139,14 +141,14 @@ | |||
837 | 139 | 141 | ||
838 | 140 | void init_fleet(EditorGameBase& egbase); | 142 | void init_fleet(EditorGameBase& egbase); |
839 | 141 | void set_fleet(Fleet* fleet); | 143 | void set_fleet(Fleet* fleet); |
841 | 142 | void update_shippingitem(Game&, std::vector<ShippingItem>::iterator); | 144 | void update_shippingitem(Game&, std::list<ShippingItem>::iterator); |
842 | 143 | void set_need_ship(Game&, bool need); | 145 | void set_need_ship(Game&, bool need); |
843 | 144 | 146 | ||
844 | 145 | Fleet* fleet_; | 147 | Fleet* fleet_; |
845 | 146 | Warehouse* warehouse_; | 148 | Warehouse* warehouse_; |
846 | 147 | PositionList dockpoints_; | 149 | PositionList dockpoints_; |
849 | 148 | std::vector<ShippingItem> waiting_; | 150 | std::list<ShippingItem> waiting_; |
850 | 149 | bool need_ship_; | 151 | uint8_t ships_coming_; |
851 | 150 | bool expedition_ready_; | 152 | bool expedition_ready_; |
852 | 151 | 153 | ||
853 | 152 | std::unique_ptr<ExpeditionBootstrap> expedition_bootstrap_; | 154 | std::unique_ptr<ExpeditionBootstrap> expedition_bootstrap_; |
854 | @@ -157,7 +159,7 @@ | |||
855 | 157 | public: | 159 | public: |
856 | 158 | Loader(); | 160 | Loader(); |
857 | 159 | 161 | ||
859 | 160 | void load(FileRead&); | 162 | void load(FileRead&, uint8_t packet_version); |
860 | 161 | void load_pointers() override; | 163 | void load_pointers() override; |
861 | 162 | void load_finish() override; | 164 | void load_finish() override; |
862 | 163 | 165 | ||
863 | 164 | 166 | ||
864 | === modified file 'src/economy/shippingitem.cc' | |||
865 | --- src/economy/shippingitem.cc 2018-04-07 16:59:00 +0000 | |||
866 | +++ src/economy/shippingitem.cc 2018-09-21 19:37:16 +0000 | |||
867 | @@ -105,7 +105,7 @@ | |||
868 | 105 | worker->end_shipping(game); | 105 | worker->end_shipping(game); |
869 | 106 | } | 106 | } |
870 | 107 | 107 | ||
872 | 108 | PortDock* ShippingItem::get_destination(Game& game) { | 108 | const PortDock* ShippingItem::get_destination(Game& game) const { |
873 | 109 | return destination_dock_.get(game); | 109 | return destination_dock_.get(game); |
874 | 110 | } | 110 | } |
875 | 111 | 111 | ||
876 | 112 | 112 | ||
877 | === modified file 'src/economy/shippingitem.h' | |||
878 | --- src/economy/shippingitem.h 2018-04-07 16:59:00 +0000 | |||
879 | +++ src/economy/shippingitem.h 2018-09-21 19:37:16 +0000 | |||
880 | @@ -53,7 +53,7 @@ | |||
881 | 53 | void get(const EditorGameBase& game, WareInstance** ware, Worker** worker) const; | 53 | void get(const EditorGameBase& game, WareInstance** ware, Worker** worker) const; |
882 | 54 | 54 | ||
883 | 55 | void set_economy(Game&, Economy* e); | 55 | void set_economy(Game&, Economy* e); |
885 | 56 | PortDock* get_destination(Game&); | 56 | const PortDock* get_destination(Game&) const; |
886 | 57 | 57 | ||
887 | 58 | void remove(EditorGameBase&); | 58 | void remove(EditorGameBase&); |
888 | 59 | 59 | ||
889 | 60 | 60 | ||
890 | === modified file 'src/logic/map_objects/tribes/ship.cc' | |||
891 | --- src/logic/map_objects/tribes/ship.cc 2018-09-11 16:58:16 +0000 | |||
892 | +++ src/logic/map_objects/tribes/ship.cc 2018-09-21 19:37:16 +0000 | |||
893 | @@ -303,12 +303,22 @@ | |||
894 | 303 | 303 | ||
895 | 304 | FCoords position = map.get_fcoords(get_position()); | 304 | FCoords position = map.get_fcoords(get_position()); |
896 | 305 | if (position.field->get_immovable() == dst) { | 305 | if (position.field->get_immovable() == dst) { |
903 | 306 | molog("ship_update: Arrived at dock %u\n", dst->serial()); | 306 | if (lastdock_ != dst) { |
904 | 307 | lastdock_ = dst; | 307 | molog("ship_update: Arrived at dock %u\n", dst->serial()); |
905 | 308 | destination_ = nullptr; | 308 | lastdock_ = dst; |
906 | 309 | dst->ship_arrived(game, *this); | 309 | } |
907 | 310 | start_task_idle(game, descr().main_animation(), 250); | 310 | if (withdraw_item(game, *dst)) { |
908 | 311 | Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged)); | 311 | schedule_act(game, kShipInterval); |
909 | 312 | return true; | ||
910 | 313 | } | ||
911 | 314 | |||
912 | 315 | dst->ship_arrived(game, *this); // This will also set the destination | ||
913 | 316 | dst = get_destination(game); | ||
914 | 317 | if (dst) { | ||
915 | 318 | start_task_movetodock(game, *dst); | ||
916 | 319 | } else { | ||
917 | 320 | start_task_idle(game, descr().main_animation(), 250); | ||
918 | 321 | } | ||
919 | 312 | return true; | 322 | return true; |
920 | 313 | } | 323 | } |
921 | 314 | 324 | ||
922 | @@ -484,7 +494,7 @@ | |||
923 | 484 | } | 494 | } |
924 | 485 | 495 | ||
925 | 486 | if (totalprob == 0) { | 496 | if (totalprob == 0) { |
927 | 487 | start_task_idle(game, descr().main_animation(), 1500); | 497 | start_task_idle(game, descr().main_animation(), kShipInterval); |
928 | 488 | return; | 498 | return; |
929 | 489 | } | 499 | } |
930 | 490 | 500 | ||
931 | @@ -496,13 +506,13 @@ | |||
932 | 496 | } | 506 | } |
933 | 497 | 507 | ||
934 | 498 | if (dir == 0 || dir > LAST_DIRECTION) { | 508 | if (dir == 0 || dir > LAST_DIRECTION) { |
936 | 499 | start_task_idle(game, descr().main_animation(), 1500); | 509 | start_task_idle(game, descr().main_animation(), kShipInterval); |
937 | 500 | return; | 510 | return; |
938 | 501 | } | 511 | } |
939 | 502 | 512 | ||
940 | 503 | FCoords neighbour = map.get_neighbour(position, dir); | 513 | FCoords neighbour = map.get_neighbour(position, dir); |
941 | 504 | if (!(neighbour.field->nodecaps() & MOVECAPS_SWIM)) { | 514 | if (!(neighbour.field->nodecaps() & MOVECAPS_SWIM)) { |
943 | 505 | start_task_idle(game, descr().main_animation(), 1500); | 515 | start_task_idle(game, descr().main_animation(), kShipInterval); |
944 | 506 | return; | 516 | return; |
945 | 507 | } | 517 | } |
946 | 508 | 518 | ||
947 | @@ -542,7 +552,7 @@ | |||
948 | 542 | pgettext("ship", "Waiting"), _("Island Circumnavigated"), | 552 | pgettext("ship", "Waiting"), _("Island Circumnavigated"), |
949 | 543 | _("An expedition ship sailed around its island without any events."), | 553 | _("An expedition ship sailed around its island without any events."), |
950 | 544 | "images/wui/ship/ship_explore_island_cw.png"); | 554 | "images/wui/ship/ship_explore_island_cw.png"); |
952 | 545 | return start_task_idle(game, descr().main_animation(), 1500); | 555 | return start_task_idle(game, descr().main_animation(), kShipInterval); |
953 | 546 | } | 556 | } |
954 | 547 | } | 557 | } |
955 | 548 | // The ship is supposed to follow the coast as close as possible, therefore the check | 558 | // The ship is supposed to follow the coast as close as possible, therefore the check |
956 | @@ -585,7 +595,7 @@ | |||
957 | 585 | shipname_.c_str()); | 595 | shipname_.c_str()); |
958 | 586 | set_ship_state_and_notify( | 596 | set_ship_state_and_notify( |
959 | 587 | ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand); | 597 | ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand); |
961 | 588 | start_task_idle(game, descr().main_animation(), 1500); | 598 | start_task_idle(game, descr().main_animation(), kShipInterval); |
962 | 589 | return; | 599 | return; |
963 | 590 | } | 600 | } |
964 | 591 | } else { // scouting towards a specific direction | 601 | } else { // scouting towards a specific direction |
965 | @@ -598,7 +608,7 @@ | |||
966 | 598 | // coast reached | 608 | // coast reached |
967 | 599 | set_ship_state_and_notify( | 609 | set_ship_state_and_notify( |
968 | 600 | ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand); | 610 | ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand); |
970 | 601 | start_task_idle(game, descr().main_animation(), 1500); | 611 | start_task_idle(game, descr().main_animation(), kShipInterval); |
971 | 602 | // Send a message to the player, that a new coast was reached | 612 | // Send a message to the player, that a new coast was reached |
972 | 603 | send_message(game, | 613 | send_message(game, |
973 | 604 | /** TRANSLATORS: A ship has discovered land */ | 614 | /** TRANSLATORS: A ship has discovered land */ |
974 | @@ -692,14 +702,14 @@ | |||
975 | 692 | } | 702 | } |
976 | 693 | 703 | ||
977 | 694 | expedition_.reset(nullptr); | 704 | expedition_.reset(nullptr); |
979 | 695 | return start_task_idle(game, descr().main_animation(), 1500); | 705 | return start_task_idle(game, descr().main_animation(), kShipInterval); |
980 | 696 | } | 706 | } |
981 | 697 | } | 707 | } |
982 | 698 | FALLS_THROUGH; | 708 | FALLS_THROUGH; |
983 | 699 | case ShipStates::kExpeditionWaiting: | 709 | case ShipStates::kExpeditionWaiting: |
984 | 700 | case ShipStates::kExpeditionPortspaceFound: { | 710 | case ShipStates::kExpeditionPortspaceFound: { |
985 | 701 | // wait for input | 711 | // wait for input |
987 | 702 | start_task_idle(game, descr().main_animation(), 1500); | 712 | start_task_idle(game, descr().main_animation(), kShipInterval); |
988 | 703 | return; | 713 | return; |
989 | 704 | } | 714 | } |
990 | 705 | FALLS_THROUGH; | 715 | FALLS_THROUGH; |
991 | @@ -729,14 +739,17 @@ | |||
992 | 729 | 739 | ||
993 | 730 | /** | 740 | /** |
994 | 731 | * Enter a new destination port for the ship. | 741 | * Enter a new destination port for the ship. |
997 | 732 | * | 742 | * Call this after (un)loading the ship, for proper logging. |
996 | 733 | * @note This is supposed to be called only from the scheduling code of @ref Fleet. | ||
998 | 734 | */ | 743 | */ |
1004 | 735 | void Ship::set_destination(Game& game, PortDock& pd) { | 744 | void Ship::set_destination(PortDock* pd) { |
1005 | 736 | molog("set_destination / sending to portdock %u (carrying %" PRIuS " items)\n", pd.serial(), | 745 | destination_ = pd; |
1006 | 737 | items_.size()); | 746 | if (pd) { |
1007 | 738 | destination_ = &pd; | 747 | molog("set_destination / sending to portdock %u (carrying %" PRIuS " items)\n", pd->serial(), |
1008 | 739 | send_signal(game, "wakeup"); | 748 | items_.size()); |
1009 | 749 | pd->ship_coming(true); | ||
1010 | 750 | } else { | ||
1011 | 751 | molog("set_destination / none\n"); | ||
1012 | 752 | } | ||
1013 | 740 | Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged)); | 753 | Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged)); |
1014 | 741 | } | 754 | } |
1015 | 742 | 755 | ||
1016 | @@ -747,14 +760,39 @@ | |||
1017 | 747 | items_.back().set_location(game, this); | 760 | items_.back().set_location(game, this); |
1018 | 748 | } | 761 | } |
1019 | 749 | 762 | ||
1026 | 750 | void Ship::withdraw_items(Game& game, PortDock& pd, std::vector<ShippingItem>& items) { | 763 | /** |
1027 | 751 | uint32_t dst = 0; | 764 | * Unload one item designated for given dock or for no dock. |
1028 | 752 | for (uint32_t src = 0; src < items_.size(); ++src) { | 765 | * \return true if item unloaded. |
1029 | 753 | PortDock* destination = items_[src].get_destination(game); | 766 | */ |
1030 | 754 | if (!destination || destination == &pd) { | 767 | bool Ship::withdraw_item(Game& game, PortDock& pd) { |
1031 | 755 | items.push_back(items_[src]); | 768 | bool unloaded = false; |
1032 | 769 | size_t dst = 0; | ||
1033 | 770 | for (ShippingItem& si : items_) { | ||
1034 | 771 | if (!unloaded) { | ||
1035 | 772 | const PortDock* itemdest = si.get_destination(game); | ||
1036 | 773 | if (!itemdest || itemdest == &pd) { | ||
1037 | 774 | pd.shipping_item_arrived(game, si); | ||
1038 | 775 | unloaded = true; | ||
1039 | 776 | continue; | ||
1040 | 777 | } | ||
1041 | 778 | } | ||
1042 | 779 | items_[dst++] = si; | ||
1043 | 780 | } | ||
1044 | 781 | items_.resize(dst); | ||
1045 | 782 | return unloaded; | ||
1046 | 783 | } | ||
1047 | 784 | |||
1048 | 785 | /** | ||
1049 | 786 | * Unload all items not favored by given next dest. | ||
1050 | 787 | * Assert all items for current portdock have already been unloaded. | ||
1051 | 788 | */ | ||
1052 | 789 | void Ship::unload_unfit_items(Game& game, PortDock& here, const PortDock& nextdest) { | ||
1053 | 790 | size_t dst = 0; | ||
1054 | 791 | for (ShippingItem& si : items_) { | ||
1055 | 792 | if (fleet_->is_path_favourable(here, nextdest, *si.get_destination(game))) { | ||
1056 | 793 | items_[dst++] = si; | ||
1057 | 756 | } else { | 794 | } else { |
1059 | 757 | items_[dst++] = items_[src]; | 795 | here.shipping_item_returned(game, si); |
1060 | 758 | } | 796 | } |
1061 | 759 | } | 797 | } |
1062 | 760 | items_.resize(dst); | 798 | items_.resize(dst); |
1063 | @@ -813,7 +851,7 @@ | |||
1064 | 813 | // I (tiborb) failed to invoke this situation when testing so | 851 | // I (tiborb) failed to invoke this situation when testing so |
1065 | 814 | // I am not sure if following line behaves allright | 852 | // I am not sure if following line behaves allright |
1066 | 815 | get_fleet()->update(game); | 853 | get_fleet()->update(game); |
1068 | 816 | start_task_idle(game, descr().main_animation(), 5000); | 854 | start_task_idle(game, descr().main_animation(), kFleetInterval); |
1069 | 817 | } | 855 | } |
1070 | 818 | } | 856 | } |
1071 | 819 | 857 | ||
1072 | @@ -837,10 +875,10 @@ | |||
1073 | 837 | 875 | ||
1074 | 838 | set_economy(game, expedition_->economy); | 876 | set_economy(game, expedition_->economy); |
1075 | 839 | 877 | ||
1077 | 840 | for (int i = items_.size() - 1; i >= 0; --i) { | 878 | for (const ShippingItem& si : items_) { |
1078 | 841 | WareInstance* ware; | 879 | WareInstance* ware; |
1079 | 842 | Worker* worker; | 880 | Worker* worker; |
1081 | 843 | items_.at(i).get(game, &ware, &worker); | 881 | si.get(game, &ware, &worker); |
1082 | 844 | if (worker) { | 882 | if (worker) { |
1083 | 845 | worker->reset_tasks(game); | 883 | worker->reset_tasks(game); |
1084 | 846 | worker->start_task_idle(game, 0, -1); | 884 | worker->start_task_idle(game, 0, -1); |
1085 | @@ -971,8 +1009,12 @@ | |||
1086 | 971 | /// @note only called via player command | 1009 | /// @note only called via player command |
1087 | 972 | void Ship::sink_ship(Game& game) { | 1010 | void Ship::sink_ship(Game& game) { |
1088 | 973 | // Running colonization has the highest priority + a sink request is only valid once | 1011 | // Running colonization has the highest priority + a sink request is only valid once |
1090 | 974 | if (!state_is_sinkable()) | 1012 | if (!state_is_sinkable()) { |
1091 | 975 | return; | 1013 | return; |
1092 | 1014 | } | ||
1093 | 1015 | if (destination_.is_set()) { | ||
1094 | 1016 | destination_.get(game)->ship_coming(false); | ||
1095 | 1017 | } | ||
1096 | 976 | ship_state_ = ShipStates::kSinkRequest; | 1018 | ship_state_ = ShipStates::kSinkRequest; |
1097 | 977 | // Make sure the ship is active and close possible open windows | 1019 | // Make sure the ship is active and close possible open windows |
1098 | 978 | ship_wakeup(game); | 1020 | ship_wakeup(game); |
1099 | 979 | 1021 | ||
1100 | === modified file 'src/logic/map_objects/tribes/ship.h' | |||
1101 | --- src/logic/map_objects/tribes/ship.h 2018-09-05 06:42:21 +0000 | |||
1102 | +++ src/logic/map_objects/tribes/ship.h 2018-09-21 19:37:16 +0000 | |||
1103 | @@ -78,6 +78,8 @@ | |||
1104 | 78 | DISALLOW_COPY_AND_ASSIGN(ShipDescr); | 78 | DISALLOW_COPY_AND_ASSIGN(ShipDescr); |
1105 | 79 | }; | 79 | }; |
1106 | 80 | 80 | ||
1107 | 81 | constexpr int32_t kShipInterval = 1500; | ||
1108 | 82 | |||
1109 | 81 | /** | 83 | /** |
1110 | 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) |
1111 | 83 | * fleet for a player, but only if they are on standard duty. Exploration ships | 85 | * fleet for a player, but only if they are on standard duty. Exploration ships |
1112 | @@ -104,7 +106,7 @@ | |||
1113 | 104 | return economy_; | 106 | return economy_; |
1114 | 105 | } | 107 | } |
1115 | 106 | void set_economy(Game&, Economy* e); | 108 | void set_economy(Game&, Economy* e); |
1117 | 107 | void set_destination(Game&, PortDock&); | 109 | void set_destination(PortDock*); |
1118 | 108 | 110 | ||
1119 | 109 | void init_auto_task(Game&) override; | 111 | void init_auto_task(Game&) override; |
1120 | 110 | 112 | ||
1121 | @@ -126,8 +128,9 @@ | |||
1122 | 126 | return items_[idx]; | 128 | return items_[idx]; |
1123 | 127 | } | 129 | } |
1124 | 128 | 130 | ||
1127 | 129 | void withdraw_items(Game& game, PortDock& pd, std::vector<ShippingItem>& items); | 131 | void add_item(Game&, const ShippingItem&); |
1128 | 130 | void add_item(Game&, const ShippingItem& item); | 132 | bool withdraw_item(Game&, PortDock&); |
1129 | 133 | void unload_unfit_items(Game&, PortDock& here, const PortDock& nextdest); | ||
1130 | 131 | 134 | ||
1131 | 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, |
1132 | 133 | // kExpeditionPortspaceFound or kExpeditionColonizing in the first states, the owning player of | 136 | // kExpeditionPortspaceFound or kExpeditionColonizing in the first states, the owning player of |
Continuous integration builds have changed state:
Travis build 3754. State: errored. Details: https:/ /travis- ci.org/ widelands/ widelands/ builds/ 412020649. /ci.appveyor. com/project/ widelands- dev/widelands/ build/_ widelands_ dev_widelands_ ship_scheduling _2-3554.
Appveyor build 3554. State: failed. Details: https:/