Merge lp:~widelands-dev/widelands/market1 into lp:widelands
- market1
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 8462 |
Proposed branch: | lp:~widelands-dev/widelands/market1 |
Merge into: | lp:widelands |
Diff against target: |
1398 lines (+786/-64) 32 files modified
data/tribes/buildings/markets/barbarians/market/init.lua (+1/-3) src/economy/request.h (+2/-5) src/economy/ware_instance.cc (+2/-1) src/game_io/game_class_packet.cc (+2/-0) src/logic/CMakeLists.txt (+7/-6) src/logic/game.cc (+82/-0) src/logic/game.h (+10/-0) src/logic/map_objects/bob.cc (+4/-0) src/logic/map_objects/bob.h (+1/-0) src/logic/map_objects/tribes/market.cc (+222/-1) src/logic/map_objects/tribes/market.h (+48/-1) src/logic/map_objects/tribes/productionsite.cc (+5/-2) src/logic/map_objects/tribes/productionsite.h (+0/-7) src/logic/map_objects/tribes/tribes.cc (+1/-0) src/logic/map_objects/tribes/tribes.h (+0/-1) src/logic/map_objects/tribes/worker.cc (+83/-0) src/logic/map_objects/tribes/worker.h (+5/-0) src/logic/message.h (+2/-1) src/logic/playercommand.cc (+93/-1) src/logic/playercommand.h (+23/-0) src/logic/queue_cmd_factory.cc (+2/-0) src/logic/queue_cmd_ids.h (+2/-1) src/logic/trade_agreement.h (+54/-0) src/map_io/map_buildingdata_packet.cc (+1/-0) src/scripting/lua_map.cc (+76/-27) src/scripting/lua_map.h (+2/-0) src/wui/game_message_menu.cc (+2/-0) src/wui/ware_statistics_menu.cc (+1/-2) src/wui/ware_statistics_menu.h (+0/-1) test/maps/market_trading.wmf/scripting/init.lua (+12/-2) test/maps/market_trading.wmf/scripting/test_market_can_be_built.lua (+2/-2) test/maps/market_trading.wmf/scripting/test_simple_trade.lua (+39/-0) |
To merge this branch: | bzr merge lp:~widelands-dev/widelands/market1 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
SirVer | Needs Resubmitting | ||
TiborB | Approve | ||
Review via email: mp+331233@code.launchpad.net |
Commit message
First working land-based trading implementation.
A trade works like this: A player proposes to another players market to initiate a trade. A trade consists of the three pieces of information: what wares will I sent, what wares do I want from you and how often do we exchange. For example I'll send 3 logs whenever you send 1 plank and 5 iron and we do ship these batches 10 times.
Once a trade is initiated, both players will require the correct number of oxen for a batch, i.e. p1 requires 3 (for 3 logs) and p2 requires 6 (1 plank + 5 iron). Once all workers and wares for one batch have arrived in the markets, both markets will launch one caravan and the items gets exchanged.
This is a minimal implementation and not ready for use in-game. Highlights of stuff still missing:
- UI (clicking a market will crash the game).
- denying/accepting a proposed trade. Every proposed trade is auto-accepted atm.
- releasing trade carriers and left-over wares once a trade is cancelled or fulfilled.
- load/save/serialize for networking for all commands and new map objects.
Implemented:
- Adds Lua functions to propose a trade from Lua.
- Adds a PlayerCommand to propose a trade from Lua, but this is still unused.
- Adds logic to trade between two Markets.
- Adds a test that does a simple trade.
The test is run with the regular regression test runs, but can be run standalone using:
./build/
Description of the change
SirVer (sirver) wrote : | # |
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2686. State: errored. Details: https:/
Appveyor build 2506. State: success. Details: https:/
SirVer (sirver) wrote : | # |
I am unsure as of now how, but this breaks the casern and something in seafaring. I have to investigate how I broke this.
SirVer (sirver) wrote : | # |
Found it - a copy & pasto. Great to have tests catching stuff like this :)
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2689. State: passed. Details: https:/
Appveyor build 2509. State: success. Details: https:/
TiborB (tiborb95) wrote : | # |
I compiled the branch and "tested" it, though actually there is not much to test.
I did as well some AI-only test so at least I can say that this does not affect normal play.
So IMHO it can go.
Did not read the code though
GunChleoc (gunchleoc) wrote : | # |
I added a bunch of small nits & ideas.
I don't know when I'll be able to check in again, so please do merge once you're happy.
SirVer (sirver) wrote : | # |
Thank you for the code review! All addressed.
@bunnybot merge
GunChleoc (gunchleoc) wrote : | # |
I added some more comments for a follow-up branch.
Preview Diff
1 | === modified file 'data/tribes/buildings/markets/barbarians/market/init.lua' | |||
2 | --- data/tribes/buildings/markets/barbarians/market/init.lua 2017-09-15 19:53:28 +0000 | |||
3 | +++ data/tribes/buildings/markets/barbarians/market/init.lua 2017-10-03 20:35:46 +0000 | |||
4 | @@ -50,7 +50,5 @@ | |||
5 | 50 | prohibited_till = 1000 | 50 | prohibited_till = 1000 |
6 | 51 | }, | 51 | }, |
7 | 52 | 52 | ||
11 | 53 | working_positions = { | 53 | carrier = "barbarians_ox", |
9 | 54 | barbarians_ox = 10, | ||
10 | 55 | }, | ||
12 | 56 | } | 54 | } |
13 | 57 | 55 | ||
14 | === modified file 'src/economy/request.h' | |||
15 | --- src/economy/request.h 2017-01-25 18:55:59 +0000 | |||
16 | +++ src/economy/request.h 2017-10-03 20:35:46 +0000 | |||
17 | @@ -123,6 +123,7 @@ | |||
18 | 123 | // callbacks for WareInstance/Worker code | 123 | // callbacks for WareInstance/Worker code |
19 | 124 | void transfer_finish(Game&, Transfer&); | 124 | void transfer_finish(Game&, Transfer&); |
20 | 125 | void transfer_fail(Game&, Transfer&); | 125 | void transfer_fail(Game&, Transfer&); |
21 | 126 | void cancel_transfer(uint32_t idx); | ||
22 | 126 | 127 | ||
23 | 127 | void set_requirements(const Requirements& r) { | 128 | void set_requirements(const Requirements& r) { |
24 | 128 | requirements_ = r; | 129 | requirements_ = r; |
25 | @@ -131,13 +132,9 @@ | |||
26 | 131 | return requirements_; | 132 | return requirements_; |
27 | 132 | } | 133 | } |
28 | 133 | 134 | ||
29 | 135 | |||
30 | 134 | private: | 136 | private: |
31 | 135 | int32_t get_base_required_time(EditorGameBase&, uint32_t nr) const; | 137 | int32_t get_base_required_time(EditorGameBase&, uint32_t nr) const; |
32 | 136 | |||
33 | 137 | public: | ||
34 | 138 | void cancel_transfer(uint32_t idx); | ||
35 | 139 | |||
36 | 140 | private: | ||
37 | 141 | void remove_transfer(uint32_t idx); | 138 | void remove_transfer(uint32_t idx); |
38 | 142 | uint32_t find_transfer(Transfer&); | 139 | uint32_t find_transfer(Transfer&); |
39 | 143 | 140 | ||
40 | 144 | 141 | ||
41 | === modified file 'src/economy/ware_instance.cc' | |||
42 | --- src/economy/ware_instance.cc 2017-08-16 10:14:29 +0000 | |||
43 | +++ src/economy/ware_instance.cc 2017-10-03 20:35:46 +0000 | |||
44 | @@ -309,8 +309,9 @@ | |||
45 | 309 | 309 | ||
46 | 310 | // Update whether we have a Supply or not | 310 | // Update whether we have a Supply or not |
47 | 311 | if (!transfer_ || !transfer_->get_request()) { | 311 | if (!transfer_ || !transfer_->get_request()) { |
49 | 312 | if (!supply_) | 312 | if (!supply_) { |
50 | 313 | supply_ = new IdleWareSupply(*this); | 313 | supply_ = new IdleWareSupply(*this); |
51 | 314 | } | ||
52 | 314 | } else { | 315 | } else { |
53 | 315 | delete supply_; | 316 | delete supply_; |
54 | 316 | supply_ = nullptr; | 317 | supply_ = nullptr; |
55 | 317 | 318 | ||
56 | === modified file 'src/game_io/game_class_packet.cc' | |||
57 | --- src/game_io/game_class_packet.cc 2017-01-25 18:55:59 +0000 | |||
58 | +++ src/game_io/game_class_packet.cc 2017-10-03 20:35:46 +0000 | |||
59 | @@ -61,6 +61,8 @@ | |||
60 | 61 | // Write gametime | 61 | // Write gametime |
61 | 62 | fw.unsigned_32(game.gametime_); | 62 | fw.unsigned_32(game.gametime_); |
62 | 63 | 63 | ||
63 | 64 | // TODO(sirver,trading): save/load trade_agreements and related data. | ||
64 | 65 | |||
65 | 64 | // We do not care for players, since they were set | 66 | // We do not care for players, since they were set |
66 | 65 | // on game initialization to match Map::scenario_player_[names|tribes] | 67 | // on game initialization to match Map::scenario_player_[names|tribes] |
67 | 66 | // or vice versa, so this is handled by map loader | 68 | // or vice versa, so this is handled by map loader |
68 | 67 | 69 | ||
69 | === modified file 'src/logic/CMakeLists.txt' | |||
70 | --- src/logic/CMakeLists.txt 2017-09-15 12:33:58 +0000 | |||
71 | +++ src/logic/CMakeLists.txt 2017-10-03 20:35:46 +0000 | |||
72 | @@ -95,14 +95,14 @@ | |||
73 | 95 | findimmovable.h | 95 | findimmovable.h |
74 | 96 | findnode.cc | 96 | findnode.cc |
75 | 97 | findnode.h | 97 | findnode.h |
76 | 98 | game.cc | ||
77 | 99 | game.h | ||
78 | 98 | game_data_error.cc | 100 | game_data_error.cc |
79 | 99 | game_data_error.h | 101 | game_data_error.h |
82 | 100 | game.cc | 102 | map.cc |
83 | 101 | game.h | 103 | map.h |
84 | 102 | map_revision.cc | 104 | map_revision.cc |
85 | 103 | map_revision.h | 105 | map_revision.h |
86 | 104 | map.cc | ||
87 | 105 | map.h | ||
88 | 106 | mapastar.cc | 106 | mapastar.cc |
89 | 107 | mapastar.h | 107 | mapastar.h |
90 | 108 | mapdifferenceregion.cc | 108 | mapdifferenceregion.cc |
91 | @@ -114,18 +114,18 @@ | |||
92 | 114 | mapregion.h | 114 | mapregion.h |
93 | 115 | maptriangleregion.cc | 115 | maptriangleregion.cc |
94 | 116 | maptriangleregion.h | 116 | maptriangleregion.h |
95 | 117 | message.h | ||
96 | 117 | message_id.h | 118 | message_id.h |
97 | 118 | message_queue.h | 119 | message_queue.h |
98 | 119 | message.h | ||
99 | 120 | nodecaps.h | 120 | nodecaps.h |
100 | 121 | objective.h | 121 | objective.h |
101 | 122 | path.cc | 122 | path.cc |
102 | 123 | path.h | 123 | path.h |
103 | 124 | pathfield.cc | 124 | pathfield.cc |
104 | 125 | pathfield.h | 125 | pathfield.h |
105 | 126 | player_area.h | ||
106 | 127 | player.cc | 126 | player.cc |
107 | 128 | player.h | 127 | player.h |
108 | 128 | player_area.h | ||
109 | 129 | playercommand.cc | 129 | playercommand.cc |
110 | 130 | playercommand.h | 130 | playercommand.h |
111 | 131 | playersmanager.cc | 131 | playersmanager.cc |
112 | @@ -138,6 +138,7 @@ | |||
113 | 138 | roadtype.h | 138 | roadtype.h |
114 | 139 | save_handler.cc | 139 | save_handler.cc |
115 | 140 | save_handler.h | 140 | save_handler.h |
116 | 141 | trade_agreement.h | ||
117 | 141 | widelands_geometry_io.cc | 142 | widelands_geometry_io.cc |
118 | 142 | widelands_geometry_io.h | 143 | widelands_geometry_io.h |
119 | 143 | map_objects/bob.cc | 144 | map_objects/bob.cc |
120 | 144 | 145 | ||
121 | === modified file 'src/logic/game.cc' | |||
122 | --- src/logic/game.cc 2017-08-30 12:01:47 +0000 | |||
123 | +++ src/logic/game.cc 2017-10-03 20:35:46 +0000 | |||
124 | @@ -49,6 +49,7 @@ | |||
125 | 49 | #include "logic/cmd_luascript.h" | 49 | #include "logic/cmd_luascript.h" |
126 | 50 | #include "logic/game_settings.h" | 50 | #include "logic/game_settings.h" |
127 | 51 | #include "logic/map_objects/tribes/carrier.h" | 51 | #include "logic/map_objects/tribes/carrier.h" |
128 | 52 | #include "logic/map_objects/tribes/market.h" | ||
129 | 52 | #include "logic/map_objects/tribes/militarysite.h" | 53 | #include "logic/map_objects/tribes/militarysite.h" |
130 | 53 | #include "logic/map_objects/tribes/ship.h" | 54 | #include "logic/map_objects/tribes/ship.h" |
131 | 54 | #include "logic/map_objects/tribes/soldier.h" | 55 | #include "logic/map_objects/tribes/soldier.h" |
132 | @@ -753,6 +754,87 @@ | |||
133 | 753 | get_gametime(), ship.get_owner()->player_number(), ship.serial())); | 754 | get_gametime(), ship.get_owner()->player_number(), ship.serial())); |
134 | 754 | } | 755 | } |
135 | 755 | 756 | ||
136 | 757 | void Game::send_player_propose_trade(const Trade& trade) { | ||
137 | 758 | auto* object = objects().get_object(trade.initiator); | ||
138 | 759 | assert(object != nullptr); | ||
139 | 760 | send_player_command( | ||
140 | 761 | *new CmdProposeTrade(get_gametime(), object->get_owner()->player_number(), trade)); | ||
141 | 762 | } | ||
142 | 763 | |||
143 | 764 | int Game::propose_trade(const Trade& trade) { | ||
144 | 765 | // TODO(sirver,trading): Check if a trade is possible (i.e. if there is a | ||
145 | 766 | // path between the two markets); | ||
146 | 767 | const int id = next_trade_agreement_id_; | ||
147 | 768 | ++next_trade_agreement_id_; | ||
148 | 769 | |||
149 | 770 | auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator)); | ||
150 | 771 | auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver)); | ||
151 | 772 | // This is only ever called through a PlayerCommand and that already made | ||
152 | 773 | // sure that the objects still exist. Since no time has passed, they should | ||
153 | 774 | // not have vanished under us. | ||
154 | 775 | assert(initiator != nullptr); | ||
155 | 776 | assert(receiver != nullptr); | ||
156 | 777 | |||
157 | 778 | receiver->removed.connect( | ||
158 | 779 | [this, id](const uint32_t /* serial */) { cancel_trade(id); }); | ||
159 | 780 | initiator->removed.connect( | ||
160 | 781 | [this, id](const uint32_t /* serial */) { cancel_trade(id); }); | ||
161 | 782 | |||
162 | 783 | receiver->send_message(*this, Message::Type::kTradeOfferReceived, _("Trade Offer"), | ||
163 | 784 | receiver->descr().icon_filename(), receiver->descr().descname(), | ||
164 | 785 | _("This market has received a new trade offer."), true); | ||
165 | 786 | trade_agreements_[id] = TradeAgreement{TradeAgreement::State::kProposed, trade}; | ||
166 | 787 | |||
167 | 788 | // TODO(sirver,trading): this should be done through another player_command, but I | ||
168 | 789 | // want to get to the trade logic implementation now. | ||
169 | 790 | accept_trade(id); | ||
170 | 791 | return id; | ||
171 | 792 | } | ||
172 | 793 | |||
173 | 794 | void Game::accept_trade(const int trade_id) { | ||
174 | 795 | auto it = trade_agreements_.find(trade_id); | ||
175 | 796 | if (it == trade_agreements_.end()) { | ||
176 | 797 | log("Game::accept_trade: Trade %d has vanished. Ignoring.\n", trade_id); | ||
177 | 798 | return; | ||
178 | 799 | } | ||
179 | 800 | const Trade& trade = it->second.trade; | ||
180 | 801 | auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator)); | ||
181 | 802 | auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver)); | ||
182 | 803 | if (initiator == nullptr || receiver == nullptr) { | ||
183 | 804 | cancel_trade(trade_id); | ||
184 | 805 | return; | ||
185 | 806 | } | ||
186 | 807 | |||
187 | 808 | initiator->new_trade(trade_id, trade.items_to_send, trade.num_batches, trade.receiver); | ||
188 | 809 | receiver->new_trade(trade_id, trade.items_to_receive, trade.num_batches, trade.initiator); | ||
189 | 810 | |||
190 | 811 | // TODO(sirver,trading): Message the users that the trade has been accepted. | ||
191 | 812 | } | ||
192 | 813 | |||
193 | 814 | void Game::cancel_trade(int trade_id) { | ||
194 | 815 | // The trade id might be long gone - since we never disconnect from the | ||
195 | 816 | // 'removed' signal of the two buildings, we might be invoked long after the | ||
196 | 817 | // trade was deleted for other reasons. | ||
197 | 818 | const auto it = trade_agreements_.find(trade_id); | ||
198 | 819 | if (it == trade_agreements_.end()) { | ||
199 | 820 | return; | ||
200 | 821 | } | ||
201 | 822 | const auto& trade = it->second.trade; | ||
202 | 823 | |||
203 | 824 | auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator)); | ||
204 | 825 | if (initiator != nullptr) { | ||
205 | 826 | initiator->cancel_trade(trade_id); | ||
206 | 827 | // TODO(sirver,trading): Send message to owner that the trade has been canceled. | ||
207 | 828 | } | ||
208 | 829 | |||
209 | 830 | auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver)); | ||
210 | 831 | if (receiver != nullptr) { | ||
211 | 832 | receiver->cancel_trade(trade_id); | ||
212 | 833 | // TODO(sirver,trading): Send message to owner that the trade has been canceled. | ||
213 | 834 | } | ||
214 | 835 | trade_agreements_.erase(trade_id); | ||
215 | 836 | } | ||
216 | 837 | |||
217 | 756 | LuaGameInterface& Game::lua() { | 838 | LuaGameInterface& Game::lua() { |
218 | 757 | return static_cast<LuaGameInterface&>(EditorGameBase::lua()); | 839 | return static_cast<LuaGameInterface&>(EditorGameBase::lua()); |
219 | 758 | } | 840 | } |
220 | 759 | 841 | ||
221 | === modified file 'src/logic/game.h' | |||
222 | --- src/logic/game.h 2017-08-18 00:17:39 +0000 | |||
223 | +++ src/logic/game.h 2017-10-03 20:35:46 +0000 | |||
224 | @@ -27,6 +27,7 @@ | |||
225 | 27 | #include "logic/cmd_queue.h" | 27 | #include "logic/cmd_queue.h" |
226 | 28 | #include "logic/editor_game_base.h" | 28 | #include "logic/editor_game_base.h" |
227 | 29 | #include "logic/save_handler.h" | 29 | #include "logic/save_handler.h" |
228 | 30 | #include "logic/trade_agreement.h" | ||
229 | 30 | #include "random/random.h" | 31 | #include "random/random.h" |
230 | 31 | #include "scripting/logic.h" | 32 | #include "scripting/logic.h" |
231 | 32 | 33 | ||
232 | @@ -207,6 +208,7 @@ | |||
233 | 207 | void send_player_ship_explore_island(Ship&, IslandExploreDirection); | 208 | void send_player_ship_explore_island(Ship&, IslandExploreDirection); |
234 | 208 | void send_player_sink_ship(Ship&); | 209 | void send_player_sink_ship(Ship&); |
235 | 209 | void send_player_cancel_expedition_ship(Ship&); | 210 | void send_player_cancel_expedition_ship(Ship&); |
236 | 211 | void send_player_propose_trade(const Trade& trade); | ||
237 | 210 | 212 | ||
238 | 211 | InteractivePlayer* get_ipl(); | 213 | InteractivePlayer* get_ipl(); |
239 | 212 | 214 | ||
240 | @@ -244,6 +246,11 @@ | |||
241 | 244 | 246 | ||
242 | 245 | void set_auto_speed(bool); | 247 | void set_auto_speed(bool); |
243 | 246 | 248 | ||
244 | 249 | // TODO(sirver,trading): document these functions once the interface settles. | ||
245 | 250 | int propose_trade(const Trade& trade); | ||
246 | 251 | void accept_trade(int trade_id); | ||
247 | 252 | void cancel_trade(int trade_id); | ||
248 | 253 | |||
249 | 247 | private: | 254 | private: |
250 | 248 | void sync_reset(); | 255 | void sync_reset(); |
251 | 249 | 256 | ||
252 | @@ -308,6 +315,9 @@ | |||
253 | 308 | std::unique_ptr<ReplayWriter> replaywriter_; | 315 | std::unique_ptr<ReplayWriter> replaywriter_; |
254 | 309 | 316 | ||
255 | 310 | GeneralStatsVector general_stats_; | 317 | GeneralStatsVector general_stats_; |
256 | 318 | int next_trade_agreement_id_ = 1; | ||
257 | 319 | // Maps from trade agreement id to the agreement. | ||
258 | 320 | std::map<int, TradeAgreement> trade_agreements_; | ||
259 | 311 | 321 | ||
260 | 312 | /// For save games and statistics generation | 322 | /// For save games and statistics generation |
261 | 313 | std::string win_condition_displayname_; | 323 | std::string win_condition_displayname_; |
262 | 314 | 324 | ||
263 | === modified file 'src/logic/map_objects/bob.cc' | |||
264 | --- src/logic/map_objects/bob.cc 2017-08-20 08:34:02 +0000 | |||
265 | +++ src/logic/map_objects/bob.cc 2017-10-03 20:35:46 +0000 | |||
266 | @@ -390,6 +390,10 @@ | |||
267 | 390 | state.ivar1 = 0; | 390 | state.ivar1 = 0; |
268 | 391 | } | 391 | } |
269 | 392 | 392 | ||
270 | 393 | bool Bob::is_idle() { | ||
271 | 394 | return get_state(taskIdle); | ||
272 | 395 | } | ||
273 | 396 | |||
274 | 393 | /** | 397 | /** |
275 | 394 | * Move along a predefined path. | 398 | * Move along a predefined path. |
276 | 395 | * \par ivar1 the step number. | 399 | * \par ivar1 the step number. |
277 | 396 | 400 | ||
278 | === modified file 'src/logic/map_objects/bob.h' | |||
279 | --- src/logic/map_objects/bob.h 2017-06-24 08:47:46 +0000 | |||
280 | +++ src/logic/map_objects/bob.h 2017-10-03 20:35:46 +0000 | |||
281 | @@ -278,6 +278,7 @@ | |||
282 | 278 | // TODO(feature-Hasi50): correct (?) Send a signal that may switch to some other \ref Task | 278 | // TODO(feature-Hasi50): correct (?) Send a signal that may switch to some other \ref Task |
283 | 279 | void send_signal(Game&, char const*); | 279 | void send_signal(Game&, char const*); |
284 | 280 | void start_task_idle(Game&, uint32_t anim, int32_t timeout); | 280 | void start_task_idle(Game&, uint32_t anim, int32_t timeout); |
285 | 281 | bool is_idle(); | ||
286 | 281 | 282 | ||
287 | 282 | /// This can fail (and return false). Therefore the caller must check the | 283 | /// This can fail (and return false). Therefore the caller must check the |
288 | 283 | /// result and find something else for the bob to do. Otherwise there will | 284 | /// result and find something else for the bob to do. Otherwise there will |
289 | 284 | 285 | ||
290 | === modified file 'src/logic/map_objects/tribes/market.cc' | |||
291 | --- src/logic/map_objects/tribes/market.cc 2017-09-18 13:43:08 +0000 | |||
292 | +++ src/logic/map_objects/tribes/market.cc 2017-10-03 20:35:46 +0000 | |||
293 | @@ -19,8 +19,11 @@ | |||
294 | 19 | 19 | ||
295 | 20 | #include "logic/map_objects/tribes/market.h" | 20 | #include "logic/map_objects/tribes/market.h" |
296 | 21 | 21 | ||
297 | 22 | #include <memory> | ||
298 | 23 | |||
299 | 22 | #include "base/i18n.h" | 24 | #include "base/i18n.h" |
300 | 23 | #include "logic/map_objects/tribes/productionsite.h" | 25 | #include "logic/map_objects/tribes/productionsite.h" |
301 | 26 | #include "logic/map_objects/tribes/tribes.h" | ||
302 | 24 | 27 | ||
303 | 25 | namespace Widelands { | 28 | namespace Widelands { |
304 | 26 | 29 | ||
305 | @@ -30,7 +33,12 @@ | |||
306 | 30 | : BuildingDescr(init_descname, MapObjectType::MARKET, table, egbase) { | 33 | : BuildingDescr(init_descname, MapObjectType::MARKET, table, egbase) { |
307 | 31 | i18n::Textdomain td("tribes"); | 34 | i18n::Textdomain td("tribes"); |
308 | 32 | 35 | ||
310 | 33 | parse_working_positions(egbase, table.get_table("working_positions").get(), &working_positions_); | 36 | DescriptionIndex const woi = egbase.tribes().worker_index(table.get_string("carrier")); |
311 | 37 | if (!egbase.tribes().worker_exists(woi)) { | ||
312 | 38 | throw wexception("The tribe does not define the worker in 'carrier'."); | ||
313 | 39 | } | ||
314 | 40 | carrier_ = woi; | ||
315 | 41 | |||
316 | 34 | // TODO(sirver,trading): Add actual logic here. | 42 | // TODO(sirver,trading): Add actual logic here. |
317 | 35 | } | 43 | } |
318 | 36 | 44 | ||
319 | @@ -38,10 +46,223 @@ | |||
320 | 38 | return *new Market(*this); | 46 | return *new Market(*this); |
321 | 39 | } | 47 | } |
322 | 40 | 48 | ||
323 | 49 | int Market::TradeOrder::num_wares_per_batch() const { | ||
324 | 50 | int sum = 0; | ||
325 | 51 | for (const auto& item_pair : items) { | ||
326 | 52 | sum += item_pair.second; | ||
327 | 53 | } | ||
328 | 54 | return sum; | ||
329 | 55 | } | ||
330 | 56 | |||
331 | 57 | bool Market::TradeOrder::fulfilled() const { | ||
332 | 58 | return num_shipped_batches == initial_num_batches; | ||
333 | 59 | } | ||
334 | 60 | |||
335 | 61 | // TODO(sirver,trading): This needs to implement 'set_economy'. Maybe common code can be shared. | ||
336 | 41 | Market::Market(const MarketDescr& the_descr) : Building(the_descr) { | 62 | Market::Market(const MarketDescr& the_descr) : Building(the_descr) { |
337 | 42 | } | 63 | } |
338 | 43 | 64 | ||
339 | 44 | Market::~Market() { | 65 | Market::~Market() { |
340 | 45 | } | 66 | } |
341 | 46 | 67 | ||
342 | 68 | void Market::new_trade(const int trade_id, | ||
343 | 69 | const BillOfMaterials& items, | ||
344 | 70 | const int num_batches, | ||
345 | 71 | const Serial other_side) { | ||
346 | 72 | trade_orders_[trade_id] = TradeOrder{items, num_batches, 0, other_side, 0, nullptr, {}}; | ||
347 | 73 | auto& trade_order = trade_orders_[trade_id]; | ||
348 | 74 | |||
349 | 75 | // Request one worker for each item in a batch. | ||
350 | 76 | trade_order.worker_request.reset( | ||
351 | 77 | new Request(*this, descr().carrier(), Market::worker_arrived_callback, wwWORKER)); | ||
352 | 78 | trade_order.worker_request->set_count(trade_order.num_wares_per_batch()); | ||
353 | 79 | |||
354 | 80 | // Make sure we have a wares queue for each item in this. | ||
355 | 81 | for (const auto& entry : items) { | ||
356 | 82 | ensure_wares_queue_exists(entry.first); | ||
357 | 83 | } | ||
358 | 84 | } | ||
359 | 85 | |||
360 | 86 | void Market::cancel_trade(const int trade_id) { | ||
361 | 87 | // TODO(sirver,trading): Launch workers, release no longer required wares and delete now unneeded 'WaresQueue's | ||
362 | 88 | trade_orders_.erase(trade_id); | ||
363 | 89 | } | ||
364 | 90 | |||
365 | 91 | void Market::worker_arrived_callback( | ||
366 | 92 | Game& game, Request& rq, DescriptionIndex /* widx */, Worker* const w, PlayerImmovable& target) { | ||
367 | 93 | auto& market = dynamic_cast<Market&>(target); | ||
368 | 94 | |||
369 | 95 | assert(w); | ||
370 | 96 | assert(w->get_location(game) == &market); | ||
371 | 97 | |||
372 | 98 | for (auto& trade_order_pair : market.trade_orders_) { | ||
373 | 99 | auto& trade_order = trade_order_pair.second; | ||
374 | 100 | if (trade_order.worker_request.get() != &rq) { | ||
375 | 101 | continue; | ||
376 | 102 | } | ||
377 | 103 | |||
378 | 104 | if (rq.get_count() == 0) { | ||
379 | 105 | // Erase this request. | ||
380 | 106 | trade_order.worker_request.reset(); | ||
381 | 107 | } | ||
382 | 108 | w->start_task_idle(game, 0, -1); | ||
383 | 109 | trade_order.workers.push_back(w); | ||
384 | 110 | Notifications::publish(NoteBuilding(market.serial(), NoteBuilding::Action::kWorkersChanged)); | ||
385 | 111 | market.try_launching_batch(&game); | ||
386 | 112 | return; | ||
387 | 113 | } | ||
388 | 114 | NEVER_HERE(); // We should have found and handled a match by now. | ||
389 | 115 | } | ||
390 | 116 | |||
391 | 117 | void Market::ware_arrived_callback(Game& g, InputQueue*, DescriptionIndex, Worker*, void* data) { | ||
392 | 118 | Market& market = *static_cast<Market*>(data); | ||
393 | 119 | market.try_launching_batch(&g); | ||
394 | 120 | } | ||
395 | 121 | |||
396 | 122 | void Market::try_launching_batch(Game* game) { | ||
397 | 123 | for (auto& pair : trade_orders_) { | ||
398 | 124 | if (!is_ready_to_launch_batch(pair.first)) { | ||
399 | 125 | continue; | ||
400 | 126 | } | ||
401 | 127 | |||
402 | 128 | auto* other_market = | ||
403 | 129 | dynamic_cast<Market*>(game->objects().get_object(pair.second.other_side)); | ||
404 | 130 | if (other_market == nullptr) { | ||
405 | 131 | // TODO(sirver,trading): Can this even happen? Where is this function called from? | ||
406 | 132 | // The other market seems to have vanished. The game tracks this and | ||
407 | 133 | // should soon delete this trade request from us. We just ignore it. | ||
408 | 134 | continue; | ||
409 | 135 | } | ||
410 | 136 | if (!other_market->is_ready_to_launch_batch(pair.first)) { | ||
411 | 137 | continue; | ||
412 | 138 | } | ||
413 | 139 | launch_batch(pair.first, game); | ||
414 | 140 | other_market->launch_batch(pair.first, game); | ||
415 | 141 | break; | ||
416 | 142 | } | ||
417 | 143 | } | ||
418 | 144 | |||
419 | 145 | bool Market::is_ready_to_launch_batch(const int trade_id) { | ||
420 | 146 | const auto it = trade_orders_.find(trade_id); | ||
421 | 147 | if (it == trade_orders_.end()) { | ||
422 | 148 | return false; | ||
423 | 149 | } | ||
424 | 150 | auto& trade_order = it->second; | ||
425 | 151 | assert(!trade_order.fulfilled()); | ||
426 | 152 | |||
427 | 153 | // Do we have all necessary wares for a batch? | ||
428 | 154 | for (const auto& item_pair : trade_order.items) { | ||
429 | 155 | const auto wares_it = wares_queue_.find(item_pair.first); | ||
430 | 156 | if (wares_it == wares_queue_.end()) { | ||
431 | 157 | return false; | ||
432 | 158 | } | ||
433 | 159 | if (wares_it->second->get_filled() < item_pair.second) { | ||
434 | 160 | return false; | ||
435 | 161 | } | ||
436 | 162 | } | ||
437 | 163 | |||
438 | 164 | // Do we have enough people to carry wares? | ||
439 | 165 | int num_available_carriers = 0; | ||
440 | 166 | for (auto* worker : trade_order.workers) { | ||
441 | 167 | num_available_carriers += worker->is_idle() ? 1 : 0; | ||
442 | 168 | } | ||
443 | 169 | return num_available_carriers == trade_order.num_wares_per_batch(); | ||
444 | 170 | } | ||
445 | 171 | |||
446 | 172 | void Market::launch_batch(const int trade_id, Game* game) { | ||
447 | 173 | assert(is_ready_to_launch_batch(trade_id)); | ||
448 | 174 | auto& trade_order = trade_orders_.at(trade_id); | ||
449 | 175 | |||
450 | 176 | // Do we have all necessary wares for a batch? | ||
451 | 177 | int worker_index = 0; | ||
452 | 178 | for (const auto& item_pair : trade_order.items) { | ||
453 | 179 | for (size_t i = 0; i < item_pair.second; ++i) { | ||
454 | 180 | Worker* carrier = trade_order.workers.at(worker_index); | ||
455 | 181 | ++worker_index; | ||
456 | 182 | assert(carrier->is_idle()); | ||
457 | 183 | |||
458 | 184 | // Give the carrier a ware. | ||
459 | 185 | WareInstance* ware = | ||
460 | 186 | new WareInstance(item_pair.first, game->tribes().get_ware_descr(item_pair.first)); | ||
461 | 187 | ware->init(*game); | ||
462 | 188 | carrier->set_carried_ware(*game, ware); | ||
463 | 189 | |||
464 | 190 | // We have to remove this item from our economy. Otherwise it would be | ||
465 | 191 | // considered idle (since it has no transport associated with it) and | ||
466 | 192 | // the engine would want to transfer it to the next warehouse. | ||
467 | 193 | ware->set_economy(nullptr); | ||
468 | 194 | wares_queue_.at(item_pair.first) | ||
469 | 195 | ->set_filled(wares_queue_.at(item_pair.first)->get_filled() - 1); | ||
470 | 196 | |||
471 | 197 | // Send the carrier going. | ||
472 | 198 | carrier->reset_tasks(*game); | ||
473 | 199 | carrier->start_task_carry_trade_item( | ||
474 | 200 | *game, trade_id, ObjectPointer(game->objects().get_object(trade_order.other_side))); | ||
475 | 201 | } | ||
476 | 202 | } | ||
477 | 203 | } | ||
478 | 204 | |||
479 | 205 | void Market::ensure_wares_queue_exists(int ware_index) { | ||
480 | 206 | if (wares_queue_.count(ware_index) > 0) { | ||
481 | 207 | return; | ||
482 | 208 | } | ||
483 | 209 | wares_queue_[ware_index] = | ||
484 | 210 | std::unique_ptr<WaresQueue>(new WaresQueue(*this, ware_index, kMaxPerItemTradeBatchSize)); | ||
485 | 211 | wares_queue_[ware_index]->set_callback(Market::ware_arrived_callback, this); | ||
486 | 212 | } | ||
487 | 213 | |||
488 | 214 | InputQueue& Market::inputqueue(DescriptionIndex index, WareWorker ware_worker) { | ||
489 | 215 | assert(ware_worker == wwWARE); | ||
490 | 216 | auto it = wares_queue_.find(index); | ||
491 | 217 | if (it != wares_queue_.end()) { | ||
492 | 218 | return *it->second; | ||
493 | 219 | } | ||
494 | 220 | // The parent will throw an exception. | ||
495 | 221 | return Building::inputqueue(index, ware_worker); | ||
496 | 222 | } | ||
497 | 223 | |||
498 | 224 | void Market::cleanup(EditorGameBase& egbase) { | ||
499 | 225 | for (auto& pair : wares_queue_) { | ||
500 | 226 | pair.second->cleanup(); | ||
501 | 227 | } | ||
502 | 228 | Building::cleanup(egbase); | ||
503 | 229 | } | ||
504 | 230 | |||
505 | 231 | void Market::traded_ware_arrived(const int trade_id, const DescriptionIndex ware_index, Game* game) { | ||
506 | 232 | auto& trade_order = trade_orders_.at(trade_id); | ||
507 | 233 | |||
508 | 234 | WareInstance* ware = new WareInstance(ware_index, game->tribes().get_ware_descr(ware_index)); | ||
509 | 235 | ware->init(*game); | ||
510 | 236 | |||
511 | 237 | // TODO(sirver,trading): This is a hack. We should have a worker that | ||
512 | 238 | // carriers stuff out. At the moment this assumes this market is barbarians | ||
513 | 239 | // (which is always correct right now), creates a carrier for each received | ||
514 | 240 | // ware to drop it off. The carrier then leaves the building and goes home. | ||
515 | 241 | const WorkerDescr& w_desc = | ||
516 | 242 | *game->tribes().get_worker_descr(game->tribes().worker_index("barbarians_carrier")); | ||
517 | 243 | auto& worker = w_desc.create(*game, owner(), this, position_); | ||
518 | 244 | worker.start_task_dropoff(*game, *ware); | ||
519 | 245 | trade_order.received_traded_wares_in_this_batch += 1; | ||
520 | 246 | owner().ware_produced(ware_index); | ||
521 | 247 | |||
522 | 248 | auto* other_market = dynamic_cast<Market*>(game->objects().get_object(trade_order.other_side)); | ||
523 | 249 | assert(other_market != nullptr); | ||
524 | 250 | other_market->owner().ware_consumed(ware_index, 1); | ||
525 | 251 | auto& other_trade_order = other_market->trade_orders_.at(trade_id); | ||
526 | 252 | if (trade_order.received_traded_wares_in_this_batch == other_trade_order.num_wares_per_batch() && | ||
527 | 253 | other_trade_order.received_traded_wares_in_this_batch == trade_order.num_wares_per_batch()) { | ||
528 | 254 | // This batch is completed. | ||
529 | 255 | ++trade_order.num_shipped_batches; | ||
530 | 256 | trade_order.received_traded_wares_in_this_batch = 0; | ||
531 | 257 | ++other_trade_order.num_shipped_batches; | ||
532 | 258 | other_trade_order.received_traded_wares_in_this_batch = 0; | ||
533 | 259 | if (trade_order.fulfilled()) { | ||
534 | 260 | assert(other_trade_order.fulfilled()); | ||
535 | 261 | // TODO(sirver,trading): This is not quite correct. This should for | ||
536 | 262 | // example send a differnet message than actually canceling a trade. | ||
537 | 263 | game->cancel_trade(trade_id); | ||
538 | 264 | } | ||
539 | 265 | } | ||
540 | 266 | } | ||
541 | 267 | |||
542 | 47 | } // namespace Widelands | 268 | } // namespace Widelands |
543 | 48 | 269 | ||
544 | === modified file 'src/logic/map_objects/tribes/market.h' | |||
545 | --- src/logic/map_objects/tribes/market.h 2017-09-15 12:33:58 +0000 | |||
546 | +++ src/logic/map_objects/tribes/market.h 2017-10-03 20:35:46 +0000 | |||
547 | @@ -20,6 +20,10 @@ | |||
548 | 20 | #ifndef WL_LOGIC_MAP_OBJECTS_TRIBES_MARKET_H | 20 | #ifndef WL_LOGIC_MAP_OBJECTS_TRIBES_MARKET_H |
549 | 21 | #define WL_LOGIC_MAP_OBJECTS_TRIBES_MARKET_H | 21 | #define WL_LOGIC_MAP_OBJECTS_TRIBES_MARKET_H |
550 | 22 | 22 | ||
551 | 23 | #include <memory> | ||
552 | 24 | |||
553 | 25 | #include "economy/request.h" | ||
554 | 26 | #include "economy/wares_queue.h" | ||
555 | 23 | #include "logic/map_objects/tribes/building.h" | 27 | #include "logic/map_objects/tribes/building.h" |
556 | 24 | 28 | ||
557 | 25 | namespace Widelands { | 29 | namespace Widelands { |
558 | @@ -32,8 +36,10 @@ | |||
559 | 32 | 36 | ||
560 | 33 | Building& create_object() const override; | 37 | Building& create_object() const override; |
561 | 34 | 38 | ||
562 | 39 | DescriptionIndex carrier() const { return carrier_; } | ||
563 | 40 | |||
564 | 35 | private: | 41 | private: |
566 | 36 | BillOfMaterials working_positions_; | 42 | DescriptionIndex carrier_; |
567 | 37 | }; | 43 | }; |
568 | 38 | 44 | ||
569 | 39 | class Market : public Building { | 45 | class Market : public Building { |
570 | @@ -42,7 +48,48 @@ | |||
571 | 42 | explicit Market(const MarketDescr& descr); | 48 | explicit Market(const MarketDescr& descr); |
572 | 43 | ~Market() override; | 49 | ~Market() override; |
573 | 44 | 50 | ||
574 | 51 | void new_trade(int trade_id, const BillOfMaterials& items, int num_batches, Serial other_side); | ||
575 | 52 | void cancel_trade(int trade_id); | ||
576 | 53 | |||
577 | 54 | InputQueue& inputqueue(DescriptionIndex, WareWorker) override; | ||
578 | 55 | void cleanup(EditorGameBase&) override; | ||
579 | 56 | |||
580 | 57 | void try_launching_batch(Game* game); | ||
581 | 58 | void traded_ware_arrived(int trade_id, DescriptionIndex ware_index, Game* game); | ||
582 | 59 | |||
583 | 45 | private: | 60 | private: |
584 | 61 | struct TradeOrder { | ||
585 | 62 | BillOfMaterials items; | ||
586 | 63 | int initial_num_batches; | ||
587 | 64 | int num_shipped_batches; | ||
588 | 65 | Serial other_side; | ||
589 | 66 | |||
590 | 67 | int received_traded_wares_in_this_batch; | ||
591 | 68 | |||
592 | 69 | // The invariant here is that worker.size() + worker_request.get_count() | ||
593 | 70 | // == 'num_wares_per_batch()' | ||
594 | 71 | std::unique_ptr<Request> worker_request; | ||
595 | 72 | std::vector<Worker*> workers; | ||
596 | 73 | |||
597 | 74 | // The number of individual wares in 'items', i.e. the sum of all '.second's. | ||
598 | 75 | int num_wares_per_batch() const; | ||
599 | 76 | |||
600 | 77 | // True if the 'num_shipped_batches' equals the 'initial_num_batches' | ||
601 | 78 | bool fulfilled() const; | ||
602 | 79 | }; | ||
603 | 80 | |||
604 | 81 | static void | ||
605 | 82 | worker_arrived_callback(Game&, Request&, DescriptionIndex, Worker*, PlayerImmovable&); | ||
606 | 83 | static void | ||
607 | 84 | ware_arrived_callback(Game& g, InputQueue* q, DescriptionIndex ware, Worker* worker, void* data); | ||
608 | 85 | |||
609 | 86 | void ensure_wares_queue_exists(int ware_index); | ||
610 | 87 | bool is_ready_to_launch_batch(int trade_id); | ||
611 | 88 | void launch_batch(int trade_id, Game* game); | ||
612 | 89 | |||
613 | 90 | std::map<int, TradeOrder> trade_orders_; // Key is 'trade_id's. | ||
614 | 91 | std::map<int, std::unique_ptr<WaresQueue>> wares_queue_; // Key is 'ware_index'. | ||
615 | 92 | |||
616 | 46 | DISALLOW_COPY_AND_ASSIGN(Market); | 93 | DISALLOW_COPY_AND_ASSIGN(Market); |
617 | 47 | }; | 94 | }; |
618 | 48 | 95 | ||
619 | 49 | 96 | ||
620 | === modified file 'src/logic/map_objects/tribes/productionsite.cc' | |||
621 | --- src/logic/map_objects/tribes/productionsite.cc 2017-09-15 12:33:58 +0000 | |||
622 | +++ src/logic/map_objects/tribes/productionsite.cc 2017-10-03 20:35:46 +0000 | |||
623 | @@ -49,8 +49,9 @@ | |||
624 | 49 | 49 | ||
625 | 50 | constexpr size_t STATISTICS_VECTOR_LENGTH = 20; | 50 | constexpr size_t STATISTICS_VECTOR_LENGTH = 20; |
626 | 51 | 51 | ||
629 | 52 | } // namespace | 52 | // Parses the descriptions of the working positions from 'items_table' and |
630 | 53 | 53 | // fills in 'working_positions'. Throws an error if the table contains invalid | |
631 | 54 | // values. | ||
632 | 54 | void parse_working_positions(const EditorGameBase& egbase, | 55 | void parse_working_positions(const EditorGameBase& egbase, |
633 | 55 | LuaTable* items_table, | 56 | LuaTable* items_table, |
634 | 56 | BillOfMaterials* working_positions) { | 57 | BillOfMaterials* working_positions) { |
635 | @@ -71,6 +72,8 @@ | |||
636 | 71 | } | 72 | } |
637 | 72 | } | 73 | } |
638 | 73 | 74 | ||
639 | 75 | } // namespace | ||
640 | 76 | |||
641 | 74 | /* | 77 | /* |
642 | 75 | ============================================================================== | 78 | ============================================================================== |
643 | 76 | 79 | ||
644 | 77 | 80 | ||
645 | === modified file 'src/logic/map_objects/tribes/productionsite.h' | |||
646 | --- src/logic/map_objects/tribes/productionsite.h 2017-09-18 13:43:08 +0000 | |||
647 | +++ src/logic/map_objects/tribes/productionsite.h 2017-10-03 20:35:46 +0000 | |||
648 | @@ -375,13 +375,6 @@ | |||
649 | 375 | } | 375 | } |
650 | 376 | }; | 376 | }; |
651 | 377 | 377 | ||
652 | 378 | // Parses the descriptions of the working positions from 'items_table' and | ||
653 | 379 | // fills in 'working_positions'. Throws an error if the table contains invalid | ||
654 | 380 | // values. | ||
655 | 381 | void parse_working_positions(const EditorGameBase& egbase, | ||
656 | 382 | LuaTable* items_table, | ||
657 | 383 | BillOfMaterials* working_positions); | ||
658 | 384 | |||
659 | 385 | } // namespace Widelands | 378 | } // namespace Widelands |
660 | 386 | 379 | ||
661 | 387 | #endif // end of include guard: WL_LOGIC_MAP_OBJECTS_TRIBES_PRODUCTIONSITE_H | 380 | #endif // end of include guard: WL_LOGIC_MAP_OBJECTS_TRIBES_PRODUCTIONSITE_H |
662 | 388 | 381 | ||
663 | === modified file 'src/logic/map_objects/tribes/tribes.cc' | |||
664 | --- src/logic/map_objects/tribes/tribes.cc 2017-09-15 12:33:58 +0000 | |||
665 | +++ src/logic/map_objects/tribes/tribes.cc 2017-10-03 20:35:46 +0000 | |||
666 | @@ -24,6 +24,7 @@ | |||
667 | 24 | #include "base/wexception.h" | 24 | #include "base/wexception.h" |
668 | 25 | #include "graphic/graphic.h" | 25 | #include "graphic/graphic.h" |
669 | 26 | #include "logic/game_data_error.h" | 26 | #include "logic/game_data_error.h" |
670 | 27 | #include "logic/map_objects/tribes/market.h" | ||
671 | 27 | 28 | ||
672 | 28 | namespace Widelands { | 29 | namespace Widelands { |
673 | 29 | 30 | ||
674 | 30 | 31 | ||
675 | === modified file 'src/logic/map_objects/tribes/tribes.h' | |||
676 | --- src/logic/map_objects/tribes/tribes.h 2017-09-15 12:33:58 +0000 | |||
677 | +++ src/logic/map_objects/tribes/tribes.h 2017-10-03 20:35:46 +0000 | |||
678 | @@ -38,7 +38,6 @@ | |||
679 | 38 | #include "logic/map_objects/tribes/tribe_descr.h" | 38 | #include "logic/map_objects/tribes/tribe_descr.h" |
680 | 39 | #include "logic/map_objects/tribes/ware_descr.h" | 39 | #include "logic/map_objects/tribes/ware_descr.h" |
681 | 40 | #include "logic/map_objects/tribes/warehouse.h" | 40 | #include "logic/map_objects/tribes/warehouse.h" |
682 | 41 | #include "logic/map_objects/tribes/market.h" | ||
683 | 42 | #include "logic/map_objects/tribes/worker_descr.h" | 41 | #include "logic/map_objects/tribes/worker_descr.h" |
684 | 43 | #include "scripting/lua_table.h" | 42 | #include "scripting/lua_table.h" |
685 | 44 | 43 | ||
686 | 45 | 44 | ||
687 | === modified file 'src/logic/map_objects/tribes/worker.cc' | |||
688 | --- src/logic/map_objects/tribes/worker.cc 2017-08-20 17:45:42 +0000 | |||
689 | +++ src/logic/map_objects/tribes/worker.cc 2017-10-03 20:35:46 +0000 | |||
690 | @@ -47,6 +47,7 @@ | |||
691 | 47 | #include "logic/map_objects/terrain_affinity.h" | 47 | #include "logic/map_objects/terrain_affinity.h" |
692 | 48 | #include "logic/map_objects/tribes/carrier.h" | 48 | #include "logic/map_objects/tribes/carrier.h" |
693 | 49 | #include "logic/map_objects/tribes/dismantlesite.h" | 49 | #include "logic/map_objects/tribes/dismantlesite.h" |
694 | 50 | #include "logic/map_objects/tribes/market.h" | ||
695 | 50 | #include "logic/map_objects/tribes/soldier.h" | 51 | #include "logic/map_objects/tribes/soldier.h" |
696 | 51 | #include "logic/map_objects/tribes/tribe_descr.h" | 52 | #include "logic/map_objects/tribes/tribe_descr.h" |
697 | 52 | #include "logic/map_objects/tribes/warehouse.h" | 53 | #include "logic/map_objects/tribes/warehouse.h" |
698 | @@ -1594,6 +1595,88 @@ | |||
699 | 1594 | send_signal(game, "update"); | 1595 | send_signal(game, "update"); |
700 | 1595 | } | 1596 | } |
701 | 1596 | 1597 | ||
702 | 1598 | // The task when a worker is part of the caravan that is trading items. | ||
703 | 1599 | const Bob::Task Worker::taskCarryTradeItem = { | ||
704 | 1600 | "carry_trade_item", static_cast<Bob::Ptr>(&Worker::carry_trade_item_update), nullptr, nullptr, true}; | ||
705 | 1601 | |||
706 | 1602 | void Worker::start_task_carry_trade_item(Game& game, | ||
707 | 1603 | const int trade_id, | ||
708 | 1604 | ObjectPointer other_market) { | ||
709 | 1605 | push_task(game, taskCarryTradeItem); | ||
710 | 1606 | auto& state = top_state(); | ||
711 | 1607 | state.ivar1 = 0; | ||
712 | 1608 | state.ivar2 = trade_id; | ||
713 | 1609 | state.objvar1 = other_market; | ||
714 | 1610 | } | ||
715 | 1611 | |||
716 | 1612 | // This is a state machine: leave building, go to the other market, drop off | ||
717 | 1613 | // wares, and return. | ||
718 | 1614 | void Worker::carry_trade_item_update(Game& game, State& state) { | ||
719 | 1615 | // Reset any signals that are not related to location | ||
720 | 1616 | std::string signal = get_signal(); | ||
721 | 1617 | signal_handled(); | ||
722 | 1618 | if (!signal.empty()) { | ||
723 | 1619 | // TODO(sirver,trading): Remove once signals are correctly handled. | ||
724 | 1620 | log("carry_trade_item_update: signal received: %s\n", signal.c_str()); | ||
725 | 1621 | } | ||
726 | 1622 | if (signal == "evict") { | ||
727 | 1623 | return pop_task(game); | ||
728 | 1624 | } | ||
729 | 1625 | |||
730 | 1626 | // First of all, make sure we're outside | ||
731 | 1627 | if (state.ivar1 == 0) { | ||
732 | 1628 | start_task_leavebuilding(game, false); | ||
733 | 1629 | ++state.ivar1; | ||
734 | 1630 | return; | ||
735 | 1631 | } | ||
736 | 1632 | |||
737 | 1633 | auto* other_market = dynamic_cast<Market*>(state.objvar1.get(game)); | ||
738 | 1634 | if (state.ivar1 == 1) { | ||
739 | 1635 | // Arrived on site. Move to the building and advance our state. | ||
740 | 1636 | if (other_market->base_flag().get_position() == get_position()) { | ||
741 | 1637 | ++state.ivar1; | ||
742 | 1638 | return start_task_move( | ||
743 | 1639 | game, WALK_NW, descr().get_right_walk_anims(does_carry_ware()), true); | ||
744 | 1640 | } | ||
745 | 1641 | |||
746 | 1642 | // Otherwise continue making progress towards the other market. | ||
747 | 1643 | if (!start_task_movepath(game, other_market->base_flag().get_position(), 5, | ||
748 | 1644 | descr().get_right_walk_anims(does_carry_ware()))) { | ||
749 | 1645 | molog("carry_trade_item_update: Could not move to other flag.\n"); | ||
750 | 1646 | // TODO(sirver,trading): something needs to happen here. | ||
751 | 1647 | } | ||
752 | 1648 | return; | ||
753 | 1649 | } | ||
754 | 1650 | |||
755 | 1651 | if (state.ivar1 == 2) { | ||
756 | 1652 | WareInstance* const ware = fetch_carried_ware(game); | ||
757 | 1653 | other_market->traded_ware_arrived(state.ivar2, ware->descr_index(), &game); | ||
758 | 1654 | ware->remove(game); | ||
759 | 1655 | ++state.ivar1; | ||
760 | 1656 | start_task_move(game, WALK_SE, descr().get_right_walk_anims(does_carry_ware()), true); | ||
761 | 1657 | return; | ||
762 | 1658 | } | ||
763 | 1659 | |||
764 | 1660 | if (state.ivar1 == 3) { | ||
765 | 1661 | ++state.ivar1; | ||
766 | 1662 | start_task_return(game, false); | ||
767 | 1663 | return; | ||
768 | 1664 | } | ||
769 | 1665 | |||
770 | 1666 | if (state.ivar1 == 4) { | ||
771 | 1667 | pop_task(game); | ||
772 | 1668 | start_task_idle(game, 0, -1); | ||
773 | 1669 | dynamic_cast<Market*>(get_location(game))->try_launching_batch(&game); | ||
774 | 1670 | return; | ||
775 | 1671 | } | ||
776 | 1672 | NEVER_HERE(); | ||
777 | 1673 | } | ||
778 | 1674 | |||
779 | 1675 | void Worker::update_task_carry_trade_item(Game& game) { | ||
780 | 1676 | if (top_state().task == &taskCarryTradeItem) | ||
781 | 1677 | send_signal(game, "update"); | ||
782 | 1678 | } | ||
783 | 1679 | |||
784 | 1597 | /** | 1680 | /** |
785 | 1598 | * Evict the worker from its current building. | 1681 | * Evict the worker from its current building. |
786 | 1599 | */ | 1682 | */ |
787 | 1600 | 1683 | ||
788 | === modified file 'src/logic/map_objects/tribes/worker.h' | |||
789 | --- src/logic/map_objects/tribes/worker.h 2017-06-24 08:47:46 +0000 | |||
790 | +++ src/logic/map_objects/tribes/worker.h 2017-10-03 20:35:46 +0000 | |||
791 | @@ -169,6 +169,9 @@ | |||
792 | 169 | void start_task_leavebuilding(Game&, bool changelocation); | 169 | void start_task_leavebuilding(Game&, bool changelocation); |
793 | 170 | void start_task_fugitive(Game&); | 170 | void start_task_fugitive(Game&); |
794 | 171 | 171 | ||
795 | 172 | void start_task_carry_trade_item(Game& game, int trade_id, ObjectPointer other_market); | ||
796 | 173 | void update_task_carry_trade_item(Game&); | ||
797 | 174 | |||
798 | 172 | void | 175 | void |
799 | 173 | start_task_geologist(Game&, uint8_t attempts, uint8_t radius, const std::string& subcommand); | 176 | start_task_geologist(Game&, uint8_t attempts, uint8_t radius, const std::string& subcommand); |
800 | 174 | 177 | ||
801 | @@ -208,6 +211,7 @@ | |||
802 | 208 | static const Task taskFugitive; | 211 | static const Task taskFugitive; |
803 | 209 | static const Task taskGeologist; | 212 | static const Task taskGeologist; |
804 | 210 | static const Task taskScout; | 213 | static const Task taskScout; |
805 | 214 | static const Task taskCarryTradeItem; | ||
806 | 211 | 215 | ||
807 | 212 | private: | 216 | private: |
808 | 213 | // task details | 217 | // task details |
809 | @@ -232,6 +236,7 @@ | |||
810 | 232 | void fugitive_update(Game&, State&); | 236 | void fugitive_update(Game&, State&); |
811 | 233 | void geologist_update(Game&, State&); | 237 | void geologist_update(Game&, State&); |
812 | 234 | void scout_update(Game&, State&); | 238 | void scout_update(Game&, State&); |
813 | 239 | void carry_trade_item_update(Game&, State&); | ||
814 | 235 | 240 | ||
815 | 236 | // Program commands | 241 | // Program commands |
816 | 237 | bool run_mine(Game&, State&, const Action&); | 242 | bool run_mine(Game&, State&, const Action&); |
817 | 238 | 243 | ||
818 | === modified file 'src/logic/message.h' | |||
819 | --- src/logic/message.h 2017-02-19 16:56:59 +0000 | |||
820 | +++ src/logic/message.h 2017-10-03 20:35:46 +0000 | |||
821 | @@ -48,7 +48,8 @@ | |||
822 | 48 | kWarfare, // everything starting from here is warfare | 48 | kWarfare, // everything starting from here is warfare |
823 | 49 | kWarfareSiteDefeated, | 49 | kWarfareSiteDefeated, |
824 | 50 | kWarfareSiteLost, | 50 | kWarfareSiteLost, |
826 | 51 | kWarfareUnderAttack | 51 | kWarfareUnderAttack, |
827 | 52 | kTradeOfferReceived, | ||
828 | 52 | }; | 53 | }; |
829 | 53 | 54 | ||
830 | 54 | /** | 55 | /** |
831 | 55 | 56 | ||
832 | === modified file 'src/logic/playercommand.cc' | |||
833 | --- src/logic/playercommand.cc 2017-06-25 21:56:53 +0000 | |||
834 | +++ src/logic/playercommand.cc 2017-10-03 20:35:46 +0000 | |||
835 | @@ -29,6 +29,7 @@ | |||
836 | 29 | #include "io/streamwrite.h" | 29 | #include "io/streamwrite.h" |
837 | 30 | #include "logic/game.h" | 30 | #include "logic/game.h" |
838 | 31 | #include "logic/map_objects/map_object.h" | 31 | #include "logic/map_objects/map_object.h" |
839 | 32 | #include "logic/map_objects/tribes/market.h" | ||
840 | 32 | #include "logic/map_objects/tribes/soldier.h" | 33 | #include "logic/map_objects/tribes/soldier.h" |
841 | 33 | #include "logic/map_objects/tribes/tribe_descr.h" | 34 | #include "logic/map_objects/tribes/tribe_descr.h" |
842 | 34 | #include "logic/player.h" | 35 | #include "logic/player.h" |
843 | @@ -52,6 +53,25 @@ | |||
844 | 52 | return mol.get<T>(object_index).serial(); | 53 | return mol.get<T>(object_index).serial(); |
845 | 53 | } | 54 | } |
846 | 54 | 55 | ||
847 | 56 | void serialize_bill_of_materials(const BillOfMaterials& bill, StreamWrite* ser) { | ||
848 | 57 | ser->unsigned_32(bill.size()); | ||
849 | 58 | for (const WareAmount& amount : bill) { | ||
850 | 59 | ser->unsigned_8(amount.first); | ||
851 | 60 | ser->unsigned_32(amount.second); | ||
852 | 61 | } | ||
853 | 62 | } | ||
854 | 63 | |||
855 | 64 | BillOfMaterials deserialize_bill_of_materials(StreamRead* des) { | ||
856 | 65 | BillOfMaterials bill; | ||
857 | 66 | const int count = des->unsigned_32(); | ||
858 | 67 | for (int i = 0; i < count; ++i) { | ||
859 | 68 | const auto index = des->unsigned_8(); | ||
860 | 69 | const auto amount = des->unsigned_32(); | ||
861 | 70 | bill.push_back(std::make_pair(index, amount)); | ||
862 | 71 | } | ||
863 | 72 | return bill; | ||
864 | 73 | } | ||
865 | 74 | |||
866 | 55 | } // namespace | 75 | } // namespace |
867 | 56 | 76 | ||
868 | 57 | // NOTE keep numbers of existing entries as they are to ensure backward compatible savegame loading | 77 | // NOTE keep numbers of existing entries as they are to ensure backward compatible savegame loading |
869 | @@ -86,7 +106,8 @@ | |||
870 | 86 | PLCMD_SHIP_EXPLORE = 27, | 106 | PLCMD_SHIP_EXPLORE = 27, |
871 | 87 | PLCMD_SHIP_CONSTRUCT = 28, | 107 | PLCMD_SHIP_CONSTRUCT = 28, |
872 | 88 | PLCMD_SHIP_SINK = 29, | 108 | PLCMD_SHIP_SINK = 29, |
874 | 89 | PLCMD_SHIP_CANCELEXPEDITION = 30 | 109 | PLCMD_SHIP_CANCELEXPEDITION = 30, |
875 | 110 | PLCMD_PROPOSE_TRADE = 31, | ||
876 | 90 | }; | 111 | }; |
877 | 91 | 112 | ||
878 | 92 | /*** class PlayerCommand ***/ | 113 | /*** class PlayerCommand ***/ |
879 | @@ -1784,4 +1805,75 @@ | |||
880 | 1784 | fw.unsigned_8(ware_); | 1805 | fw.unsigned_8(ware_); |
881 | 1785 | fw.unsigned_8(static_cast<uint8_t>(policy_)); | 1806 | fw.unsigned_8(static_cast<uint8_t>(policy_)); |
882 | 1786 | } | 1807 | } |
883 | 1808 | |||
884 | 1809 | CmdProposeTrade::CmdProposeTrade(uint32_t time, PlayerNumber pn, const Trade& trade) | ||
885 | 1810 | : PlayerCommand(time, pn), trade_(trade) { | ||
886 | 1811 | } | ||
887 | 1812 | |||
888 | 1813 | CmdProposeTrade::CmdProposeTrade() : PlayerCommand() { | ||
889 | 1814 | } | ||
890 | 1815 | |||
891 | 1816 | void CmdProposeTrade::execute(Game& game) { | ||
892 | 1817 | Player* plr = game.get_player(sender()); | ||
893 | 1818 | if (plr == nullptr) { | ||
894 | 1819 | return; | ||
895 | 1820 | } | ||
896 | 1821 | |||
897 | 1822 | Market* initiator = dynamic_cast<Market*>(game.objects().get_object(trade_.initiator)); | ||
898 | 1823 | if (initiator == nullptr) { | ||
899 | 1824 | log("CmdProposeTrade: initiator vanished or is not a market.\n"); | ||
900 | 1825 | return; | ||
901 | 1826 | } | ||
902 | 1827 | if (&initiator->owner() != plr) { | ||
903 | 1828 | log("CmdProposeTrade: sender %u, but market owner %u\n", sender(), | ||
904 | 1829 | initiator->owner().player_number()); | ||
905 | 1830 | return; | ||
906 | 1831 | } | ||
907 | 1832 | Market* receiver = dynamic_cast<Market*>(game.objects().get_object(trade_.receiver)); | ||
908 | 1833 | if (receiver == nullptr) { | ||
909 | 1834 | log("CmdProposeTrade: receiver vanished or is not a market.\n"); | ||
910 | 1835 | return; | ||
911 | 1836 | } | ||
912 | 1837 | if (&initiator->owner() == &receiver->owner()) { | ||
913 | 1838 | log("CmdProposeTrade: Sending and receiving player are the same.\n"); | ||
914 | 1839 | return; | ||
915 | 1840 | } | ||
916 | 1841 | |||
917 | 1842 | // TODO(sirver,trading): Maybe check connectivity between markets here and | ||
918 | 1843 | // report errors. | ||
919 | 1844 | game.propose_trade(trade_); | ||
920 | 1845 | } | ||
921 | 1846 | |||
922 | 1847 | CmdProposeTrade::CmdProposeTrade(StreamRead& des) : PlayerCommand(0, des.unsigned_8()) { | ||
923 | 1848 | trade_.initiator = des.unsigned_32(); | ||
924 | 1849 | trade_.receiver = des.unsigned_32(); | ||
925 | 1850 | trade_.items_to_send = deserialize_bill_of_materials(&des); | ||
926 | 1851 | trade_.items_to_receive = deserialize_bill_of_materials(&des); | ||
927 | 1852 | trade_.num_batches = des.signed_32(); | ||
928 | 1853 | } | ||
929 | 1854 | |||
930 | 1855 | void CmdProposeTrade::serialize(StreamWrite& ser) { | ||
931 | 1856 | ser.unsigned_8(PLCMD_PROPOSE_TRADE); | ||
932 | 1857 | ser.unsigned_8(sender()); | ||
933 | 1858 | ser.unsigned_32(trade_.initiator); | ||
934 | 1859 | ser.unsigned_32(trade_.receiver); | ||
935 | 1860 | serialize_bill_of_materials(trade_.items_to_send, &ser); | ||
936 | 1861 | serialize_bill_of_materials(trade_.items_to_receive, &ser); | ||
937 | 1862 | ser.signed_32(trade_.num_batches); | ||
938 | 1863 | } | ||
939 | 1864 | |||
940 | 1865 | void CmdProposeTrade::read(FileRead& /* fr */, | ||
941 | 1866 | EditorGameBase& /* egbase */, | ||
942 | 1867 | MapObjectLoader& /* mol */) { | ||
943 | 1868 | // TODO(sirver,trading): Implement this. | ||
944 | 1869 | NEVER_HERE(); | ||
945 | 1870 | } | ||
946 | 1871 | |||
947 | 1872 | void CmdProposeTrade::write(FileWrite& /* fw */, | ||
948 | 1873 | EditorGameBase& /* egbase */, | ||
949 | 1874 | MapObjectSaver& /* mos */) { | ||
950 | 1875 | // TODO(sirver,trading): Implement this. | ||
951 | 1876 | NEVER_HERE(); | ||
952 | 1877 | } | ||
953 | 1878 | |||
954 | 1787 | } | 1879 | } |
955 | 1788 | 1880 | ||
956 | === modified file 'src/logic/playercommand.h' | |||
957 | --- src/logic/playercommand.h 2017-08-16 13:23:15 +0000 | |||
958 | +++ src/logic/playercommand.h 2017-10-03 20:35:46 +0000 | |||
959 | @@ -846,6 +846,29 @@ | |||
960 | 846 | DescriptionIndex ware_; | 846 | DescriptionIndex ware_; |
961 | 847 | Warehouse::StockPolicy policy_; | 847 | Warehouse::StockPolicy policy_; |
962 | 848 | }; | 848 | }; |
963 | 849 | |||
964 | 850 | struct CmdProposeTrade : PlayerCommand { | ||
965 | 851 | CmdProposeTrade(uint32_t time, PlayerNumber pn, const Trade& trade); | ||
966 | 852 | |||
967 | 853 | QueueCommandTypes id() const override { | ||
968 | 854 | return QueueCommandTypes::kProposeTrade; | ||
969 | 855 | } | ||
970 | 856 | |||
971 | 857 | void execute(Game& game) override; | ||
972 | 858 | |||
973 | 859 | // Network (de-)serialization | ||
974 | 860 | explicit CmdProposeTrade(StreamRead& des); | ||
975 | 861 | void serialize(StreamWrite& ser) override; | ||
976 | 862 | |||
977 | 863 | // Savegame functions | ||
978 | 864 | CmdProposeTrade(); | ||
979 | 865 | void write(FileWrite&, EditorGameBase&, MapObjectSaver&) override; | ||
980 | 866 | void read(FileRead&, EditorGameBase&, MapObjectLoader&) override; | ||
981 | 867 | |||
982 | 868 | private: | ||
983 | 869 | Trade trade_; | ||
984 | 870 | }; | ||
985 | 871 | |||
986 | 849 | } | 872 | } |
987 | 850 | 873 | ||
988 | 851 | #endif // end of include guard: WL_LOGIC_PLAYERCOMMAND_H | 874 | #endif // end of include guard: WL_LOGIC_PLAYERCOMMAND_H |
989 | 852 | 875 | ||
990 | === modified file 'src/logic/queue_cmd_factory.cc' | |||
991 | --- src/logic/queue_cmd_factory.cc 2017-01-25 18:55:59 +0000 | |||
992 | +++ src/logic/queue_cmd_factory.cc 2017-10-03 20:35:46 +0000 | |||
993 | @@ -78,6 +78,8 @@ | |||
994 | 78 | return *new CmdEvictWorker(); | 78 | return *new CmdEvictWorker(); |
995 | 79 | case QueueCommandTypes::kMilitarysiteSetSoldierPreference: | 79 | case QueueCommandTypes::kMilitarysiteSetSoldierPreference: |
996 | 80 | return *new CmdMilitarySiteSetSoldierPreference(); | 80 | return *new CmdMilitarySiteSetSoldierPreference(); |
997 | 81 | case QueueCommandTypes::kProposeTrade: | ||
998 | 82 | return *new CmdProposeTrade(); | ||
999 | 81 | case QueueCommandTypes::kSinkShip: | 83 | case QueueCommandTypes::kSinkShip: |
1000 | 82 | return *new CmdShipSink(); | 84 | return *new CmdShipSink(); |
1001 | 83 | case QueueCommandTypes::kShipCancelExpedition: | 85 | case QueueCommandTypes::kShipCancelExpedition: |
1002 | 84 | 86 | ||
1003 | === modified file 'src/logic/queue_cmd_ids.h' | |||
1004 | --- src/logic/queue_cmd_ids.h 2017-01-25 18:55:59 +0000 | |||
1005 | +++ src/logic/queue_cmd_ids.h 2017-10-03 20:35:46 +0000 | |||
1006 | @@ -70,7 +70,8 @@ | |||
1007 | 70 | 70 | ||
1008 | 71 | kEvictWorker, | 71 | kEvictWorker, |
1009 | 72 | 72 | ||
1011 | 73 | kMilitarysiteSetSoldierPreference, // 26 | 73 | kMilitarysiteSetSoldierPreference, |
1012 | 74 | kProposeTrade, // 27 | ||
1013 | 74 | 75 | ||
1014 | 75 | kSinkShip = 121, | 76 | kSinkShip = 121, |
1015 | 76 | kShipCancelExpedition, | 77 | kShipCancelExpedition, |
1016 | 77 | 78 | ||
1017 | === added file 'src/logic/trade_agreement.h' | |||
1018 | --- src/logic/trade_agreement.h 1970-01-01 00:00:00 +0000 | |||
1019 | +++ src/logic/trade_agreement.h 2017-10-03 20:35:46 +0000 | |||
1020 | @@ -0,0 +1,54 @@ | |||
1021 | 1 | /* | ||
1022 | 2 | * Copyright (C) 2006-2017 by the Widelands Development Team | ||
1023 | 3 | * | ||
1024 | 4 | * This program is free software; you can redistribute it and/or | ||
1025 | 5 | * modify it under the terms of the GNU General Public License | ||
1026 | 6 | * as published by the Free Software Foundation; either version 2 | ||
1027 | 7 | * of the License, or (at your option) any later version. | ||
1028 | 8 | * | ||
1029 | 9 | * This program is distributed in the hope that it will be useful, | ||
1030 | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1031 | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1032 | 12 | * GNU General Public License for more details. | ||
1033 | 13 | * | ||
1034 | 14 | * You should have received a copy of the GNU General Public License | ||
1035 | 15 | * along with this program; if not, write to the Free Software | ||
1036 | 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
1037 | 17 | * | ||
1038 | 18 | */ | ||
1039 | 19 | |||
1040 | 20 | #ifndef WL_LOGIC_TRADE_AGREEMENT_H | ||
1041 | 21 | #define WL_LOGIC_TRADE_AGREEMENT_H | ||
1042 | 22 | |||
1043 | 23 | #include "logic/map_objects/map_object.h" | ||
1044 | 24 | |||
1045 | 25 | namespace Widelands { | ||
1046 | 26 | |||
1047 | 27 | // TODO(sirver,trading): Document everything in here. | ||
1048 | 28 | |||
1049 | 29 | // Maximum number of a single ware that can be contained in a trade batch. | ||
1050 | 30 | constexpr int kMaxPerItemTradeBatchSize = 15; | ||
1051 | 31 | |||
1052 | 32 | struct Trade { | ||
1053 | 33 | BillOfMaterials items_to_send; | ||
1054 | 34 | BillOfMaterials items_to_receive; | ||
1055 | 35 | int num_batches; | ||
1056 | 36 | Serial initiator; | ||
1057 | 37 | Serial receiver; | ||
1058 | 38 | }; | ||
1059 | 39 | |||
1060 | 40 | // TODO(sirver,trading): This class should probably be private to 'Game'. | ||
1061 | 41 | struct TradeAgreement { | ||
1062 | 42 | enum class State { | ||
1063 | 43 | kProposed, | ||
1064 | 44 | kRunning, | ||
1065 | 45 | }; | ||
1066 | 46 | |||
1067 | 47 | State state; | ||
1068 | 48 | Trade trade; | ||
1069 | 49 | }; | ||
1070 | 50 | |||
1071 | 51 | } // namespace Widelands | ||
1072 | 52 | |||
1073 | 53 | |||
1074 | 54 | #endif // end of include guard: WL_LOGIC_TRADE_AGREEMENT_H | ||
1075 | 0 | 55 | ||
1076 | === modified file 'src/map_io/map_buildingdata_packet.cc' | |||
1077 | --- src/map_io/map_buildingdata_packet.cc 2017-08-20 08:34:02 +0000 | |||
1078 | +++ src/map_io/map_buildingdata_packet.cc 2017-10-03 20:35:46 +0000 | |||
1079 | @@ -339,6 +339,7 @@ | |||
1080 | 339 | } | 339 | } |
1081 | 340 | } | 340 | } |
1082 | 341 | 341 | ||
1083 | 342 | // TODO(sirver,trading): Pull out and reuse this for market workers. | ||
1084 | 342 | assert(warehouse.incorporated_workers_.empty()); | 343 | assert(warehouse.incorporated_workers_.empty()); |
1085 | 343 | { | 344 | { |
1086 | 344 | uint16_t const nrworkers = fr.unsigned_16(); | 345 | uint16_t const nrworkers = fr.unsigned_16(); |
1087 | 345 | 346 | ||
1088 | === modified file 'src/scripting/lua_map.cc' | |||
1089 | --- src/scripting/lua_map.cc 2017-09-18 13:43:08 +0000 | |||
1090 | +++ src/scripting/lua_map.cc 2017-10-03 20:35:46 +0000 | |||
1091 | @@ -33,6 +33,7 @@ | |||
1092 | 33 | #include "logic/map_objects/immovable.h" | 33 | #include "logic/map_objects/immovable.h" |
1093 | 34 | #include "logic/map_objects/terrain_affinity.h" | 34 | #include "logic/map_objects/terrain_affinity.h" |
1094 | 35 | #include "logic/map_objects/tribes/carrier.h" | 35 | #include "logic/map_objects/tribes/carrier.h" |
1095 | 36 | #include "logic/map_objects/tribes/market.h" | ||
1096 | 36 | #include "logic/map_objects/tribes/ship.h" | 37 | #include "logic/map_objects/tribes/ship.h" |
1097 | 37 | #include "logic/map_objects/tribes/soldier.h" | 38 | #include "logic/map_objects/tribes/soldier.h" |
1098 | 38 | #include "logic/map_objects/tribes/tribes.h" | 39 | #include "logic/map_objects/tribes/tribes.h" |
1099 | @@ -594,6 +595,48 @@ | |||
1100 | 594 | } | 595 | } |
1101 | 595 | return 0; | 596 | return 0; |
1102 | 596 | } | 597 | } |
1103 | 598 | |||
1104 | 599 | // Parses a table of name/count pairs as given from Lua. | ||
1105 | 600 | void parse_wares_workers(lua_State* L, | ||
1106 | 601 | int table_index, | ||
1107 | 602 | const TribeDescr& tribe, | ||
1108 | 603 | InputMap* ware_workers_list, | ||
1109 | 604 | bool is_ware) { | ||
1110 | 605 | luaL_checktype(L, table_index, LUA_TTABLE); | ||
1111 | 606 | lua_pushnil(L); | ||
1112 | 607 | while (lua_next(L, table_index) != 0) { | ||
1113 | 608 | if (is_ware) { | ||
1114 | 609 | if (tribe.ware_index(luaL_checkstring(L, -2)) == INVALID_INDEX) { | ||
1115 | 610 | report_error(L, "Illegal ware %s", luaL_checkstring(L, -2)); | ||
1116 | 611 | } | ||
1117 | 612 | ware_workers_list->insert( | ||
1118 | 613 | std::make_pair(std::make_pair(tribe.ware_index(luaL_checkstring(L, -2)), | ||
1119 | 614 | Widelands::WareWorker::wwWARE), | ||
1120 | 615 | luaL_checkuint32(L, -1))); | ||
1121 | 616 | } else { | ||
1122 | 617 | if (tribe.worker_index(luaL_checkstring(L, -2)) == INVALID_INDEX) { | ||
1123 | 618 | report_error(L, "Illegal worker %s", luaL_checkstring(L, -2)); | ||
1124 | 619 | } | ||
1125 | 620 | ware_workers_list->insert( | ||
1126 | 621 | std::make_pair(std::make_pair(tribe.worker_index(luaL_checkstring(L, -2)), | ||
1127 | 622 | Widelands::WareWorker::wwWORKER), | ||
1128 | 623 | luaL_checkuint32(L, -1))); | ||
1129 | 624 | } | ||
1130 | 625 | lua_pop(L, 1); | ||
1131 | 626 | } | ||
1132 | 627 | } | ||
1133 | 628 | |||
1134 | 629 | BillOfMaterials parse_wares_as_bill_of_material(lua_State* L, int table_index, | ||
1135 | 630 | const TribeDescr& tribe) { | ||
1136 | 631 | InputMap input_map; | ||
1137 | 632 | parse_wares_workers(L, table_index, tribe, &input_map, true /* is_ware */); | ||
1138 | 633 | BillOfMaterials result; | ||
1139 | 634 | for (const auto& pair : input_map) { | ||
1140 | 635 | result.push_back(std::make_pair(pair.first.first, pair.second)); | ||
1141 | 636 | } | ||
1142 | 637 | return result; | ||
1143 | 638 | } | ||
1144 | 639 | |||
1145 | 597 | } // namespace | 640 | } // namespace |
1146 | 598 | 641 | ||
1147 | 599 | /* | 642 | /* |
1148 | @@ -782,7 +825,6 @@ | |||
1149 | 782 | 825 | ||
1150 | 783 | // We either received, two items string,int: | 826 | // We either received, two items string,int: |
1151 | 784 | if (nargs == 3) { | 827 | if (nargs == 3) { |
1152 | 785 | |||
1153 | 786 | result = RequestedWareWorker::kSingle; | 828 | result = RequestedWareWorker::kSingle; |
1154 | 787 | if (is_ware) { | 829 | if (is_ware) { |
1155 | 788 | if (tribe.ware_index(luaL_checkstring(L, 2)) == INVALID_INDEX) { | 830 | if (tribe.ware_index(luaL_checkstring(L, 2)) == INVALID_INDEX) { |
1156 | @@ -803,32 +845,7 @@ | |||
1157 | 803 | } else { | 845 | } else { |
1158 | 804 | result = RequestedWareWorker::kList; | 846 | result = RequestedWareWorker::kList; |
1159 | 805 | // or we got a table with name:quantity | 847 | // or we got a table with name:quantity |
1186 | 806 | luaL_checktype(L, 2, LUA_TTABLE); | 848 | parse_wares_workers(L, 2, tribe, ware_workers_list, is_ware); |
1161 | 807 | lua_pushnil(L); | ||
1162 | 808 | while (lua_next(L, 2) != 0) { | ||
1163 | 809 | if (is_ware) { | ||
1164 | 810 | if (tribe.ware_index(luaL_checkstring(L, -2)) == INVALID_INDEX) { | ||
1165 | 811 | report_error(L, "Illegal ware %s", luaL_checkstring(L, -2)); | ||
1166 | 812 | } | ||
1167 | 813 | } else { | ||
1168 | 814 | if (tribe.worker_index(luaL_checkstring(L, -2)) == INVALID_INDEX) { | ||
1169 | 815 | report_error(L, "Illegal worker %s", luaL_checkstring(L, -2)); | ||
1170 | 816 | } | ||
1171 | 817 | } | ||
1172 | 818 | |||
1173 | 819 | if (is_ware) { | ||
1174 | 820 | ware_workers_list->insert( | ||
1175 | 821 | std::make_pair(std::make_pair(tribe.ware_index(luaL_checkstring(L, -2)), | ||
1176 | 822 | Widelands::WareWorker::wwWARE), | ||
1177 | 823 | luaL_checkuint32(L, -1))); | ||
1178 | 824 | } else { | ||
1179 | 825 | ware_workers_list->insert( | ||
1180 | 826 | std::make_pair(std::make_pair(tribe.worker_index(luaL_checkstring(L, -2)), | ||
1181 | 827 | Widelands::WareWorker::wwWORKER), | ||
1182 | 828 | luaL_checkuint32(L, -1))); | ||
1183 | 829 | } | ||
1184 | 830 | lua_pop(L, 1); | ||
1185 | 831 | } | ||
1187 | 832 | } | 849 | } |
1188 | 833 | return result; | 850 | return result; |
1189 | 834 | } | 851 | } |
1190 | @@ -2762,6 +2779,8 @@ | |||
1191 | 2762 | trading over land with other players. See the parent classes for more | 2779 | trading over land with other players. See the parent classes for more |
1192 | 2763 | properties. | 2780 | properties. |
1193 | 2764 | */ | 2781 | */ |
1194 | 2782 | // TODO(sirver,trading): Expose the properties of MarketDescription here once | ||
1195 | 2783 | // the interface settles. | ||
1196 | 2765 | const char LuaMarketDescription::className[] = "MarketDescription"; | 2784 | const char LuaMarketDescription::className[] = "MarketDescription"; |
1197 | 2766 | const MethodType<LuaMarketDescription> LuaMarketDescription::Methods[] = { | 2785 | const MethodType<LuaMarketDescription> LuaMarketDescription::Methods[] = { |
1198 | 2767 | {nullptr, nullptr}, | 2786 | {nullptr, nullptr}, |
1199 | @@ -5083,6 +5102,7 @@ | |||
1200 | 5083 | */ | 5102 | */ |
1201 | 5084 | const char LuaMarket::className[] = "Market"; | 5103 | const char LuaMarket::className[] = "Market"; |
1202 | 5085 | const MethodType<LuaMarket> LuaMarket::Methods[] = { | 5104 | const MethodType<LuaMarket> LuaMarket::Methods[] = { |
1203 | 5105 | METHOD(LuaMarket, propose_trade), | ||
1204 | 5086 | // TODO(sirver,trading): Implement and fix documentation. | 5106 | // TODO(sirver,trading): Implement and fix documentation. |
1205 | 5087 | // METHOD(LuaMarket, set_wares), | 5107 | // METHOD(LuaMarket, set_wares), |
1206 | 5088 | // METHOD(LuaMarket, get_wares), | 5108 | // METHOD(LuaMarket, get_wares), |
1207 | @@ -5106,6 +5126,35 @@ | |||
1208 | 5106 | ========================================================== | 5126 | ========================================================== |
1209 | 5107 | */ | 5127 | */ |
1210 | 5108 | 5128 | ||
1211 | 5129 | /* RST | ||
1212 | 5130 | .. method:: propose_trade(other_market, num_batches, items_to_send, items_to_receive) | ||
1213 | 5131 | |||
1214 | 5132 | TODO(sirver,trading): document | ||
1215 | 5133 | |||
1216 | 5134 | :returns: :const:`nil` | ||
1217 | 5135 | */ | ||
1218 | 5136 | int LuaMarket::propose_trade(lua_State* L) { | ||
1219 | 5137 | if (lua_gettop(L) != 5) { | ||
1220 | 5138 | report_error(L, "Takes 4 arguments."); | ||
1221 | 5139 | } | ||
1222 | 5140 | Game& game = get_game(L); | ||
1223 | 5141 | Market* self = get(L, game); | ||
1224 | 5142 | Market* other_market = (*get_user_class<LuaMarket>(L, 2))->get(L, game); | ||
1225 | 5143 | const int num_batches = luaL_checkinteger(L, 3); | ||
1226 | 5144 | |||
1227 | 5145 | const BillOfMaterials items_to_send = parse_wares_as_bill_of_material(L, 4, self->owner().tribe()); | ||
1228 | 5146 | // TODO(sirver,trading): unsure if correct. Test inter-tribe trading, i.e. | ||
1229 | 5147 | // barbarians trading with empire, but shipping atlantean only wares. | ||
1230 | 5148 | const BillOfMaterials items_to_receive = parse_wares_as_bill_of_material(L, 5, self->owner().tribe()); | ||
1231 | 5149 | const int trade_id = game.propose_trade( | ||
1232 | 5150 | Trade{items_to_send, items_to_receive, num_batches, self->serial(), other_market->serial()}); | ||
1233 | 5151 | |||
1234 | 5152 | // TODO(sirver,trading): Wrap 'Trade' into its own Lua class? | ||
1235 | 5153 | lua_pushint32(L, trade_id); | ||
1236 | 5154 | return 1; | ||
1237 | 5155 | } | ||
1238 | 5156 | |||
1239 | 5157 | |||
1240 | 5109 | /* | 5158 | /* |
1241 | 5110 | ========================================================== | 5159 | ========================================================== |
1242 | 5111 | C METHODS | 5160 | C METHODS |
1243 | 5112 | 5161 | ||
1244 | === modified file 'src/scripting/lua_map.h' | |||
1245 | --- src/scripting/lua_map.h 2017-09-20 21:27:25 +0000 | |||
1246 | +++ src/scripting/lua_map.h 2017-10-03 20:35:46 +0000 | |||
1247 | @@ -29,6 +29,7 @@ | |||
1248 | 29 | #include "logic/game.h" | 29 | #include "logic/game.h" |
1249 | 30 | #include "logic/map_objects/tribes/constructionsite.h" | 30 | #include "logic/map_objects/tribes/constructionsite.h" |
1250 | 31 | #include "logic/map_objects/tribes/dismantlesite.h" | 31 | #include "logic/map_objects/tribes/dismantlesite.h" |
1251 | 32 | #include "logic/map_objects/tribes/market.h" | ||
1252 | 32 | #include "logic/map_objects/tribes/militarysite.h" | 33 | #include "logic/map_objects/tribes/militarysite.h" |
1253 | 33 | #include "logic/map_objects/tribes/productionsite.h" | 34 | #include "logic/map_objects/tribes/productionsite.h" |
1254 | 34 | #include "logic/map_objects/tribes/ship.h" | 35 | #include "logic/map_objects/tribes/ship.h" |
1255 | @@ -1125,6 +1126,7 @@ | |||
1256 | 1125 | /* | 1126 | /* |
1257 | 1126 | * Lua Methods | 1127 | * Lua Methods |
1258 | 1127 | */ | 1128 | */ |
1259 | 1129 | int propose_trade(lua_State* L); | ||
1260 | 1128 | 1130 | ||
1261 | 1129 | /* | 1131 | /* |
1262 | 1130 | * C Methods | 1132 | * C Methods |
1263 | 1131 | 1133 | ||
1264 | === modified file 'src/wui/game_message_menu.cc' | |||
1265 | --- src/wui/game_message_menu.cc 2017-08-17 15:34:45 +0000 | |||
1266 | +++ src/wui/game_message_menu.cc 2017-10-03 20:35:46 +0000 | |||
1267 | @@ -487,6 +487,7 @@ | |||
1268 | 487 | case Widelands::Message::Type::kWarfareSiteDefeated: | 487 | case Widelands::Message::Type::kWarfareSiteDefeated: |
1269 | 488 | case Widelands::Message::Type::kWarfareSiteLost: | 488 | case Widelands::Message::Type::kWarfareSiteLost: |
1270 | 489 | case Widelands::Message::Type::kWarfareUnderAttack: | 489 | case Widelands::Message::Type::kWarfareUnderAttack: |
1271 | 490 | case Widelands::Message::Type::kTradeOfferReceived: | ||
1272 | 490 | set_filter_messages_tooltips(); | 491 | set_filter_messages_tooltips(); |
1273 | 491 | message_filter_ = Widelands::Message::Type::kAllMessages; | 492 | message_filter_ = Widelands::Message::Type::kAllMessages; |
1274 | 492 | geologistsbtn_->set_perm_pressed(false); | 493 | geologistsbtn_->set_perm_pressed(false); |
1275 | @@ -580,6 +581,7 @@ | |||
1276 | 580 | case Widelands::Message::Type::kWarfareSiteDefeated: | 581 | case Widelands::Message::Type::kWarfareSiteDefeated: |
1277 | 581 | case Widelands::Message::Type::kWarfareSiteLost: | 582 | case Widelands::Message::Type::kWarfareSiteLost: |
1278 | 582 | case Widelands::Message::Type::kWarfareUnderAttack: | 583 | case Widelands::Message::Type::kWarfareUnderAttack: |
1279 | 584 | case Widelands::Message::Type::kTradeOfferReceived: | ||
1280 | 583 | return "images/wui/messages/message_new.png"; | 585 | return "images/wui/messages/message_new.png"; |
1281 | 584 | } | 586 | } |
1282 | 585 | NEVER_HERE(); | 587 | NEVER_HERE(); |
1283 | 586 | 588 | ||
1284 | === modified file 'src/wui/ware_statistics_menu.cc' | |||
1285 | --- src/wui/ware_statistics_menu.cc 2017-08-08 17:39:40 +0000 | |||
1286 | +++ src/wui/ware_statistics_menu.cc 2017-10-03 20:35:46 +0000 | |||
1287 | @@ -103,8 +103,7 @@ | |||
1288 | 103 | ®istry, | 103 | ®istry, |
1289 | 104 | kPlotWidth + 2 * kSpacing, | 104 | kPlotWidth + 2 * kSpacing, |
1290 | 105 | 270, | 105 | 270, |
1293 | 106 | _("Ware Statistics")), | 106 | _("Ware Statistics")) { |
1292 | 107 | parent_(&parent) { | ||
1294 | 108 | uint8_t const nr_wares = parent.get_player()->egbase().tribes().nrwares(); | 107 | uint8_t const nr_wares = parent.get_player()->egbase().tribes().nrwares(); |
1295 | 109 | 108 | ||
1296 | 110 | // Init color sets | 109 | // Init color sets |
1297 | 111 | 110 | ||
1298 | === modified file 'src/wui/ware_statistics_menu.h' | |||
1299 | --- src/wui/ware_statistics_menu.h 2017-08-18 14:27:26 +0000 | |||
1300 | +++ src/wui/ware_statistics_menu.h 2017-10-03 20:35:46 +0000 | |||
1301 | @@ -37,7 +37,6 @@ | |||
1302 | 37 | void set_time(int32_t); | 37 | void set_time(int32_t); |
1303 | 38 | 38 | ||
1304 | 39 | private: | 39 | private: |
1305 | 40 | InteractivePlayer* parent_; | ||
1306 | 41 | WuiPlotArea* plot_production_; | 40 | WuiPlotArea* plot_production_; |
1307 | 42 | WuiPlotArea* plot_consumption_; | 41 | WuiPlotArea* plot_consumption_; |
1308 | 43 | WuiPlotArea* plot_stock_; | 42 | WuiPlotArea* plot_stock_; |
1309 | 44 | 43 | ||
1310 | === modified file 'test/maps/market_trading.wmf/scripting/init.lua' | |||
1311 | --- test/maps/market_trading.wmf/scripting/init.lua 2017-09-18 13:43:08 +0000 | |||
1312 | +++ test/maps/market_trading.wmf/scripting/init.lua 2017-10-03 20:35:46 +0000 | |||
1313 | @@ -1,6 +1,6 @@ | |||
1314 | 1 | include "scripting/lunit.lua" | ||
1315 | 2 | include "scripting/coroutine.lua" | 1 | include "scripting/coroutine.lua" |
1316 | 3 | include "scripting/infrastructure.lua" | 2 | include "scripting/infrastructure.lua" |
1317 | 3 | include "scripting/lunit.lua" | ||
1318 | 4 | include "scripting/ui.lua" | 4 | include "scripting/ui.lua" |
1319 | 5 | 5 | ||
1320 | 6 | game = wl.Game() | 6 | game = wl.Game() |
1321 | @@ -22,7 +22,17 @@ | |||
1322 | 22 | end | 22 | end |
1323 | 23 | end | 23 | end |
1324 | 24 | 24 | ||
1325 | 25 | function place_markets() | ||
1326 | 26 | prefilled_buildings(p1, { "barbarians_market", 22, 27 }) | ||
1327 | 27 | market_p1 = map:get_field(22, 27).immovable | ||
1328 | 28 | connected_road(p1, market_p1.flag, "tr,tl|", true) | ||
1329 | 29 | |||
1330 | 30 | prefilled_buildings(p2, { "barbarians_market", 31, 27 }) | ||
1331 | 31 | market_p2 = map:get_field(31, 27).immovable | ||
1332 | 32 | connected_road(p2, market_p2.flag, "tr,tl|", true) | ||
1333 | 33 | end | ||
1334 | 34 | |||
1335 | 25 | full_headquarters(p1, 22, 25) | 35 | full_headquarters(p1, 22, 25) |
1337 | 26 | full_headquarters(p2, 32, 25) | 36 | full_headquarters(p2, 31, 25) |
1338 | 27 | 37 | ||
1339 | 28 | game.desired_speed = 50000 | 38 | game.desired_speed = 50000 |
1340 | 29 | 39 | ||
1341 | === renamed file 'test/maps/market_trading.wmf/scripting/test_market_can_be_build.lua' => 'test/maps/market_trading.wmf/scripting/test_market_can_be_built.lua' | |||
1342 | --- test/maps/market_trading.wmf/scripting/test_market_can_be_build.lua 2017-09-18 13:43:08 +0000 | |||
1343 | +++ test/maps/market_trading.wmf/scripting/test_market_can_be_built.lua 2017-10-03 20:35:46 +0000 | |||
1344 | @@ -1,8 +1,8 @@ | |||
1345 | 1 | run(function() | 1 | run(function() |
1346 | 2 | sleep(2000) | 2 | sleep(2000) |
1347 | 3 | 3 | ||
1350 | 4 | market = p2:place_building("barbarians_market", map:get_field(35, 25), true, true) | 4 | market = p2:place_building("barbarians_market", map:get_field(31, 27), true, true) |
1351 | 5 | connected_road(p2, market.flag, "l,l,l|", true) | 5 | connected_road(p2, market.flag, "tr,tl|", true) |
1352 | 6 | 6 | ||
1353 | 7 | while #p2:get_buildings("barbarians_market") == 0 do | 7 | while #p2:get_buildings("barbarians_market") == 0 do |
1354 | 8 | sleep(10000) | 8 | sleep(10000) |
1355 | 9 | 9 | ||
1356 | === added file 'test/maps/market_trading.wmf/scripting/test_simple_trade.lua' | |||
1357 | --- test/maps/market_trading.wmf/scripting/test_simple_trade.lua 1970-01-01 00:00:00 +0000 | |||
1358 | +++ test/maps/market_trading.wmf/scripting/test_simple_trade.lua 2017-10-03 20:35:46 +0000 | |||
1359 | @@ -0,0 +1,39 @@ | |||
1360 | 1 | run(function() | ||
1361 | 2 | sleep(2000) | ||
1362 | 3 | place_markets() | ||
1363 | 4 | market_p2:propose_trade(market_p1, 5, { log = 3 }, { granite = 2, iron = 1 }) | ||
1364 | 5 | |||
1365 | 6 | local p1_initial = { | ||
1366 | 7 | iron = p1:get_wares("iron"), | ||
1367 | 8 | log = p1:get_wares("log"), | ||
1368 | 9 | granite = p1:get_wares("granite"), | ||
1369 | 10 | } | ||
1370 | 11 | local p2_initial = { | ||
1371 | 12 | iron = p1:get_wares("iron"), | ||
1372 | 13 | log = p1:get_wares("log"), | ||
1373 | 14 | granite = p1:get_wares("granite"), | ||
1374 | 15 | } | ||
1375 | 16 | |||
1376 | 17 | -- We await until one ware we trade has the right count for one player. | ||
1377 | 18 | -- Then, we'll sleep half as long as we already waited to make sure that no | ||
1378 | 19 | -- additional batches are shipped. Then we check all stocks for the correct | ||
1379 | 20 | -- numbers. | ||
1380 | 21 | local start_time = game.time | ||
1381 | 22 | while p2:get_wares("iron") - p2_initial["iron"] < 5 do | ||
1382 | 23 | sleep(10000) | ||
1383 | 24 | end | ||
1384 | 25 | |||
1385 | 26 | sleep(math.ceil((game.time - start_time) / 2)) | ||
1386 | 27 | |||
1387 | 28 | assert_equal(5, p2:get_wares("iron") - p2_initial["iron"]) | ||
1388 | 29 | assert_equal(10, p2:get_wares("granite") - p2_initial["granite"]) | ||
1389 | 30 | assert_equal(-15, p2:get_wares("log") - p2_initial["log"]) | ||
1390 | 31 | |||
1391 | 32 | assert_equal(-5, p1:get_wares("iron") - p1_initial["iron"]) | ||
1392 | 33 | assert_equal(-10, p1:get_wares("granite") - p1_initial["granite"]) | ||
1393 | 34 | assert_equal(15, p1:get_wares("log") - p1_initial["log"]) | ||
1394 | 35 | |||
1395 | 36 | print("# All Tests passed.") | ||
1396 | 37 | wl.ui.MapView():close() | ||
1397 | 38 | end) | ||
1398 | 39 |
Still very big, but the smallest functional piece I could make. Forward looking I'll probably have essentially one branch per TODO which should make the diffs even smaller.