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

Proposed by SirVer
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
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/debug/src/widelands --datadir=data --datadir_for_testing=test --scenario=maps/market_trading.wmf --script=maps/market_trading.wmf/scripting/test_simple_trade.lua

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

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.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2686. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/278792165.
Appveyor build 2506. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_market1-2506.

Revision history for this message
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.

Revision history for this message
SirVer (sirver) wrote :

Found it - a copy & pasto. Great to have tests catching stuff like this :)

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2689. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/278977032.
Appveyor build 2509. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_market1-2509.

Revision history for this message
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

review: Approve
Revision history for this message
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.

Revision history for this message
SirVer (sirver) wrote :

Thank you for the code review! All addressed.

@bunnybot merge

review: Needs Resubmitting
Revision history for this message
GunChleoc (gunchleoc) wrote :

I added some more comments for a follow-up branch.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 prohibited_till = 1000
6 },
7
8- working_positions = {
9- barbarians_ox = 10,
10- },
11+ carrier = "barbarians_ox",
12 }
13
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 // callbacks for WareInstance/Worker code
19 void transfer_finish(Game&, Transfer&);
20 void transfer_fail(Game&, Transfer&);
21+ void cancel_transfer(uint32_t idx);
22
23 void set_requirements(const Requirements& r) {
24 requirements_ = r;
25@@ -131,13 +132,9 @@
26 return requirements_;
27 }
28
29+
30 private:
31 int32_t get_base_required_time(EditorGameBase&, uint32_t nr) const;
32-
33-public:
34- void cancel_transfer(uint32_t idx);
35-
36-private:
37 void remove_transfer(uint32_t idx);
38 uint32_t find_transfer(Transfer&);
39
40
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
46 // Update whether we have a Supply or not
47 if (!transfer_ || !transfer_->get_request()) {
48- if (!supply_)
49+ if (!supply_) {
50 supply_ = new IdleWareSupply(*this);
51+ }
52 } else {
53 delete supply_;
54 supply_ = nullptr;
55
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 // Write gametime
61 fw.unsigned_32(game.gametime_);
62
63+ // TODO(sirver,trading): save/load trade_agreements and related data.
64+
65 // We do not care for players, since they were set
66 // on game initialization to match Map::scenario_player_[names|tribes]
67 // or vice versa, so this is handled by map loader
68
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 findimmovable.h
74 findnode.cc
75 findnode.h
76+ game.cc
77+ game.h
78 game_data_error.cc
79 game_data_error.h
80- game.cc
81- game.h
82+ map.cc
83+ map.h
84 map_revision.cc
85 map_revision.h
86- map.cc
87- map.h
88 mapastar.cc
89 mapastar.h
90 mapdifferenceregion.cc
91@@ -114,18 +114,18 @@
92 mapregion.h
93 maptriangleregion.cc
94 maptriangleregion.h
95+ message.h
96 message_id.h
97 message_queue.h
98- message.h
99 nodecaps.h
100 objective.h
101 path.cc
102 path.h
103 pathfield.cc
104 pathfield.h
105- player_area.h
106 player.cc
107 player.h
108+ player_area.h
109 playercommand.cc
110 playercommand.h
111 playersmanager.cc
112@@ -138,6 +138,7 @@
113 roadtype.h
114 save_handler.cc
115 save_handler.h
116+ trade_agreement.h
117 widelands_geometry_io.cc
118 widelands_geometry_io.h
119 map_objects/bob.cc
120
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 #include "logic/cmd_luascript.h"
126 #include "logic/game_settings.h"
127 #include "logic/map_objects/tribes/carrier.h"
128+#include "logic/map_objects/tribes/market.h"
129 #include "logic/map_objects/tribes/militarysite.h"
130 #include "logic/map_objects/tribes/ship.h"
131 #include "logic/map_objects/tribes/soldier.h"
132@@ -753,6 +754,87 @@
133 get_gametime(), ship.get_owner()->player_number(), ship.serial()));
134 }
135
136+void Game::send_player_propose_trade(const Trade& trade) {
137+ auto* object = objects().get_object(trade.initiator);
138+ assert(object != nullptr);
139+ send_player_command(
140+ *new CmdProposeTrade(get_gametime(), object->get_owner()->player_number(), trade));
141+}
142+
143+int Game::propose_trade(const Trade& trade) {
144+ // TODO(sirver,trading): Check if a trade is possible (i.e. if there is a
145+ // path between the two markets);
146+ const int id = next_trade_agreement_id_;
147+ ++next_trade_agreement_id_;
148+
149+ auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator));
150+ auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver));
151+ // This is only ever called through a PlayerCommand and that already made
152+ // sure that the objects still exist. Since no time has passed, they should
153+ // not have vanished under us.
154+ assert(initiator != nullptr);
155+ assert(receiver != nullptr);
156+
157+ receiver->removed.connect(
158+ [this, id](const uint32_t /* serial */) { cancel_trade(id); });
159+ initiator->removed.connect(
160+ [this, id](const uint32_t /* serial */) { cancel_trade(id); });
161+
162+ receiver->send_message(*this, Message::Type::kTradeOfferReceived, _("Trade Offer"),
163+ receiver->descr().icon_filename(), receiver->descr().descname(),
164+ _("This market has received a new trade offer."), true);
165+ trade_agreements_[id] = TradeAgreement{TradeAgreement::State::kProposed, trade};
166+
167+ // TODO(sirver,trading): this should be done through another player_command, but I
168+ // want to get to the trade logic implementation now.
169+ accept_trade(id);
170+ return id;
171+}
172+
173+void Game::accept_trade(const int trade_id) {
174+ auto it = trade_agreements_.find(trade_id);
175+ if (it == trade_agreements_.end()) {
176+ log("Game::accept_trade: Trade %d has vanished. Ignoring.\n", trade_id);
177+ return;
178+ }
179+ const Trade& trade = it->second.trade;
180+ auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator));
181+ auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver));
182+ if (initiator == nullptr || receiver == nullptr) {
183+ cancel_trade(trade_id);
184+ return;
185+ }
186+
187+ initiator->new_trade(trade_id, trade.items_to_send, trade.num_batches, trade.receiver);
188+ receiver->new_trade(trade_id, trade.items_to_receive, trade.num_batches, trade.initiator);
189+
190+ // TODO(sirver,trading): Message the users that the trade has been accepted.
191+}
192+
193+void Game::cancel_trade(int trade_id) {
194+ // The trade id might be long gone - since we never disconnect from the
195+ // 'removed' signal of the two buildings, we might be invoked long after the
196+ // trade was deleted for other reasons.
197+ const auto it = trade_agreements_.find(trade_id);
198+ if (it == trade_agreements_.end()) {
199+ return;
200+ }
201+ const auto& trade = it->second.trade;
202+
203+ auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator));
204+ if (initiator != nullptr) {
205+ initiator->cancel_trade(trade_id);
206+ // TODO(sirver,trading): Send message to owner that the trade has been canceled.
207+ }
208+
209+ auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver));
210+ if (receiver != nullptr) {
211+ receiver->cancel_trade(trade_id);
212+ // TODO(sirver,trading): Send message to owner that the trade has been canceled.
213+ }
214+ trade_agreements_.erase(trade_id);
215+}
216+
217 LuaGameInterface& Game::lua() {
218 return static_cast<LuaGameInterface&>(EditorGameBase::lua());
219 }
220
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 #include "logic/cmd_queue.h"
226 #include "logic/editor_game_base.h"
227 #include "logic/save_handler.h"
228+#include "logic/trade_agreement.h"
229 #include "random/random.h"
230 #include "scripting/logic.h"
231
232@@ -207,6 +208,7 @@
233 void send_player_ship_explore_island(Ship&, IslandExploreDirection);
234 void send_player_sink_ship(Ship&);
235 void send_player_cancel_expedition_ship(Ship&);
236+ void send_player_propose_trade(const Trade& trade);
237
238 InteractivePlayer* get_ipl();
239
240@@ -244,6 +246,11 @@
241
242 void set_auto_speed(bool);
243
244+ // TODO(sirver,trading): document these functions once the interface settles.
245+ int propose_trade(const Trade& trade);
246+ void accept_trade(int trade_id);
247+ void cancel_trade(int trade_id);
248+
249 private:
250 void sync_reset();
251
252@@ -308,6 +315,9 @@
253 std::unique_ptr<ReplayWriter> replaywriter_;
254
255 GeneralStatsVector general_stats_;
256+ int next_trade_agreement_id_ = 1;
257+ // Maps from trade agreement id to the agreement.
258+ std::map<int, TradeAgreement> trade_agreements_;
259
260 /// For save games and statistics generation
261 std::string win_condition_displayname_;
262
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 state.ivar1 = 0;
268 }
269
270+bool Bob::is_idle() {
271+ return get_state(taskIdle);
272+}
273+
274 /**
275 * Move along a predefined path.
276 * \par ivar1 the step number.
277
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 // TODO(feature-Hasi50): correct (?) Send a signal that may switch to some other \ref Task
283 void send_signal(Game&, char const*);
284 void start_task_idle(Game&, uint32_t anim, int32_t timeout);
285+ bool is_idle();
286
287 /// This can fail (and return false). Therefore the caller must check the
288 /// result and find something else for the bob to do. Otherwise there will
289
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
295 #include "logic/map_objects/tribes/market.h"
296
297+#include <memory>
298+
299 #include "base/i18n.h"
300 #include "logic/map_objects/tribes/productionsite.h"
301+#include "logic/map_objects/tribes/tribes.h"
302
303 namespace Widelands {
304
305@@ -30,7 +33,12 @@
306 : BuildingDescr(init_descname, MapObjectType::MARKET, table, egbase) {
307 i18n::Textdomain td("tribes");
308
309- parse_working_positions(egbase, table.get_table("working_positions").get(), &working_positions_);
310+ DescriptionIndex const woi = egbase.tribes().worker_index(table.get_string("carrier"));
311+ if (!egbase.tribes().worker_exists(woi)) {
312+ throw wexception("The tribe does not define the worker in 'carrier'.");
313+ }
314+ carrier_ = woi;
315+
316 // TODO(sirver,trading): Add actual logic here.
317 }
318
319@@ -38,10 +46,223 @@
320 return *new Market(*this);
321 }
322
323+int Market::TradeOrder::num_wares_per_batch() const {
324+ int sum = 0;
325+ for (const auto& item_pair : items) {
326+ sum += item_pair.second;
327+ }
328+ return sum;
329+}
330+
331+bool Market::TradeOrder::fulfilled() const {
332+ return num_shipped_batches == initial_num_batches;
333+}
334+
335+// TODO(sirver,trading): This needs to implement 'set_economy'. Maybe common code can be shared.
336 Market::Market(const MarketDescr& the_descr) : Building(the_descr) {
337 }
338
339 Market::~Market() {
340 }
341
342+void Market::new_trade(const int trade_id,
343+ const BillOfMaterials& items,
344+ const int num_batches,
345+ const Serial other_side) {
346+ trade_orders_[trade_id] = TradeOrder{items, num_batches, 0, other_side, 0, nullptr, {}};
347+ auto& trade_order = trade_orders_[trade_id];
348+
349+ // Request one worker for each item in a batch.
350+ trade_order.worker_request.reset(
351+ new Request(*this, descr().carrier(), Market::worker_arrived_callback, wwWORKER));
352+ trade_order.worker_request->set_count(trade_order.num_wares_per_batch());
353+
354+ // Make sure we have a wares queue for each item in this.
355+ for (const auto& entry : items) {
356+ ensure_wares_queue_exists(entry.first);
357+ }
358+}
359+
360+void Market::cancel_trade(const int trade_id) {
361+ // TODO(sirver,trading): Launch workers, release no longer required wares and delete now unneeded 'WaresQueue's
362+ trade_orders_.erase(trade_id);
363+}
364+
365+void Market::worker_arrived_callback(
366+ Game& game, Request& rq, DescriptionIndex /* widx */, Worker* const w, PlayerImmovable& target) {
367+ auto& market = dynamic_cast<Market&>(target);
368+
369+ assert(w);
370+ assert(w->get_location(game) == &market);
371+
372+ for (auto& trade_order_pair : market.trade_orders_) {
373+ auto& trade_order = trade_order_pair.second;
374+ if (trade_order.worker_request.get() != &rq) {
375+ continue;
376+ }
377+
378+ if (rq.get_count() == 0) {
379+ // Erase this request.
380+ trade_order.worker_request.reset();
381+ }
382+ w->start_task_idle(game, 0, -1);
383+ trade_order.workers.push_back(w);
384+ Notifications::publish(NoteBuilding(market.serial(), NoteBuilding::Action::kWorkersChanged));
385+ market.try_launching_batch(&game);
386+ return;
387+ }
388+ NEVER_HERE(); // We should have found and handled a match by now.
389+}
390+
391+void Market::ware_arrived_callback(Game& g, InputQueue*, DescriptionIndex, Worker*, void* data) {
392+ Market& market = *static_cast<Market*>(data);
393+ market.try_launching_batch(&g);
394+}
395+
396+void Market::try_launching_batch(Game* game) {
397+ for (auto& pair : trade_orders_) {
398+ if (!is_ready_to_launch_batch(pair.first)) {
399+ continue;
400+ }
401+
402+ auto* other_market =
403+ dynamic_cast<Market*>(game->objects().get_object(pair.second.other_side));
404+ if (other_market == nullptr) {
405+ // TODO(sirver,trading): Can this even happen? Where is this function called from?
406+ // The other market seems to have vanished. The game tracks this and
407+ // should soon delete this trade request from us. We just ignore it.
408+ continue;
409+ }
410+ if (!other_market->is_ready_to_launch_batch(pair.first)) {
411+ continue;
412+ }
413+ launch_batch(pair.first, game);
414+ other_market->launch_batch(pair.first, game);
415+ break;
416+ }
417+}
418+
419+bool Market::is_ready_to_launch_batch(const int trade_id) {
420+ const auto it = trade_orders_.find(trade_id);
421+ if (it == trade_orders_.end()) {
422+ return false;
423+ }
424+ auto& trade_order = it->second;
425+ assert(!trade_order.fulfilled());
426+
427+ // Do we have all necessary wares for a batch?
428+ for (const auto& item_pair : trade_order.items) {
429+ const auto wares_it = wares_queue_.find(item_pair.first);
430+ if (wares_it == wares_queue_.end()) {
431+ return false;
432+ }
433+ if (wares_it->second->get_filled() < item_pair.second) {
434+ return false;
435+ }
436+ }
437+
438+ // Do we have enough people to carry wares?
439+ int num_available_carriers = 0;
440+ for (auto* worker : trade_order.workers) {
441+ num_available_carriers += worker->is_idle() ? 1 : 0;
442+ }
443+ return num_available_carriers == trade_order.num_wares_per_batch();
444+}
445+
446+void Market::launch_batch(const int trade_id, Game* game) {
447+ assert(is_ready_to_launch_batch(trade_id));
448+ auto& trade_order = trade_orders_.at(trade_id);
449+
450+ // Do we have all necessary wares for a batch?
451+ int worker_index = 0;
452+ for (const auto& item_pair : trade_order.items) {
453+ for (size_t i = 0; i < item_pair.second; ++i) {
454+ Worker* carrier = trade_order.workers.at(worker_index);
455+ ++worker_index;
456+ assert(carrier->is_idle());
457+
458+ // Give the carrier a ware.
459+ WareInstance* ware =
460+ new WareInstance(item_pair.first, game->tribes().get_ware_descr(item_pair.first));
461+ ware->init(*game);
462+ carrier->set_carried_ware(*game, ware);
463+
464+ // We have to remove this item from our economy. Otherwise it would be
465+ // considered idle (since it has no transport associated with it) and
466+ // the engine would want to transfer it to the next warehouse.
467+ ware->set_economy(nullptr);
468+ wares_queue_.at(item_pair.first)
469+ ->set_filled(wares_queue_.at(item_pair.first)->get_filled() - 1);
470+
471+ // Send the carrier going.
472+ carrier->reset_tasks(*game);
473+ carrier->start_task_carry_trade_item(
474+ *game, trade_id, ObjectPointer(game->objects().get_object(trade_order.other_side)));
475+ }
476+ }
477+}
478+
479+void Market::ensure_wares_queue_exists(int ware_index) {
480+ if (wares_queue_.count(ware_index) > 0) {
481+ return;
482+ }
483+ wares_queue_[ware_index] =
484+ std::unique_ptr<WaresQueue>(new WaresQueue(*this, ware_index, kMaxPerItemTradeBatchSize));
485+ wares_queue_[ware_index]->set_callback(Market::ware_arrived_callback, this);
486+}
487+
488+InputQueue& Market::inputqueue(DescriptionIndex index, WareWorker ware_worker) {
489+ assert(ware_worker == wwWARE);
490+ auto it = wares_queue_.find(index);
491+ if (it != wares_queue_.end()) {
492+ return *it->second;
493+ }
494+ // The parent will throw an exception.
495+ return Building::inputqueue(index, ware_worker);
496+}
497+
498+void Market::cleanup(EditorGameBase& egbase) {
499+ for (auto& pair : wares_queue_) {
500+ pair.second->cleanup();
501+ }
502+ Building::cleanup(egbase);
503+}
504+
505+void Market::traded_ware_arrived(const int trade_id, const DescriptionIndex ware_index, Game* game) {
506+ auto& trade_order = trade_orders_.at(trade_id);
507+
508+ WareInstance* ware = new WareInstance(ware_index, game->tribes().get_ware_descr(ware_index));
509+ ware->init(*game);
510+
511+ // TODO(sirver,trading): This is a hack. We should have a worker that
512+ // carriers stuff out. At the moment this assumes this market is barbarians
513+ // (which is always correct right now), creates a carrier for each received
514+ // ware to drop it off. The carrier then leaves the building and goes home.
515+ const WorkerDescr& w_desc =
516+ *game->tribes().get_worker_descr(game->tribes().worker_index("barbarians_carrier"));
517+ auto& worker = w_desc.create(*game, owner(), this, position_);
518+ worker.start_task_dropoff(*game, *ware);
519+ trade_order.received_traded_wares_in_this_batch += 1;
520+ owner().ware_produced(ware_index);
521+
522+ auto* other_market = dynamic_cast<Market*>(game->objects().get_object(trade_order.other_side));
523+ assert(other_market != nullptr);
524+ other_market->owner().ware_consumed(ware_index, 1);
525+ auto& other_trade_order = other_market->trade_orders_.at(trade_id);
526+ if (trade_order.received_traded_wares_in_this_batch == other_trade_order.num_wares_per_batch() &&
527+ other_trade_order.received_traded_wares_in_this_batch == trade_order.num_wares_per_batch()) {
528+ // This batch is completed.
529+ ++trade_order.num_shipped_batches;
530+ trade_order.received_traded_wares_in_this_batch = 0;
531+ ++other_trade_order.num_shipped_batches;
532+ other_trade_order.received_traded_wares_in_this_batch = 0;
533+ if (trade_order.fulfilled()) {
534+ assert(other_trade_order.fulfilled());
535+ // TODO(sirver,trading): This is not quite correct. This should for
536+ // example send a differnet message than actually canceling a trade.
537+ game->cancel_trade(trade_id);
538+ }
539+ }
540+}
541+
542 } // namespace Widelands
543
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 #ifndef WL_LOGIC_MAP_OBJECTS_TRIBES_MARKET_H
549 #define WL_LOGIC_MAP_OBJECTS_TRIBES_MARKET_H
550
551+#include <memory>
552+
553+#include "economy/request.h"
554+#include "economy/wares_queue.h"
555 #include "logic/map_objects/tribes/building.h"
556
557 namespace Widelands {
558@@ -32,8 +36,10 @@
559
560 Building& create_object() const override;
561
562+ DescriptionIndex carrier() const { return carrier_; }
563+
564 private:
565- BillOfMaterials working_positions_;
566+ DescriptionIndex carrier_;
567 };
568
569 class Market : public Building {
570@@ -42,7 +48,48 @@
571 explicit Market(const MarketDescr& descr);
572 ~Market() override;
573
574+ void new_trade(int trade_id, const BillOfMaterials& items, int num_batches, Serial other_side);
575+ void cancel_trade(int trade_id);
576+
577+ InputQueue& inputqueue(DescriptionIndex, WareWorker) override;
578+ void cleanup(EditorGameBase&) override;
579+
580+ void try_launching_batch(Game* game);
581+ void traded_ware_arrived(int trade_id, DescriptionIndex ware_index, Game* game);
582+
583 private:
584+ struct TradeOrder {
585+ BillOfMaterials items;
586+ int initial_num_batches;
587+ int num_shipped_batches;
588+ Serial other_side;
589+
590+ int received_traded_wares_in_this_batch;
591+
592+ // The invariant here is that worker.size() + worker_request.get_count()
593+ // == 'num_wares_per_batch()'
594+ std::unique_ptr<Request> worker_request;
595+ std::vector<Worker*> workers;
596+
597+ // The number of individual wares in 'items', i.e. the sum of all '.second's.
598+ int num_wares_per_batch() const;
599+
600+ // True if the 'num_shipped_batches' equals the 'initial_num_batches'
601+ bool fulfilled() const;
602+ };
603+
604+ static void
605+ worker_arrived_callback(Game&, Request&, DescriptionIndex, Worker*, PlayerImmovable&);
606+ static void
607+ ware_arrived_callback(Game& g, InputQueue* q, DescriptionIndex ware, Worker* worker, void* data);
608+
609+ void ensure_wares_queue_exists(int ware_index);
610+ bool is_ready_to_launch_batch(int trade_id);
611+ void launch_batch(int trade_id, Game* game);
612+
613+ std::map<int, TradeOrder> trade_orders_; // Key is 'trade_id's.
614+ std::map<int, std::unique_ptr<WaresQueue>> wares_queue_; // Key is 'ware_index'.
615+
616 DISALLOW_COPY_AND_ASSIGN(Market);
617 };
618
619
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
625 constexpr size_t STATISTICS_VECTOR_LENGTH = 20;
626
627-} // namespace
628-
629+// Parses the descriptions of the working positions from 'items_table' and
630+// fills in 'working_positions'. Throws an error if the table contains invalid
631+// values.
632 void parse_working_positions(const EditorGameBase& egbase,
633 LuaTable* items_table,
634 BillOfMaterials* working_positions) {
635@@ -71,6 +72,8 @@
636 }
637 }
638
639+} // namespace
640+
641 /*
642 ==============================================================================
643
644
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 }
650 };
651
652-// Parses the descriptions of the working positions from 'items_table' and
653-// fills in 'working_positions'. Throws an error if the table contains invalid
654-// values.
655-void parse_working_positions(const EditorGameBase& egbase,
656- LuaTable* items_table,
657- BillOfMaterials* working_positions);
658-
659 } // namespace Widelands
660
661 #endif // end of include guard: WL_LOGIC_MAP_OBJECTS_TRIBES_PRODUCTIONSITE_H
662
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 #include "base/wexception.h"
668 #include "graphic/graphic.h"
669 #include "logic/game_data_error.h"
670+#include "logic/map_objects/tribes/market.h"
671
672 namespace Widelands {
673
674
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 #include "logic/map_objects/tribes/tribe_descr.h"
680 #include "logic/map_objects/tribes/ware_descr.h"
681 #include "logic/map_objects/tribes/warehouse.h"
682-#include "logic/map_objects/tribes/market.h"
683 #include "logic/map_objects/tribes/worker_descr.h"
684 #include "scripting/lua_table.h"
685
686
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 #include "logic/map_objects/terrain_affinity.h"
692 #include "logic/map_objects/tribes/carrier.h"
693 #include "logic/map_objects/tribes/dismantlesite.h"
694+#include "logic/map_objects/tribes/market.h"
695 #include "logic/map_objects/tribes/soldier.h"
696 #include "logic/map_objects/tribes/tribe_descr.h"
697 #include "logic/map_objects/tribes/warehouse.h"
698@@ -1594,6 +1595,88 @@
699 send_signal(game, "update");
700 }
701
702+// The task when a worker is part of the caravan that is trading items.
703+const Bob::Task Worker::taskCarryTradeItem = {
704+ "carry_trade_item", static_cast<Bob::Ptr>(&Worker::carry_trade_item_update), nullptr, nullptr, true};
705+
706+void Worker::start_task_carry_trade_item(Game& game,
707+ const int trade_id,
708+ ObjectPointer other_market) {
709+ push_task(game, taskCarryTradeItem);
710+ auto& state = top_state();
711+ state.ivar1 = 0;
712+ state.ivar2 = trade_id;
713+ state.objvar1 = other_market;
714+}
715+
716+// This is a state machine: leave building, go to the other market, drop off
717+// wares, and return.
718+void Worker::carry_trade_item_update(Game& game, State& state) {
719+ // Reset any signals that are not related to location
720+ std::string signal = get_signal();
721+ signal_handled();
722+ if (!signal.empty()) {
723+ // TODO(sirver,trading): Remove once signals are correctly handled.
724+ log("carry_trade_item_update: signal received: %s\n", signal.c_str());
725+ }
726+ if (signal == "evict") {
727+ return pop_task(game);
728+ }
729+
730+ // First of all, make sure we're outside
731+ if (state.ivar1 == 0) {
732+ start_task_leavebuilding(game, false);
733+ ++state.ivar1;
734+ return;
735+ }
736+
737+ auto* other_market = dynamic_cast<Market*>(state.objvar1.get(game));
738+ if (state.ivar1 == 1) {
739+ // Arrived on site. Move to the building and advance our state.
740+ if (other_market->base_flag().get_position() == get_position()) {
741+ ++state.ivar1;
742+ return start_task_move(
743+ game, WALK_NW, descr().get_right_walk_anims(does_carry_ware()), true);
744+ }
745+
746+ // Otherwise continue making progress towards the other market.
747+ if (!start_task_movepath(game, other_market->base_flag().get_position(), 5,
748+ descr().get_right_walk_anims(does_carry_ware()))) {
749+ molog("carry_trade_item_update: Could not move to other flag.\n");
750+ // TODO(sirver,trading): something needs to happen here.
751+ }
752+ return;
753+ }
754+
755+ if (state.ivar1 == 2) {
756+ WareInstance* const ware = fetch_carried_ware(game);
757+ other_market->traded_ware_arrived(state.ivar2, ware->descr_index(), &game);
758+ ware->remove(game);
759+ ++state.ivar1;
760+ start_task_move(game, WALK_SE, descr().get_right_walk_anims(does_carry_ware()), true);
761+ return;
762+ }
763+
764+ if (state.ivar1 == 3) {
765+ ++state.ivar1;
766+ start_task_return(game, false);
767+ return;
768+ }
769+
770+ if (state.ivar1 == 4) {
771+ pop_task(game);
772+ start_task_idle(game, 0, -1);
773+ dynamic_cast<Market*>(get_location(game))->try_launching_batch(&game);
774+ return;
775+ }
776+ NEVER_HERE();
777+}
778+
779+void Worker::update_task_carry_trade_item(Game& game) {
780+ if (top_state().task == &taskCarryTradeItem)
781+ send_signal(game, "update");
782+}
783+
784 /**
785 * Evict the worker from its current building.
786 */
787
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 void start_task_leavebuilding(Game&, bool changelocation);
793 void start_task_fugitive(Game&);
794
795+ void start_task_carry_trade_item(Game& game, int trade_id, ObjectPointer other_market);
796+ void update_task_carry_trade_item(Game&);
797+
798 void
799 start_task_geologist(Game&, uint8_t attempts, uint8_t radius, const std::string& subcommand);
800
801@@ -208,6 +211,7 @@
802 static const Task taskFugitive;
803 static const Task taskGeologist;
804 static const Task taskScout;
805+ static const Task taskCarryTradeItem;
806
807 private:
808 // task details
809@@ -232,6 +236,7 @@
810 void fugitive_update(Game&, State&);
811 void geologist_update(Game&, State&);
812 void scout_update(Game&, State&);
813+ void carry_trade_item_update(Game&, State&);
814
815 // Program commands
816 bool run_mine(Game&, State&, const Action&);
817
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 kWarfare, // everything starting from here is warfare
823 kWarfareSiteDefeated,
824 kWarfareSiteLost,
825- kWarfareUnderAttack
826+ kWarfareUnderAttack,
827+ kTradeOfferReceived,
828 };
829
830 /**
831
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 #include "io/streamwrite.h"
837 #include "logic/game.h"
838 #include "logic/map_objects/map_object.h"
839+#include "logic/map_objects/tribes/market.h"
840 #include "logic/map_objects/tribes/soldier.h"
841 #include "logic/map_objects/tribes/tribe_descr.h"
842 #include "logic/player.h"
843@@ -52,6 +53,25 @@
844 return mol.get<T>(object_index).serial();
845 }
846
847+void serialize_bill_of_materials(const BillOfMaterials& bill, StreamWrite* ser) {
848+ ser->unsigned_32(bill.size());
849+ for (const WareAmount& amount : bill) {
850+ ser->unsigned_8(amount.first);
851+ ser->unsigned_32(amount.second);
852+ }
853+}
854+
855+BillOfMaterials deserialize_bill_of_materials(StreamRead* des) {
856+ BillOfMaterials bill;
857+ const int count = des->unsigned_32();
858+ for (int i = 0; i < count; ++i) {
859+ const auto index = des->unsigned_8();
860+ const auto amount = des->unsigned_32();
861+ bill.push_back(std::make_pair(index, amount));
862+ }
863+ return bill;
864+}
865+
866 } // namespace
867
868 // NOTE keep numbers of existing entries as they are to ensure backward compatible savegame loading
869@@ -86,7 +106,8 @@
870 PLCMD_SHIP_EXPLORE = 27,
871 PLCMD_SHIP_CONSTRUCT = 28,
872 PLCMD_SHIP_SINK = 29,
873- PLCMD_SHIP_CANCELEXPEDITION = 30
874+ PLCMD_SHIP_CANCELEXPEDITION = 30,
875+ PLCMD_PROPOSE_TRADE = 31,
876 };
877
878 /*** class PlayerCommand ***/
879@@ -1784,4 +1805,75 @@
880 fw.unsigned_8(ware_);
881 fw.unsigned_8(static_cast<uint8_t>(policy_));
882 }
883+
884+CmdProposeTrade::CmdProposeTrade(uint32_t time, PlayerNumber pn, const Trade& trade)
885+ : PlayerCommand(time, pn), trade_(trade) {
886+}
887+
888+CmdProposeTrade::CmdProposeTrade() : PlayerCommand() {
889+}
890+
891+void CmdProposeTrade::execute(Game& game) {
892+ Player* plr = game.get_player(sender());
893+ if (plr == nullptr) {
894+ return;
895+ }
896+
897+ Market* initiator = dynamic_cast<Market*>(game.objects().get_object(trade_.initiator));
898+ if (initiator == nullptr) {
899+ log("CmdProposeTrade: initiator vanished or is not a market.\n");
900+ return;
901+ }
902+ if (&initiator->owner() != plr) {
903+ log("CmdProposeTrade: sender %u, but market owner %u\n", sender(),
904+ initiator->owner().player_number());
905+ return;
906+ }
907+ Market* receiver = dynamic_cast<Market*>(game.objects().get_object(trade_.receiver));
908+ if (receiver == nullptr) {
909+ log("CmdProposeTrade: receiver vanished or is not a market.\n");
910+ return;
911+ }
912+ if (&initiator->owner() == &receiver->owner()) {
913+ log("CmdProposeTrade: Sending and receiving player are the same.\n");
914+ return;
915+ }
916+
917+ // TODO(sirver,trading): Maybe check connectivity between markets here and
918+ // report errors.
919+ game.propose_trade(trade_);
920+}
921+
922+CmdProposeTrade::CmdProposeTrade(StreamRead& des) : PlayerCommand(0, des.unsigned_8()) {
923+ trade_.initiator = des.unsigned_32();
924+ trade_.receiver = des.unsigned_32();
925+ trade_.items_to_send = deserialize_bill_of_materials(&des);
926+ trade_.items_to_receive = deserialize_bill_of_materials(&des);
927+ trade_.num_batches = des.signed_32();
928+}
929+
930+void CmdProposeTrade::serialize(StreamWrite& ser) {
931+ ser.unsigned_8(PLCMD_PROPOSE_TRADE);
932+ ser.unsigned_8(sender());
933+ ser.unsigned_32(trade_.initiator);
934+ ser.unsigned_32(trade_.receiver);
935+ serialize_bill_of_materials(trade_.items_to_send, &ser);
936+ serialize_bill_of_materials(trade_.items_to_receive, &ser);
937+ ser.signed_32(trade_.num_batches);
938+}
939+
940+void CmdProposeTrade::read(FileRead& /* fr */,
941+ EditorGameBase& /* egbase */,
942+ MapObjectLoader& /* mol */) {
943+ // TODO(sirver,trading): Implement this.
944+ NEVER_HERE();
945+}
946+
947+void CmdProposeTrade::write(FileWrite& /* fw */,
948+ EditorGameBase& /* egbase */,
949+ MapObjectSaver& /* mos */) {
950+ // TODO(sirver,trading): Implement this.
951+ NEVER_HERE();
952+}
953+
954 }
955
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 DescriptionIndex ware_;
961 Warehouse::StockPolicy policy_;
962 };
963+
964+struct CmdProposeTrade : PlayerCommand {
965+ CmdProposeTrade(uint32_t time, PlayerNumber pn, const Trade& trade);
966+
967+ QueueCommandTypes id() const override {
968+ return QueueCommandTypes::kProposeTrade;
969+ }
970+
971+ void execute(Game& game) override;
972+
973+ // Network (de-)serialization
974+ explicit CmdProposeTrade(StreamRead& des);
975+ void serialize(StreamWrite& ser) override;
976+
977+ // Savegame functions
978+ CmdProposeTrade();
979+ void write(FileWrite&, EditorGameBase&, MapObjectSaver&) override;
980+ void read(FileRead&, EditorGameBase&, MapObjectLoader&) override;
981+
982+private:
983+ Trade trade_;
984+};
985+
986 }
987
988 #endif // end of include guard: WL_LOGIC_PLAYERCOMMAND_H
989
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 return *new CmdEvictWorker();
995 case QueueCommandTypes::kMilitarysiteSetSoldierPreference:
996 return *new CmdMilitarySiteSetSoldierPreference();
997+ case QueueCommandTypes::kProposeTrade:
998+ return *new CmdProposeTrade();
999 case QueueCommandTypes::kSinkShip:
1000 return *new CmdShipSink();
1001 case QueueCommandTypes::kShipCancelExpedition:
1002
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
1008 kEvictWorker,
1009
1010- kMilitarysiteSetSoldierPreference, // 26
1011+ kMilitarysiteSetSoldierPreference,
1012+ kProposeTrade, // 27
1013
1014 kSinkShip = 121,
1015 kShipCancelExpedition,
1016
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+/*
1022+ * Copyright (C) 2006-2017 by the Widelands Development Team
1023+ *
1024+ * This program is free software; you can redistribute it and/or
1025+ * modify it under the terms of the GNU General Public License
1026+ * as published by the Free Software Foundation; either version 2
1027+ * of the License, or (at your option) any later version.
1028+ *
1029+ * This program is distributed in the hope that it will be useful,
1030+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1031+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1032+ * GNU General Public License for more details.
1033+ *
1034+ * You should have received a copy of the GNU General Public License
1035+ * along with this program; if not, write to the Free Software
1036+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
1037+ *
1038+ */
1039+
1040+#ifndef WL_LOGIC_TRADE_AGREEMENT_H
1041+#define WL_LOGIC_TRADE_AGREEMENT_H
1042+
1043+#include "logic/map_objects/map_object.h"
1044+
1045+namespace Widelands {
1046+
1047+// TODO(sirver,trading): Document everything in here.
1048+
1049+// Maximum number of a single ware that can be contained in a trade batch.
1050+constexpr int kMaxPerItemTradeBatchSize = 15;
1051+
1052+struct Trade {
1053+ BillOfMaterials items_to_send;
1054+ BillOfMaterials items_to_receive;
1055+ int num_batches;
1056+ Serial initiator;
1057+ Serial receiver;
1058+};
1059+
1060+// TODO(sirver,trading): This class should probably be private to 'Game'.
1061+struct TradeAgreement {
1062+ enum class State {
1063+ kProposed,
1064+ kRunning,
1065+ };
1066+
1067+ State state;
1068+ Trade trade;
1069+};
1070+
1071+} // namespace Widelands
1072+
1073+
1074+#endif // end of include guard: WL_LOGIC_TRADE_AGREEMENT_H
1075
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 }
1081 }
1082
1083+ // TODO(sirver,trading): Pull out and reuse this for market workers.
1084 assert(warehouse.incorporated_workers_.empty());
1085 {
1086 uint16_t const nrworkers = fr.unsigned_16();
1087
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 #include "logic/map_objects/immovable.h"
1093 #include "logic/map_objects/terrain_affinity.h"
1094 #include "logic/map_objects/tribes/carrier.h"
1095+#include "logic/map_objects/tribes/market.h"
1096 #include "logic/map_objects/tribes/ship.h"
1097 #include "logic/map_objects/tribes/soldier.h"
1098 #include "logic/map_objects/tribes/tribes.h"
1099@@ -594,6 +595,48 @@
1100 }
1101 return 0;
1102 }
1103+
1104+// Parses a table of name/count pairs as given from Lua.
1105+void parse_wares_workers(lua_State* L,
1106+ int table_index,
1107+ const TribeDescr& tribe,
1108+ InputMap* ware_workers_list,
1109+ bool is_ware) {
1110+ luaL_checktype(L, table_index, LUA_TTABLE);
1111+ lua_pushnil(L);
1112+ while (lua_next(L, table_index) != 0) {
1113+ if (is_ware) {
1114+ if (tribe.ware_index(luaL_checkstring(L, -2)) == INVALID_INDEX) {
1115+ report_error(L, "Illegal ware %s", luaL_checkstring(L, -2));
1116+ }
1117+ ware_workers_list->insert(
1118+ std::make_pair(std::make_pair(tribe.ware_index(luaL_checkstring(L, -2)),
1119+ Widelands::WareWorker::wwWARE),
1120+ luaL_checkuint32(L, -1)));
1121+ } else {
1122+ if (tribe.worker_index(luaL_checkstring(L, -2)) == INVALID_INDEX) {
1123+ report_error(L, "Illegal worker %s", luaL_checkstring(L, -2));
1124+ }
1125+ ware_workers_list->insert(
1126+ std::make_pair(std::make_pair(tribe.worker_index(luaL_checkstring(L, -2)),
1127+ Widelands::WareWorker::wwWORKER),
1128+ luaL_checkuint32(L, -1)));
1129+ }
1130+ lua_pop(L, 1);
1131+ }
1132+}
1133+
1134+BillOfMaterials parse_wares_as_bill_of_material(lua_State* L, int table_index,
1135+ const TribeDescr& tribe) {
1136+ InputMap input_map;
1137+ parse_wares_workers(L, table_index, tribe, &input_map, true /* is_ware */);
1138+ BillOfMaterials result;
1139+ for (const auto& pair : input_map) {
1140+ result.push_back(std::make_pair(pair.first.first, pair.second));
1141+ }
1142+ return result;
1143+}
1144+
1145 } // namespace
1146
1147 /*
1148@@ -782,7 +825,6 @@
1149
1150 // We either received, two items string,int:
1151 if (nargs == 3) {
1152-
1153 result = RequestedWareWorker::kSingle;
1154 if (is_ware) {
1155 if (tribe.ware_index(luaL_checkstring(L, 2)) == INVALID_INDEX) {
1156@@ -803,32 +845,7 @@
1157 } else {
1158 result = RequestedWareWorker::kList;
1159 // or we got a table with name:quantity
1160- luaL_checktype(L, 2, LUA_TTABLE);
1161- lua_pushnil(L);
1162- while (lua_next(L, 2) != 0) {
1163- if (is_ware) {
1164- if (tribe.ware_index(luaL_checkstring(L, -2)) == INVALID_INDEX) {
1165- report_error(L, "Illegal ware %s", luaL_checkstring(L, -2));
1166- }
1167- } else {
1168- if (tribe.worker_index(luaL_checkstring(L, -2)) == INVALID_INDEX) {
1169- report_error(L, "Illegal worker %s", luaL_checkstring(L, -2));
1170- }
1171- }
1172-
1173- if (is_ware) {
1174- ware_workers_list->insert(
1175- std::make_pair(std::make_pair(tribe.ware_index(luaL_checkstring(L, -2)),
1176- Widelands::WareWorker::wwWARE),
1177- luaL_checkuint32(L, -1)));
1178- } else {
1179- ware_workers_list->insert(
1180- std::make_pair(std::make_pair(tribe.worker_index(luaL_checkstring(L, -2)),
1181- Widelands::WareWorker::wwWORKER),
1182- luaL_checkuint32(L, -1)));
1183- }
1184- lua_pop(L, 1);
1185- }
1186+ parse_wares_workers(L, 2, tribe, ware_workers_list, is_ware);
1187 }
1188 return result;
1189 }
1190@@ -2762,6 +2779,8 @@
1191 trading over land with other players. See the parent classes for more
1192 properties.
1193 */
1194+// TODO(sirver,trading): Expose the properties of MarketDescription here once
1195+// the interface settles.
1196 const char LuaMarketDescription::className[] = "MarketDescription";
1197 const MethodType<LuaMarketDescription> LuaMarketDescription::Methods[] = {
1198 {nullptr, nullptr},
1199@@ -5083,6 +5102,7 @@
1200 */
1201 const char LuaMarket::className[] = "Market";
1202 const MethodType<LuaMarket> LuaMarket::Methods[] = {
1203+ METHOD(LuaMarket, propose_trade),
1204 // TODO(sirver,trading): Implement and fix documentation.
1205 // METHOD(LuaMarket, set_wares),
1206 // METHOD(LuaMarket, get_wares),
1207@@ -5106,6 +5126,35 @@
1208 ==========================================================
1209 */
1210
1211+/* RST
1212+ .. method:: propose_trade(other_market, num_batches, items_to_send, items_to_receive)
1213+
1214+ TODO(sirver,trading): document
1215+
1216+ :returns: :const:`nil`
1217+*/
1218+int LuaMarket::propose_trade(lua_State* L) {
1219+ if (lua_gettop(L) != 5) {
1220+ report_error(L, "Takes 4 arguments.");
1221+ }
1222+ Game& game = get_game(L);
1223+ Market* self = get(L, game);
1224+ Market* other_market = (*get_user_class<LuaMarket>(L, 2))->get(L, game);
1225+ const int num_batches = luaL_checkinteger(L, 3);
1226+
1227+ const BillOfMaterials items_to_send = parse_wares_as_bill_of_material(L, 4, self->owner().tribe());
1228+ // TODO(sirver,trading): unsure if correct. Test inter-tribe trading, i.e.
1229+ // barbarians trading with empire, but shipping atlantean only wares.
1230+ const BillOfMaterials items_to_receive = parse_wares_as_bill_of_material(L, 5, self->owner().tribe());
1231+ const int trade_id = game.propose_trade(
1232+ Trade{items_to_send, items_to_receive, num_batches, self->serial(), other_market->serial()});
1233+
1234+ // TODO(sirver,trading): Wrap 'Trade' into its own Lua class?
1235+ lua_pushint32(L, trade_id);
1236+ return 1;
1237+}
1238+
1239+
1240 /*
1241 ==========================================================
1242 C METHODS
1243
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 #include "logic/game.h"
1249 #include "logic/map_objects/tribes/constructionsite.h"
1250 #include "logic/map_objects/tribes/dismantlesite.h"
1251+#include "logic/map_objects/tribes/market.h"
1252 #include "logic/map_objects/tribes/militarysite.h"
1253 #include "logic/map_objects/tribes/productionsite.h"
1254 #include "logic/map_objects/tribes/ship.h"
1255@@ -1125,6 +1126,7 @@
1256 /*
1257 * Lua Methods
1258 */
1259+ int propose_trade(lua_State* L);
1260
1261 /*
1262 * C Methods
1263
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 case Widelands::Message::Type::kWarfareSiteDefeated:
1269 case Widelands::Message::Type::kWarfareSiteLost:
1270 case Widelands::Message::Type::kWarfareUnderAttack:
1271+ case Widelands::Message::Type::kTradeOfferReceived:
1272 set_filter_messages_tooltips();
1273 message_filter_ = Widelands::Message::Type::kAllMessages;
1274 geologistsbtn_->set_perm_pressed(false);
1275@@ -580,6 +581,7 @@
1276 case Widelands::Message::Type::kWarfareSiteDefeated:
1277 case Widelands::Message::Type::kWarfareSiteLost:
1278 case Widelands::Message::Type::kWarfareUnderAttack:
1279+ case Widelands::Message::Type::kTradeOfferReceived:
1280 return "images/wui/messages/message_new.png";
1281 }
1282 NEVER_HERE();
1283
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 &registry,
1289 kPlotWidth + 2 * kSpacing,
1290 270,
1291- _("Ware Statistics")),
1292- parent_(&parent) {
1293+ _("Ware Statistics")) {
1294 uint8_t const nr_wares = parent.get_player()->egbase().tribes().nrwares();
1295
1296 // Init color sets
1297
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 void set_time(int32_t);
1303
1304 private:
1305- InteractivePlayer* parent_;
1306 WuiPlotArea* plot_production_;
1307 WuiPlotArea* plot_consumption_;
1308 WuiPlotArea* plot_stock_;
1309
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-include "scripting/lunit.lua"
1315 include "scripting/coroutine.lua"
1316 include "scripting/infrastructure.lua"
1317+include "scripting/lunit.lua"
1318 include "scripting/ui.lua"
1319
1320 game = wl.Game()
1321@@ -22,7 +22,17 @@
1322 end
1323 end
1324
1325+function place_markets()
1326+ prefilled_buildings(p1, { "barbarians_market", 22, 27 })
1327+ market_p1 = map:get_field(22, 27).immovable
1328+ connected_road(p1, market_p1.flag, "tr,tl|", true)
1329+
1330+ prefilled_buildings(p2, { "barbarians_market", 31, 27 })
1331+ market_p2 = map:get_field(31, 27).immovable
1332+ connected_road(p2, market_p2.flag, "tr,tl|", true)
1333+end
1334+
1335 full_headquarters(p1, 22, 25)
1336-full_headquarters(p2, 32, 25)
1337+full_headquarters(p2, 31, 25)
1338
1339 game.desired_speed = 50000
1340
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 run(function()
1346 sleep(2000)
1347
1348- market = p2:place_building("barbarians_market", map:get_field(35, 25), true, true)
1349- connected_road(p2, market.flag, "l,l,l|", true)
1350+ market = p2:place_building("barbarians_market", map:get_field(31, 27), true, true)
1351+ connected_road(p2, market.flag, "tr,tl|", true)
1352
1353 while #p2:get_buildings("barbarians_market") == 0 do
1354 sleep(10000)
1355
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+run(function()
1361+ sleep(2000)
1362+ place_markets()
1363+ market_p2:propose_trade(market_p1, 5, { log = 3 }, { granite = 2, iron = 1 })
1364+
1365+ local p1_initial = {
1366+ iron = p1:get_wares("iron"),
1367+ log = p1:get_wares("log"),
1368+ granite = p1:get_wares("granite"),
1369+ }
1370+ local p2_initial = {
1371+ iron = p1:get_wares("iron"),
1372+ log = p1:get_wares("log"),
1373+ granite = p1:get_wares("granite"),
1374+ }
1375+
1376+ -- We await until one ware we trade has the right count for one player.
1377+ -- Then, we'll sleep half as long as we already waited to make sure that no
1378+ -- additional batches are shipped. Then we check all stocks for the correct
1379+ -- numbers.
1380+ local start_time = game.time
1381+ while p2:get_wares("iron") - p2_initial["iron"] < 5 do
1382+ sleep(10000)
1383+ end
1384+
1385+ sleep(math.ceil((game.time - start_time) / 2))
1386+
1387+ assert_equal(5, p2:get_wares("iron") - p2_initial["iron"])
1388+ assert_equal(10, p2:get_wares("granite") - p2_initial["granite"])
1389+ assert_equal(-15, p2:get_wares("log") - p2_initial["log"])
1390+
1391+ assert_equal(-5, p1:get_wares("iron") - p1_initial["iron"])
1392+ assert_equal(-10, p1:get_wares("granite") - p1_initial["granite"])
1393+ assert_equal(15, p1:get_wares("log") - p1_initial["log"])
1394+
1395+ print("# All Tests passed.")
1396+ wl.ui.MapView():close()
1397+end)
1398+

Subscribers

People subscribed via source and target branches

to status/vote changes: