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
=== modified file 'data/tribes/buildings/markets/barbarians/market/init.lua'
--- data/tribes/buildings/markets/barbarians/market/init.lua 2017-09-15 19:53:28 +0000
+++ data/tribes/buildings/markets/barbarians/market/init.lua 2017-10-03 20:35:46 +0000
@@ -50,7 +50,5 @@
50 prohibited_till = 100050 prohibited_till = 1000
51 },51 },
5252
53 working_positions = {53 carrier = "barbarians_ox",
54 barbarians_ox = 10,
55 },
56}54}
5755
=== modified file 'src/economy/request.h'
--- src/economy/request.h 2017-01-25 18:55:59 +0000
+++ src/economy/request.h 2017-10-03 20:35:46 +0000
@@ -123,6 +123,7 @@
123 // callbacks for WareInstance/Worker code123 // callbacks for WareInstance/Worker code
124 void transfer_finish(Game&, Transfer&);124 void transfer_finish(Game&, Transfer&);
125 void transfer_fail(Game&, Transfer&);125 void transfer_fail(Game&, Transfer&);
126 void cancel_transfer(uint32_t idx);
126127
127 void set_requirements(const Requirements& r) {128 void set_requirements(const Requirements& r) {
128 requirements_ = r;129 requirements_ = r;
@@ -131,13 +132,9 @@
131 return requirements_;132 return requirements_;
132 }133 }
133134
135
134private:136private:
135 int32_t get_base_required_time(EditorGameBase&, uint32_t nr) const;137 int32_t get_base_required_time(EditorGameBase&, uint32_t nr) const;
136
137public:
138 void cancel_transfer(uint32_t idx);
139
140private:
141 void remove_transfer(uint32_t idx);138 void remove_transfer(uint32_t idx);
142 uint32_t find_transfer(Transfer&);139 uint32_t find_transfer(Transfer&);
143140
144141
=== modified file 'src/economy/ware_instance.cc'
--- src/economy/ware_instance.cc 2017-08-16 10:14:29 +0000
+++ src/economy/ware_instance.cc 2017-10-03 20:35:46 +0000
@@ -309,8 +309,9 @@
309309
310 // Update whether we have a Supply or not310 // Update whether we have a Supply or not
311 if (!transfer_ || !transfer_->get_request()) {311 if (!transfer_ || !transfer_->get_request()) {
312 if (!supply_)312 if (!supply_) {
313 supply_ = new IdleWareSupply(*this);313 supply_ = new IdleWareSupply(*this);
314 }
314 } else {315 } else {
315 delete supply_;316 delete supply_;
316 supply_ = nullptr;317 supply_ = nullptr;
317318
=== modified file 'src/game_io/game_class_packet.cc'
--- src/game_io/game_class_packet.cc 2017-01-25 18:55:59 +0000
+++ src/game_io/game_class_packet.cc 2017-10-03 20:35:46 +0000
@@ -61,6 +61,8 @@
61 // Write gametime61 // Write gametime
62 fw.unsigned_32(game.gametime_);62 fw.unsigned_32(game.gametime_);
6363
64 // TODO(sirver,trading): save/load trade_agreements and related data.
65
64 // We do not care for players, since they were set66 // We do not care for players, since they were set
65 // on game initialization to match Map::scenario_player_[names|tribes]67 // on game initialization to match Map::scenario_player_[names|tribes]
66 // or vice versa, so this is handled by map loader68 // or vice versa, so this is handled by map loader
6769
=== modified file 'src/logic/CMakeLists.txt'
--- src/logic/CMakeLists.txt 2017-09-15 12:33:58 +0000
+++ src/logic/CMakeLists.txt 2017-10-03 20:35:46 +0000
@@ -95,14 +95,14 @@
95 findimmovable.h95 findimmovable.h
96 findnode.cc96 findnode.cc
97 findnode.h97 findnode.h
98 game.cc
99 game.h
98 game_data_error.cc100 game_data_error.cc
99 game_data_error.h101 game_data_error.h
100 game.cc102 map.cc
101 game.h103 map.h
102 map_revision.cc104 map_revision.cc
103 map_revision.h105 map_revision.h
104 map.cc
105 map.h
106 mapastar.cc106 mapastar.cc
107 mapastar.h107 mapastar.h
108 mapdifferenceregion.cc108 mapdifferenceregion.cc
@@ -114,18 +114,18 @@
114 mapregion.h114 mapregion.h
115 maptriangleregion.cc115 maptriangleregion.cc
116 maptriangleregion.h116 maptriangleregion.h
117 message.h
117 message_id.h118 message_id.h
118 message_queue.h119 message_queue.h
119 message.h
120 nodecaps.h120 nodecaps.h
121 objective.h121 objective.h
122 path.cc122 path.cc
123 path.h123 path.h
124 pathfield.cc124 pathfield.cc
125 pathfield.h125 pathfield.h
126 player_area.h
127 player.cc126 player.cc
128 player.h127 player.h
128 player_area.h
129 playercommand.cc129 playercommand.cc
130 playercommand.h130 playercommand.h
131 playersmanager.cc131 playersmanager.cc
@@ -138,6 +138,7 @@
138 roadtype.h138 roadtype.h
139 save_handler.cc139 save_handler.cc
140 save_handler.h140 save_handler.h
141 trade_agreement.h
141 widelands_geometry_io.cc142 widelands_geometry_io.cc
142 widelands_geometry_io.h143 widelands_geometry_io.h
143 map_objects/bob.cc144 map_objects/bob.cc
144145
=== modified file 'src/logic/game.cc'
--- src/logic/game.cc 2017-08-30 12:01:47 +0000
+++ src/logic/game.cc 2017-10-03 20:35:46 +0000
@@ -49,6 +49,7 @@
49#include "logic/cmd_luascript.h"49#include "logic/cmd_luascript.h"
50#include "logic/game_settings.h"50#include "logic/game_settings.h"
51#include "logic/map_objects/tribes/carrier.h"51#include "logic/map_objects/tribes/carrier.h"
52#include "logic/map_objects/tribes/market.h"
52#include "logic/map_objects/tribes/militarysite.h"53#include "logic/map_objects/tribes/militarysite.h"
53#include "logic/map_objects/tribes/ship.h"54#include "logic/map_objects/tribes/ship.h"
54#include "logic/map_objects/tribes/soldier.h"55#include "logic/map_objects/tribes/soldier.h"
@@ -753,6 +754,87 @@
753 get_gametime(), ship.get_owner()->player_number(), ship.serial()));754 get_gametime(), ship.get_owner()->player_number(), ship.serial()));
754}755}
755756
757void Game::send_player_propose_trade(const Trade& trade) {
758 auto* object = objects().get_object(trade.initiator);
759 assert(object != nullptr);
760 send_player_command(
761 *new CmdProposeTrade(get_gametime(), object->get_owner()->player_number(), trade));
762}
763
764int Game::propose_trade(const Trade& trade) {
765 // TODO(sirver,trading): Check if a trade is possible (i.e. if there is a
766 // path between the two markets);
767 const int id = next_trade_agreement_id_;
768 ++next_trade_agreement_id_;
769
770 auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator));
771 auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver));
772 // This is only ever called through a PlayerCommand and that already made
773 // sure that the objects still exist. Since no time has passed, they should
774 // not have vanished under us.
775 assert(initiator != nullptr);
776 assert(receiver != nullptr);
777
778 receiver->removed.connect(
779 [this, id](const uint32_t /* serial */) { cancel_trade(id); });
780 initiator->removed.connect(
781 [this, id](const uint32_t /* serial */) { cancel_trade(id); });
782
783 receiver->send_message(*this, Message::Type::kTradeOfferReceived, _("Trade Offer"),
784 receiver->descr().icon_filename(), receiver->descr().descname(),
785 _("This market has received a new trade offer."), true);
786 trade_agreements_[id] = TradeAgreement{TradeAgreement::State::kProposed, trade};
787
788 // TODO(sirver,trading): this should be done through another player_command, but I
789 // want to get to the trade logic implementation now.
790 accept_trade(id);
791 return id;
792}
793
794void Game::accept_trade(const int trade_id) {
795 auto it = trade_agreements_.find(trade_id);
796 if (it == trade_agreements_.end()) {
797 log("Game::accept_trade: Trade %d has vanished. Ignoring.\n", trade_id);
798 return;
799 }
800 const Trade& trade = it->second.trade;
801 auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator));
802 auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver));
803 if (initiator == nullptr || receiver == nullptr) {
804 cancel_trade(trade_id);
805 return;
806 }
807
808 initiator->new_trade(trade_id, trade.items_to_send, trade.num_batches, trade.receiver);
809 receiver->new_trade(trade_id, trade.items_to_receive, trade.num_batches, trade.initiator);
810
811 // TODO(sirver,trading): Message the users that the trade has been accepted.
812}
813
814void Game::cancel_trade(int trade_id) {
815 // The trade id might be long gone - since we never disconnect from the
816 // 'removed' signal of the two buildings, we might be invoked long after the
817 // trade was deleted for other reasons.
818 const auto it = trade_agreements_.find(trade_id);
819 if (it == trade_agreements_.end()) {
820 return;
821 }
822 const auto& trade = it->second.trade;
823
824 auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator));
825 if (initiator != nullptr) {
826 initiator->cancel_trade(trade_id);
827 // TODO(sirver,trading): Send message to owner that the trade has been canceled.
828 }
829
830 auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver));
831 if (receiver != nullptr) {
832 receiver->cancel_trade(trade_id);
833 // TODO(sirver,trading): Send message to owner that the trade has been canceled.
834 }
835 trade_agreements_.erase(trade_id);
836}
837
756LuaGameInterface& Game::lua() {838LuaGameInterface& Game::lua() {
757 return static_cast<LuaGameInterface&>(EditorGameBase::lua());839 return static_cast<LuaGameInterface&>(EditorGameBase::lua());
758}840}
759841
=== modified file 'src/logic/game.h'
--- src/logic/game.h 2017-08-18 00:17:39 +0000
+++ src/logic/game.h 2017-10-03 20:35:46 +0000
@@ -27,6 +27,7 @@
27#include "logic/cmd_queue.h"27#include "logic/cmd_queue.h"
28#include "logic/editor_game_base.h"28#include "logic/editor_game_base.h"
29#include "logic/save_handler.h"29#include "logic/save_handler.h"
30#include "logic/trade_agreement.h"
30#include "random/random.h"31#include "random/random.h"
31#include "scripting/logic.h"32#include "scripting/logic.h"
3233
@@ -207,6 +208,7 @@
207 void send_player_ship_explore_island(Ship&, IslandExploreDirection);208 void send_player_ship_explore_island(Ship&, IslandExploreDirection);
208 void send_player_sink_ship(Ship&);209 void send_player_sink_ship(Ship&);
209 void send_player_cancel_expedition_ship(Ship&);210 void send_player_cancel_expedition_ship(Ship&);
211 void send_player_propose_trade(const Trade& trade);
210212
211 InteractivePlayer* get_ipl();213 InteractivePlayer* get_ipl();
212214
@@ -244,6 +246,11 @@
244246
245 void set_auto_speed(bool);247 void set_auto_speed(bool);
246248
249 // TODO(sirver,trading): document these functions once the interface settles.
250 int propose_trade(const Trade& trade);
251 void accept_trade(int trade_id);
252 void cancel_trade(int trade_id);
253
247private:254private:
248 void sync_reset();255 void sync_reset();
249256
@@ -308,6 +315,9 @@
308 std::unique_ptr<ReplayWriter> replaywriter_;315 std::unique_ptr<ReplayWriter> replaywriter_;
309316
310 GeneralStatsVector general_stats_;317 GeneralStatsVector general_stats_;
318 int next_trade_agreement_id_ = 1;
319 // Maps from trade agreement id to the agreement.
320 std::map<int, TradeAgreement> trade_agreements_;
311321
312 /// For save games and statistics generation322 /// For save games and statistics generation
313 std::string win_condition_displayname_;323 std::string win_condition_displayname_;
314324
=== modified file 'src/logic/map_objects/bob.cc'
--- src/logic/map_objects/bob.cc 2017-08-20 08:34:02 +0000
+++ src/logic/map_objects/bob.cc 2017-10-03 20:35:46 +0000
@@ -390,6 +390,10 @@
390 state.ivar1 = 0;390 state.ivar1 = 0;
391}391}
392392
393bool Bob::is_idle() {
394 return get_state(taskIdle);
395}
396
393/**397/**
394 * Move along a predefined path.398 * Move along a predefined path.
395 * \par ivar1 the step number.399 * \par ivar1 the step number.
396400
=== modified file 'src/logic/map_objects/bob.h'
--- src/logic/map_objects/bob.h 2017-06-24 08:47:46 +0000
+++ src/logic/map_objects/bob.h 2017-10-03 20:35:46 +0000
@@ -278,6 +278,7 @@
278 // TODO(feature-Hasi50): correct (?) Send a signal that may switch to some other \ref Task278 // TODO(feature-Hasi50): correct (?) Send a signal that may switch to some other \ref Task
279 void send_signal(Game&, char const*);279 void send_signal(Game&, char const*);
280 void start_task_idle(Game&, uint32_t anim, int32_t timeout);280 void start_task_idle(Game&, uint32_t anim, int32_t timeout);
281 bool is_idle();
281282
282 /// This can fail (and return false). Therefore the caller must check the283 /// This can fail (and return false). Therefore the caller must check the
283 /// result and find something else for the bob to do. Otherwise there will284 /// result and find something else for the bob to do. Otherwise there will
284285
=== modified file 'src/logic/map_objects/tribes/market.cc'
--- src/logic/map_objects/tribes/market.cc 2017-09-18 13:43:08 +0000
+++ src/logic/map_objects/tribes/market.cc 2017-10-03 20:35:46 +0000
@@ -19,8 +19,11 @@
1919
20#include "logic/map_objects/tribes/market.h"20#include "logic/map_objects/tribes/market.h"
2121
22#include <memory>
23
22#include "base/i18n.h"24#include "base/i18n.h"
23#include "logic/map_objects/tribes/productionsite.h"25#include "logic/map_objects/tribes/productionsite.h"
26#include "logic/map_objects/tribes/tribes.h"
2427
25namespace Widelands {28namespace Widelands {
2629
@@ -30,7 +33,12 @@
30 : BuildingDescr(init_descname, MapObjectType::MARKET, table, egbase) {33 : BuildingDescr(init_descname, MapObjectType::MARKET, table, egbase) {
31 i18n::Textdomain td("tribes");34 i18n::Textdomain td("tribes");
3235
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"));
37 if (!egbase.tribes().worker_exists(woi)) {
38 throw wexception("The tribe does not define the worker in 'carrier'.");
39 }
40 carrier_ = woi;
41
34 // TODO(sirver,trading): Add actual logic here.42 // TODO(sirver,trading): Add actual logic here.
35}43}
3644
@@ -38,10 +46,223 @@
38 return *new Market(*this);46 return *new Market(*this);
39}47}
4048
49int Market::TradeOrder::num_wares_per_batch() const {
50 int sum = 0;
51 for (const auto& item_pair : items) {
52 sum += item_pair.second;
53 }
54 return sum;
55}
56
57bool Market::TradeOrder::fulfilled() const {
58 return num_shipped_batches == initial_num_batches;
59}
60
61// TODO(sirver,trading): This needs to implement 'set_economy'. Maybe common code can be shared.
41Market::Market(const MarketDescr& the_descr) : Building(the_descr) {62Market::Market(const MarketDescr& the_descr) : Building(the_descr) {
42}63}
4364
44Market::~Market() {65Market::~Market() {
45}66}
4667
68void Market::new_trade(const int trade_id,
69 const BillOfMaterials& items,
70 const int num_batches,
71 const Serial other_side) {
72 trade_orders_[trade_id] = TradeOrder{items, num_batches, 0, other_side, 0, nullptr, {}};
73 auto& trade_order = trade_orders_[trade_id];
74
75 // Request one worker for each item in a batch.
76 trade_order.worker_request.reset(
77 new Request(*this, descr().carrier(), Market::worker_arrived_callback, wwWORKER));
78 trade_order.worker_request->set_count(trade_order.num_wares_per_batch());
79
80 // Make sure we have a wares queue for each item in this.
81 for (const auto& entry : items) {
82 ensure_wares_queue_exists(entry.first);
83 }
84}
85
86void Market::cancel_trade(const int trade_id) {
87 // TODO(sirver,trading): Launch workers, release no longer required wares and delete now unneeded 'WaresQueue's
88 trade_orders_.erase(trade_id);
89}
90
91void Market::worker_arrived_callback(
92 Game& game, Request& rq, DescriptionIndex /* widx */, Worker* const w, PlayerImmovable& target) {
93 auto& market = dynamic_cast<Market&>(target);
94
95 assert(w);
96 assert(w->get_location(game) == &market);
97
98 for (auto& trade_order_pair : market.trade_orders_) {
99 auto& trade_order = trade_order_pair.second;
100 if (trade_order.worker_request.get() != &rq) {
101 continue;
102 }
103
104 if (rq.get_count() == 0) {
105 // Erase this request.
106 trade_order.worker_request.reset();
107 }
108 w->start_task_idle(game, 0, -1);
109 trade_order.workers.push_back(w);
110 Notifications::publish(NoteBuilding(market.serial(), NoteBuilding::Action::kWorkersChanged));
111 market.try_launching_batch(&game);
112 return;
113 }
114 NEVER_HERE(); // We should have found and handled a match by now.
115}
116
117void Market::ware_arrived_callback(Game& g, InputQueue*, DescriptionIndex, Worker*, void* data) {
118 Market& market = *static_cast<Market*>(data);
119 market.try_launching_batch(&g);
120}
121
122void Market::try_launching_batch(Game* game) {
123 for (auto& pair : trade_orders_) {
124 if (!is_ready_to_launch_batch(pair.first)) {
125 continue;
126 }
127
128 auto* other_market =
129 dynamic_cast<Market*>(game->objects().get_object(pair.second.other_side));
130 if (other_market == nullptr) {
131 // TODO(sirver,trading): Can this even happen? Where is this function called from?
132 // The other market seems to have vanished. The game tracks this and
133 // should soon delete this trade request from us. We just ignore it.
134 continue;
135 }
136 if (!other_market->is_ready_to_launch_batch(pair.first)) {
137 continue;
138 }
139 launch_batch(pair.first, game);
140 other_market->launch_batch(pair.first, game);
141 break;
142 }
143}
144
145bool Market::is_ready_to_launch_batch(const int trade_id) {
146 const auto it = trade_orders_.find(trade_id);
147 if (it == trade_orders_.end()) {
148 return false;
149 }
150 auto& trade_order = it->second;
151 assert(!trade_order.fulfilled());
152
153 // Do we have all necessary wares for a batch?
154 for (const auto& item_pair : trade_order.items) {
155 const auto wares_it = wares_queue_.find(item_pair.first);
156 if (wares_it == wares_queue_.end()) {
157 return false;
158 }
159 if (wares_it->second->get_filled() < item_pair.second) {
160 return false;
161 }
162 }
163
164 // Do we have enough people to carry wares?
165 int num_available_carriers = 0;
166 for (auto* worker : trade_order.workers) {
167 num_available_carriers += worker->is_idle() ? 1 : 0;
168 }
169 return num_available_carriers == trade_order.num_wares_per_batch();
170}
171
172void Market::launch_batch(const int trade_id, Game* game) {
173 assert(is_ready_to_launch_batch(trade_id));
174 auto& trade_order = trade_orders_.at(trade_id);
175
176 // Do we have all necessary wares for a batch?
177 int worker_index = 0;
178 for (const auto& item_pair : trade_order.items) {
179 for (size_t i = 0; i < item_pair.second; ++i) {
180 Worker* carrier = trade_order.workers.at(worker_index);
181 ++worker_index;
182 assert(carrier->is_idle());
183
184 // Give the carrier a ware.
185 WareInstance* ware =
186 new WareInstance(item_pair.first, game->tribes().get_ware_descr(item_pair.first));
187 ware->init(*game);
188 carrier->set_carried_ware(*game, ware);
189
190 // We have to remove this item from our economy. Otherwise it would be
191 // considered idle (since it has no transport associated with it) and
192 // the engine would want to transfer it to the next warehouse.
193 ware->set_economy(nullptr);
194 wares_queue_.at(item_pair.first)
195 ->set_filled(wares_queue_.at(item_pair.first)->get_filled() - 1);
196
197 // Send the carrier going.
198 carrier->reset_tasks(*game);
199 carrier->start_task_carry_trade_item(
200 *game, trade_id, ObjectPointer(game->objects().get_object(trade_order.other_side)));
201 }
202 }
203}
204
205void Market::ensure_wares_queue_exists(int ware_index) {
206 if (wares_queue_.count(ware_index) > 0) {
207 return;
208 }
209 wares_queue_[ware_index] =
210 std::unique_ptr<WaresQueue>(new WaresQueue(*this, ware_index, kMaxPerItemTradeBatchSize));
211 wares_queue_[ware_index]->set_callback(Market::ware_arrived_callback, this);
212}
213
214InputQueue& Market::inputqueue(DescriptionIndex index, WareWorker ware_worker) {
215 assert(ware_worker == wwWARE);
216 auto it = wares_queue_.find(index);
217 if (it != wares_queue_.end()) {
218 return *it->second;
219 }
220 // The parent will throw an exception.
221 return Building::inputqueue(index, ware_worker);
222}
223
224void Market::cleanup(EditorGameBase& egbase) {
225 for (auto& pair : wares_queue_) {
226 pair.second->cleanup();
227 }
228 Building::cleanup(egbase);
229}
230
231void Market::traded_ware_arrived(const int trade_id, const DescriptionIndex ware_index, Game* game) {
232 auto& trade_order = trade_orders_.at(trade_id);
233
234 WareInstance* ware = new WareInstance(ware_index, game->tribes().get_ware_descr(ware_index));
235 ware->init(*game);
236
237 // TODO(sirver,trading): This is a hack. We should have a worker that
238 // carriers stuff out. At the moment this assumes this market is barbarians
239 // (which is always correct right now), creates a carrier for each received
240 // ware to drop it off. The carrier then leaves the building and goes home.
241 const WorkerDescr& w_desc =
242 *game->tribes().get_worker_descr(game->tribes().worker_index("barbarians_carrier"));
243 auto& worker = w_desc.create(*game, owner(), this, position_);
244 worker.start_task_dropoff(*game, *ware);
245 trade_order.received_traded_wares_in_this_batch += 1;
246 owner().ware_produced(ware_index);
247
248 auto* other_market = dynamic_cast<Market*>(game->objects().get_object(trade_order.other_side));
249 assert(other_market != nullptr);
250 other_market->owner().ware_consumed(ware_index, 1);
251 auto& other_trade_order = other_market->trade_orders_.at(trade_id);
252 if (trade_order.received_traded_wares_in_this_batch == other_trade_order.num_wares_per_batch() &&
253 other_trade_order.received_traded_wares_in_this_batch == trade_order.num_wares_per_batch()) {
254 // This batch is completed.
255 ++trade_order.num_shipped_batches;
256 trade_order.received_traded_wares_in_this_batch = 0;
257 ++other_trade_order.num_shipped_batches;
258 other_trade_order.received_traded_wares_in_this_batch = 0;
259 if (trade_order.fulfilled()) {
260 assert(other_trade_order.fulfilled());
261 // TODO(sirver,trading): This is not quite correct. This should for
262 // example send a differnet message than actually canceling a trade.
263 game->cancel_trade(trade_id);
264 }
265 }
266}
267
47} // namespace Widelands268} // namespace Widelands
48269
=== modified file 'src/logic/map_objects/tribes/market.h'
--- src/logic/map_objects/tribes/market.h 2017-09-15 12:33:58 +0000
+++ src/logic/map_objects/tribes/market.h 2017-10-03 20:35:46 +0000
@@ -20,6 +20,10 @@
20#ifndef WL_LOGIC_MAP_OBJECTS_TRIBES_MARKET_H20#ifndef WL_LOGIC_MAP_OBJECTS_TRIBES_MARKET_H
21#define WL_LOGIC_MAP_OBJECTS_TRIBES_MARKET_H21#define WL_LOGIC_MAP_OBJECTS_TRIBES_MARKET_H
2222
23#include <memory>
24
25#include "economy/request.h"
26#include "economy/wares_queue.h"
23#include "logic/map_objects/tribes/building.h"27#include "logic/map_objects/tribes/building.h"
2428
25namespace Widelands {29namespace Widelands {
@@ -32,8 +36,10 @@
3236
33 Building& create_object() const override;37 Building& create_object() const override;
3438
39 DescriptionIndex carrier() const { return carrier_; }
40
35private:41private:
36 BillOfMaterials working_positions_;42 DescriptionIndex carrier_;
37};43};
3844
39class Market : public Building {45class Market : public Building {
@@ -42,7 +48,48 @@
42 explicit Market(const MarketDescr& descr);48 explicit Market(const MarketDescr& descr);
43 ~Market() override;49 ~Market() override;
4450
51 void new_trade(int trade_id, const BillOfMaterials& items, int num_batches, Serial other_side);
52 void cancel_trade(int trade_id);
53
54 InputQueue& inputqueue(DescriptionIndex, WareWorker) override;
55 void cleanup(EditorGameBase&) override;
56
57 void try_launching_batch(Game* game);
58 void traded_ware_arrived(int trade_id, DescriptionIndex ware_index, Game* game);
59
45private:60private:
61 struct TradeOrder {
62 BillOfMaterials items;
63 int initial_num_batches;
64 int num_shipped_batches;
65 Serial other_side;
66
67 int received_traded_wares_in_this_batch;
68
69 // The invariant here is that worker.size() + worker_request.get_count()
70 // == 'num_wares_per_batch()'
71 std::unique_ptr<Request> worker_request;
72 std::vector<Worker*> workers;
73
74 // The number of individual wares in 'items', i.e. the sum of all '.second's.
75 int num_wares_per_batch() const;
76
77 // True if the 'num_shipped_batches' equals the 'initial_num_batches'
78 bool fulfilled() const;
79 };
80
81 static void
82 worker_arrived_callback(Game&, Request&, DescriptionIndex, Worker*, PlayerImmovable&);
83 static void
84 ware_arrived_callback(Game& g, InputQueue* q, DescriptionIndex ware, Worker* worker, void* data);
85
86 void ensure_wares_queue_exists(int ware_index);
87 bool is_ready_to_launch_batch(int trade_id);
88 void launch_batch(int trade_id, Game* game);
89
90 std::map<int, TradeOrder> trade_orders_; // Key is 'trade_id's.
91 std::map<int, std::unique_ptr<WaresQueue>> wares_queue_; // Key is 'ware_index'.
92
46 DISALLOW_COPY_AND_ASSIGN(Market);93 DISALLOW_COPY_AND_ASSIGN(Market);
47};94};
4895
4996
=== modified file 'src/logic/map_objects/tribes/productionsite.cc'
--- src/logic/map_objects/tribes/productionsite.cc 2017-09-15 12:33:58 +0000
+++ src/logic/map_objects/tribes/productionsite.cc 2017-10-03 20:35:46 +0000
@@ -49,8 +49,9 @@
4949
50constexpr size_t STATISTICS_VECTOR_LENGTH = 20;50constexpr size_t STATISTICS_VECTOR_LENGTH = 20;
5151
52} // namespace52// Parses the descriptions of the working positions from 'items_table' and
5353// fills in 'working_positions'. Throws an error if the table contains invalid
54// values.
54void parse_working_positions(const EditorGameBase& egbase,55void parse_working_positions(const EditorGameBase& egbase,
55 LuaTable* items_table,56 LuaTable* items_table,
56 BillOfMaterials* working_positions) {57 BillOfMaterials* working_positions) {
@@ -71,6 +72,8 @@
71 }72 }
72}73}
7374
75} // namespace
76
74/*77/*
75==============================================================================78==============================================================================
7679
7780
=== modified file 'src/logic/map_objects/tribes/productionsite.h'
--- src/logic/map_objects/tribes/productionsite.h 2017-09-18 13:43:08 +0000
+++ src/logic/map_objects/tribes/productionsite.h 2017-10-03 20:35:46 +0000
@@ -375,13 +375,6 @@
375 }375 }
376};376};
377377
378// Parses the descriptions of the working positions from 'items_table' and
379// fills in 'working_positions'. Throws an error if the table contains invalid
380// values.
381void parse_working_positions(const EditorGameBase& egbase,
382 LuaTable* items_table,
383 BillOfMaterials* working_positions);
384
385} // namespace Widelands378} // namespace Widelands
386379
387#endif // end of include guard: WL_LOGIC_MAP_OBJECTS_TRIBES_PRODUCTIONSITE_H380#endif // end of include guard: WL_LOGIC_MAP_OBJECTS_TRIBES_PRODUCTIONSITE_H
388381
=== modified file 'src/logic/map_objects/tribes/tribes.cc'
--- src/logic/map_objects/tribes/tribes.cc 2017-09-15 12:33:58 +0000
+++ src/logic/map_objects/tribes/tribes.cc 2017-10-03 20:35:46 +0000
@@ -24,6 +24,7 @@
24#include "base/wexception.h"24#include "base/wexception.h"
25#include "graphic/graphic.h"25#include "graphic/graphic.h"
26#include "logic/game_data_error.h"26#include "logic/game_data_error.h"
27#include "logic/map_objects/tribes/market.h"
2728
28namespace Widelands {29namespace Widelands {
2930
3031
=== modified file 'src/logic/map_objects/tribes/tribes.h'
--- src/logic/map_objects/tribes/tribes.h 2017-09-15 12:33:58 +0000
+++ src/logic/map_objects/tribes/tribes.h 2017-10-03 20:35:46 +0000
@@ -38,7 +38,6 @@
38#include "logic/map_objects/tribes/tribe_descr.h"38#include "logic/map_objects/tribes/tribe_descr.h"
39#include "logic/map_objects/tribes/ware_descr.h"39#include "logic/map_objects/tribes/ware_descr.h"
40#include "logic/map_objects/tribes/warehouse.h"40#include "logic/map_objects/tribes/warehouse.h"
41#include "logic/map_objects/tribes/market.h"
42#include "logic/map_objects/tribes/worker_descr.h"41#include "logic/map_objects/tribes/worker_descr.h"
43#include "scripting/lua_table.h"42#include "scripting/lua_table.h"
4443
4544
=== modified file 'src/logic/map_objects/tribes/worker.cc'
--- src/logic/map_objects/tribes/worker.cc 2017-08-20 17:45:42 +0000
+++ src/logic/map_objects/tribes/worker.cc 2017-10-03 20:35:46 +0000
@@ -47,6 +47,7 @@
47#include "logic/map_objects/terrain_affinity.h"47#include "logic/map_objects/terrain_affinity.h"
48#include "logic/map_objects/tribes/carrier.h"48#include "logic/map_objects/tribes/carrier.h"
49#include "logic/map_objects/tribes/dismantlesite.h"49#include "logic/map_objects/tribes/dismantlesite.h"
50#include "logic/map_objects/tribes/market.h"
50#include "logic/map_objects/tribes/soldier.h"51#include "logic/map_objects/tribes/soldier.h"
51#include "logic/map_objects/tribes/tribe_descr.h"52#include "logic/map_objects/tribes/tribe_descr.h"
52#include "logic/map_objects/tribes/warehouse.h"53#include "logic/map_objects/tribes/warehouse.h"
@@ -1594,6 +1595,88 @@
1594 send_signal(game, "update");1595 send_signal(game, "update");
1595}1596}
15961597
1598// The task when a worker is part of the caravan that is trading items.
1599const Bob::Task Worker::taskCarryTradeItem = {
1600 "carry_trade_item", static_cast<Bob::Ptr>(&Worker::carry_trade_item_update), nullptr, nullptr, true};
1601
1602void Worker::start_task_carry_trade_item(Game& game,
1603 const int trade_id,
1604 ObjectPointer other_market) {
1605 push_task(game, taskCarryTradeItem);
1606 auto& state = top_state();
1607 state.ivar1 = 0;
1608 state.ivar2 = trade_id;
1609 state.objvar1 = other_market;
1610}
1611
1612// This is a state machine: leave building, go to the other market, drop off
1613// wares, and return.
1614void Worker::carry_trade_item_update(Game& game, State& state) {
1615 // Reset any signals that are not related to location
1616 std::string signal = get_signal();
1617 signal_handled();
1618 if (!signal.empty()) {
1619 // TODO(sirver,trading): Remove once signals are correctly handled.
1620 log("carry_trade_item_update: signal received: %s\n", signal.c_str());
1621 }
1622 if (signal == "evict") {
1623 return pop_task(game);
1624 }
1625
1626 // First of all, make sure we're outside
1627 if (state.ivar1 == 0) {
1628 start_task_leavebuilding(game, false);
1629 ++state.ivar1;
1630 return;
1631 }
1632
1633 auto* other_market = dynamic_cast<Market*>(state.objvar1.get(game));
1634 if (state.ivar1 == 1) {
1635 // Arrived on site. Move to the building and advance our state.
1636 if (other_market->base_flag().get_position() == get_position()) {
1637 ++state.ivar1;
1638 return start_task_move(
1639 game, WALK_NW, descr().get_right_walk_anims(does_carry_ware()), true);
1640 }
1641
1642 // Otherwise continue making progress towards the other market.
1643 if (!start_task_movepath(game, other_market->base_flag().get_position(), 5,
1644 descr().get_right_walk_anims(does_carry_ware()))) {
1645 molog("carry_trade_item_update: Could not move to other flag.\n");
1646 // TODO(sirver,trading): something needs to happen here.
1647 }
1648 return;
1649 }
1650
1651 if (state.ivar1 == 2) {
1652 WareInstance* const ware = fetch_carried_ware(game);
1653 other_market->traded_ware_arrived(state.ivar2, ware->descr_index(), &game);
1654 ware->remove(game);
1655 ++state.ivar1;
1656 start_task_move(game, WALK_SE, descr().get_right_walk_anims(does_carry_ware()), true);
1657 return;
1658 }
1659
1660 if (state.ivar1 == 3) {
1661 ++state.ivar1;
1662 start_task_return(game, false);
1663 return;
1664 }
1665
1666 if (state.ivar1 == 4) {
1667 pop_task(game);
1668 start_task_idle(game, 0, -1);
1669 dynamic_cast<Market*>(get_location(game))->try_launching_batch(&game);
1670 return;
1671 }
1672 NEVER_HERE();
1673}
1674
1675void Worker::update_task_carry_trade_item(Game& game) {
1676 if (top_state().task == &taskCarryTradeItem)
1677 send_signal(game, "update");
1678}
1679
1597/**1680/**
1598 * Evict the worker from its current building.1681 * Evict the worker from its current building.
1599 */1682 */
16001683
=== modified file 'src/logic/map_objects/tribes/worker.h'
--- src/logic/map_objects/tribes/worker.h 2017-06-24 08:47:46 +0000
+++ src/logic/map_objects/tribes/worker.h 2017-10-03 20:35:46 +0000
@@ -169,6 +169,9 @@
169 void start_task_leavebuilding(Game&, bool changelocation);169 void start_task_leavebuilding(Game&, bool changelocation);
170 void start_task_fugitive(Game&);170 void start_task_fugitive(Game&);
171171
172 void start_task_carry_trade_item(Game& game, int trade_id, ObjectPointer other_market);
173 void update_task_carry_trade_item(Game&);
174
172 void175 void
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);
174177
@@ -208,6 +211,7 @@
208 static const Task taskFugitive;211 static const Task taskFugitive;
209 static const Task taskGeologist;212 static const Task taskGeologist;
210 static const Task taskScout;213 static const Task taskScout;
214 static const Task taskCarryTradeItem;
211215
212private:216private:
213 // task details217 // task details
@@ -232,6 +236,7 @@
232 void fugitive_update(Game&, State&);236 void fugitive_update(Game&, State&);
233 void geologist_update(Game&, State&);237 void geologist_update(Game&, State&);
234 void scout_update(Game&, State&);238 void scout_update(Game&, State&);
239 void carry_trade_item_update(Game&, State&);
235240
236 // Program commands241 // Program commands
237 bool run_mine(Game&, State&, const Action&);242 bool run_mine(Game&, State&, const Action&);
238243
=== modified file 'src/logic/message.h'
--- src/logic/message.h 2017-02-19 16:56:59 +0000
+++ src/logic/message.h 2017-10-03 20:35:46 +0000
@@ -48,7 +48,8 @@
48 kWarfare, // everything starting from here is warfare48 kWarfare, // everything starting from here is warfare
49 kWarfareSiteDefeated,49 kWarfareSiteDefeated,
50 kWarfareSiteLost,50 kWarfareSiteLost,
51 kWarfareUnderAttack51 kWarfareUnderAttack,
52 kTradeOfferReceived,
52 };53 };
5354
54 /**55 /**
5556
=== modified file 'src/logic/playercommand.cc'
--- src/logic/playercommand.cc 2017-06-25 21:56:53 +0000
+++ src/logic/playercommand.cc 2017-10-03 20:35:46 +0000
@@ -29,6 +29,7 @@
29#include "io/streamwrite.h"29#include "io/streamwrite.h"
30#include "logic/game.h"30#include "logic/game.h"
31#include "logic/map_objects/map_object.h"31#include "logic/map_objects/map_object.h"
32#include "logic/map_objects/tribes/market.h"
32#include "logic/map_objects/tribes/soldier.h"33#include "logic/map_objects/tribes/soldier.h"
33#include "logic/map_objects/tribes/tribe_descr.h"34#include "logic/map_objects/tribes/tribe_descr.h"
34#include "logic/player.h"35#include "logic/player.h"
@@ -52,6 +53,25 @@
52 return mol.get<T>(object_index).serial();53 return mol.get<T>(object_index).serial();
53}54}
5455
56void serialize_bill_of_materials(const BillOfMaterials& bill, StreamWrite* ser) {
57 ser->unsigned_32(bill.size());
58 for (const WareAmount& amount : bill) {
59 ser->unsigned_8(amount.first);
60 ser->unsigned_32(amount.second);
61 }
62}
63
64BillOfMaterials deserialize_bill_of_materials(StreamRead* des) {
65 BillOfMaterials bill;
66 const int count = des->unsigned_32();
67 for (int i = 0; i < count; ++i) {
68 const auto index = des->unsigned_8();
69 const auto amount = des->unsigned_32();
70 bill.push_back(std::make_pair(index, amount));
71 }
72 return bill;
73}
74
55} // namespace75} // namespace
5676
57// NOTE keep numbers of existing entries as they are to ensure backward compatible savegame loading77// NOTE keep numbers of existing entries as they are to ensure backward compatible savegame loading
@@ -86,7 +106,8 @@
86 PLCMD_SHIP_EXPLORE = 27,106 PLCMD_SHIP_EXPLORE = 27,
87 PLCMD_SHIP_CONSTRUCT = 28,107 PLCMD_SHIP_CONSTRUCT = 28,
88 PLCMD_SHIP_SINK = 29,108 PLCMD_SHIP_SINK = 29,
89 PLCMD_SHIP_CANCELEXPEDITION = 30109 PLCMD_SHIP_CANCELEXPEDITION = 30,
110 PLCMD_PROPOSE_TRADE = 31,
90};111};
91112
92/*** class PlayerCommand ***/113/*** class PlayerCommand ***/
@@ -1784,4 +1805,75 @@
1784 fw.unsigned_8(ware_);1805 fw.unsigned_8(ware_);
1785 fw.unsigned_8(static_cast<uint8_t>(policy_));1806 fw.unsigned_8(static_cast<uint8_t>(policy_));
1786}1807}
1808
1809CmdProposeTrade::CmdProposeTrade(uint32_t time, PlayerNumber pn, const Trade& trade)
1810 : PlayerCommand(time, pn), trade_(trade) {
1811}
1812
1813CmdProposeTrade::CmdProposeTrade() : PlayerCommand() {
1814}
1815
1816void CmdProposeTrade::execute(Game& game) {
1817 Player* plr = game.get_player(sender());
1818 if (plr == nullptr) {
1819 return;
1820 }
1821
1822 Market* initiator = dynamic_cast<Market*>(game.objects().get_object(trade_.initiator));
1823 if (initiator == nullptr) {
1824 log("CmdProposeTrade: initiator vanished or is not a market.\n");
1825 return;
1826 }
1827 if (&initiator->owner() != plr) {
1828 log("CmdProposeTrade: sender %u, but market owner %u\n", sender(),
1829 initiator->owner().player_number());
1830 return;
1831 }
1832 Market* receiver = dynamic_cast<Market*>(game.objects().get_object(trade_.receiver));
1833 if (receiver == nullptr) {
1834 log("CmdProposeTrade: receiver vanished or is not a market.\n");
1835 return;
1836 }
1837 if (&initiator->owner() == &receiver->owner()) {
1838 log("CmdProposeTrade: Sending and receiving player are the same.\n");
1839 return;
1840 }
1841
1842 // TODO(sirver,trading): Maybe check connectivity between markets here and
1843 // report errors.
1844 game.propose_trade(trade_);
1845}
1846
1847CmdProposeTrade::CmdProposeTrade(StreamRead& des) : PlayerCommand(0, des.unsigned_8()) {
1848 trade_.initiator = des.unsigned_32();
1849 trade_.receiver = des.unsigned_32();
1850 trade_.items_to_send = deserialize_bill_of_materials(&des);
1851 trade_.items_to_receive = deserialize_bill_of_materials(&des);
1852 trade_.num_batches = des.signed_32();
1853}
1854
1855void CmdProposeTrade::serialize(StreamWrite& ser) {
1856 ser.unsigned_8(PLCMD_PROPOSE_TRADE);
1857 ser.unsigned_8(sender());
1858 ser.unsigned_32(trade_.initiator);
1859 ser.unsigned_32(trade_.receiver);
1860 serialize_bill_of_materials(trade_.items_to_send, &ser);
1861 serialize_bill_of_materials(trade_.items_to_receive, &ser);
1862 ser.signed_32(trade_.num_batches);
1863}
1864
1865void CmdProposeTrade::read(FileRead& /* fr */,
1866 EditorGameBase& /* egbase */,
1867 MapObjectLoader& /* mol */) {
1868 // TODO(sirver,trading): Implement this.
1869 NEVER_HERE();
1870}
1871
1872void CmdProposeTrade::write(FileWrite& /* fw */,
1873 EditorGameBase& /* egbase */,
1874 MapObjectSaver& /* mos */) {
1875 // TODO(sirver,trading): Implement this.
1876 NEVER_HERE();
1877}
1878
1787}1879}
17881880
=== modified file 'src/logic/playercommand.h'
--- src/logic/playercommand.h 2017-08-16 13:23:15 +0000
+++ src/logic/playercommand.h 2017-10-03 20:35:46 +0000
@@ -846,6 +846,29 @@
846 DescriptionIndex ware_;846 DescriptionIndex ware_;
847 Warehouse::StockPolicy policy_;847 Warehouse::StockPolicy policy_;
848};848};
849
850struct CmdProposeTrade : PlayerCommand {
851 CmdProposeTrade(uint32_t time, PlayerNumber pn, const Trade& trade);
852
853 QueueCommandTypes id() const override {
854 return QueueCommandTypes::kProposeTrade;
855 }
856
857 void execute(Game& game) override;
858
859 // Network (de-)serialization
860 explicit CmdProposeTrade(StreamRead& des);
861 void serialize(StreamWrite& ser) override;
862
863 // Savegame functions
864 CmdProposeTrade();
865 void write(FileWrite&, EditorGameBase&, MapObjectSaver&) override;
866 void read(FileRead&, EditorGameBase&, MapObjectLoader&) override;
867
868private:
869 Trade trade_;
870};
871
849}872}
850873
851#endif // end of include guard: WL_LOGIC_PLAYERCOMMAND_H874#endif // end of include guard: WL_LOGIC_PLAYERCOMMAND_H
852875
=== modified file 'src/logic/queue_cmd_factory.cc'
--- src/logic/queue_cmd_factory.cc 2017-01-25 18:55:59 +0000
+++ src/logic/queue_cmd_factory.cc 2017-10-03 20:35:46 +0000
@@ -78,6 +78,8 @@
78 return *new CmdEvictWorker();78 return *new CmdEvictWorker();
79 case QueueCommandTypes::kMilitarysiteSetSoldierPreference:79 case QueueCommandTypes::kMilitarysiteSetSoldierPreference:
80 return *new CmdMilitarySiteSetSoldierPreference();80 return *new CmdMilitarySiteSetSoldierPreference();
81 case QueueCommandTypes::kProposeTrade:
82 return *new CmdProposeTrade();
81 case QueueCommandTypes::kSinkShip:83 case QueueCommandTypes::kSinkShip:
82 return *new CmdShipSink();84 return *new CmdShipSink();
83 case QueueCommandTypes::kShipCancelExpedition:85 case QueueCommandTypes::kShipCancelExpedition:
8486
=== modified file 'src/logic/queue_cmd_ids.h'
--- src/logic/queue_cmd_ids.h 2017-01-25 18:55:59 +0000
+++ src/logic/queue_cmd_ids.h 2017-10-03 20:35:46 +0000
@@ -70,7 +70,8 @@
7070
71 kEvictWorker,71 kEvictWorker,
7272
73 kMilitarysiteSetSoldierPreference, // 2673 kMilitarysiteSetSoldierPreference,
74 kProposeTrade, // 27
7475
75 kSinkShip = 121,76 kSinkShip = 121,
76 kShipCancelExpedition,77 kShipCancelExpedition,
7778
=== added file 'src/logic/trade_agreement.h'
--- src/logic/trade_agreement.h 1970-01-01 00:00:00 +0000
+++ src/logic/trade_agreement.h 2017-10-03 20:35:46 +0000
@@ -0,0 +1,54 @@
1/*
2 * Copyright (C) 2006-2017 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 *
18 */
19
20#ifndef WL_LOGIC_TRADE_AGREEMENT_H
21#define WL_LOGIC_TRADE_AGREEMENT_H
22
23#include "logic/map_objects/map_object.h"
24
25namespace Widelands {
26
27// TODO(sirver,trading): Document everything in here.
28
29// Maximum number of a single ware that can be contained in a trade batch.
30constexpr int kMaxPerItemTradeBatchSize = 15;
31
32struct Trade {
33 BillOfMaterials items_to_send;
34 BillOfMaterials items_to_receive;
35 int num_batches;
36 Serial initiator;
37 Serial receiver;
38};
39
40// TODO(sirver,trading): This class should probably be private to 'Game'.
41struct TradeAgreement {
42 enum class State {
43 kProposed,
44 kRunning,
45 };
46
47 State state;
48 Trade trade;
49};
50
51} // namespace Widelands
52
53
54#endif // end of include guard: WL_LOGIC_TRADE_AGREEMENT_H
055
=== modified file 'src/map_io/map_buildingdata_packet.cc'
--- src/map_io/map_buildingdata_packet.cc 2017-08-20 08:34:02 +0000
+++ src/map_io/map_buildingdata_packet.cc 2017-10-03 20:35:46 +0000
@@ -339,6 +339,7 @@
339 }339 }
340 }340 }
341341
342 // TODO(sirver,trading): Pull out and reuse this for market workers.
342 assert(warehouse.incorporated_workers_.empty());343 assert(warehouse.incorporated_workers_.empty());
343 {344 {
344 uint16_t const nrworkers = fr.unsigned_16();345 uint16_t const nrworkers = fr.unsigned_16();
345346
=== modified file 'src/scripting/lua_map.cc'
--- src/scripting/lua_map.cc 2017-09-18 13:43:08 +0000
+++ src/scripting/lua_map.cc 2017-10-03 20:35:46 +0000
@@ -33,6 +33,7 @@
33#include "logic/map_objects/immovable.h"33#include "logic/map_objects/immovable.h"
34#include "logic/map_objects/terrain_affinity.h"34#include "logic/map_objects/terrain_affinity.h"
35#include "logic/map_objects/tribes/carrier.h"35#include "logic/map_objects/tribes/carrier.h"
36#include "logic/map_objects/tribes/market.h"
36#include "logic/map_objects/tribes/ship.h"37#include "logic/map_objects/tribes/ship.h"
37#include "logic/map_objects/tribes/soldier.h"38#include "logic/map_objects/tribes/soldier.h"
38#include "logic/map_objects/tribes/tribes.h"39#include "logic/map_objects/tribes/tribes.h"
@@ -594,6 +595,48 @@
594 }595 }
595 return 0;596 return 0;
596}597}
598
599// Parses a table of name/count pairs as given from Lua.
600void parse_wares_workers(lua_State* L,
601 int table_index,
602 const TribeDescr& tribe,
603 InputMap* ware_workers_list,
604 bool is_ware) {
605 luaL_checktype(L, table_index, LUA_TTABLE);
606 lua_pushnil(L);
607 while (lua_next(L, table_index) != 0) {
608 if (is_ware) {
609 if (tribe.ware_index(luaL_checkstring(L, -2)) == INVALID_INDEX) {
610 report_error(L, "Illegal ware %s", luaL_checkstring(L, -2));
611 }
612 ware_workers_list->insert(
613 std::make_pair(std::make_pair(tribe.ware_index(luaL_checkstring(L, -2)),
614 Widelands::WareWorker::wwWARE),
615 luaL_checkuint32(L, -1)));
616 } else {
617 if (tribe.worker_index(luaL_checkstring(L, -2)) == INVALID_INDEX) {
618 report_error(L, "Illegal worker %s", luaL_checkstring(L, -2));
619 }
620 ware_workers_list->insert(
621 std::make_pair(std::make_pair(tribe.worker_index(luaL_checkstring(L, -2)),
622 Widelands::WareWorker::wwWORKER),
623 luaL_checkuint32(L, -1)));
624 }
625 lua_pop(L, 1);
626 }
627}
628
629BillOfMaterials parse_wares_as_bill_of_material(lua_State* L, int table_index,
630 const TribeDescr& tribe) {
631 InputMap input_map;
632 parse_wares_workers(L, table_index, tribe, &input_map, true /* is_ware */);
633 BillOfMaterials result;
634 for (const auto& pair : input_map) {
635 result.push_back(std::make_pair(pair.first.first, pair.second));
636 }
637 return result;
638}
639
597} // namespace640} // namespace
598641
599/*642/*
@@ -782,7 +825,6 @@
782825
783 // We either received, two items string,int:826 // We either received, two items string,int:
784 if (nargs == 3) {827 if (nargs == 3) {
785
786 result = RequestedWareWorker::kSingle;828 result = RequestedWareWorker::kSingle;
787 if (is_ware) {829 if (is_ware) {
788 if (tribe.ware_index(luaL_checkstring(L, 2)) == INVALID_INDEX) {830 if (tribe.ware_index(luaL_checkstring(L, 2)) == INVALID_INDEX) {
@@ -803,32 +845,7 @@
803 } else {845 } else {
804 result = RequestedWareWorker::kList;846 result = RequestedWareWorker::kList;
805 // or we got a table with name:quantity847 // or we got a table with name:quantity
806 luaL_checktype(L, 2, LUA_TTABLE);848 parse_wares_workers(L, 2, tribe, ware_workers_list, is_ware);
807 lua_pushnil(L);
808 while (lua_next(L, 2) != 0) {
809 if (is_ware) {
810 if (tribe.ware_index(luaL_checkstring(L, -2)) == INVALID_INDEX) {
811 report_error(L, "Illegal ware %s", luaL_checkstring(L, -2));
812 }
813 } else {
814 if (tribe.worker_index(luaL_checkstring(L, -2)) == INVALID_INDEX) {
815 report_error(L, "Illegal worker %s", luaL_checkstring(L, -2));
816 }
817 }
818
819 if (is_ware) {
820 ware_workers_list->insert(
821 std::make_pair(std::make_pair(tribe.ware_index(luaL_checkstring(L, -2)),
822 Widelands::WareWorker::wwWARE),
823 luaL_checkuint32(L, -1)));
824 } else {
825 ware_workers_list->insert(
826 std::make_pair(std::make_pair(tribe.worker_index(luaL_checkstring(L, -2)),
827 Widelands::WareWorker::wwWORKER),
828 luaL_checkuint32(L, -1)));
829 }
830 lua_pop(L, 1);
831 }
832 }849 }
833 return result;850 return result;
834}851}
@@ -2762,6 +2779,8 @@
2762 trading over land with other players. See the parent classes for more2779 trading over land with other players. See the parent classes for more
2763 properties.2780 properties.
2764*/2781*/
2782// TODO(sirver,trading): Expose the properties of MarketDescription here once
2783// the interface settles.
2765const char LuaMarketDescription::className[] = "MarketDescription";2784const char LuaMarketDescription::className[] = "MarketDescription";
2766const MethodType<LuaMarketDescription> LuaMarketDescription::Methods[] = {2785const MethodType<LuaMarketDescription> LuaMarketDescription::Methods[] = {
2767 {nullptr, nullptr},2786 {nullptr, nullptr},
@@ -5083,6 +5102,7 @@
5083*/5102*/
5084const char LuaMarket::className[] = "Market";5103const char LuaMarket::className[] = "Market";
5085const MethodType<LuaMarket> LuaMarket::Methods[] = {5104const MethodType<LuaMarket> LuaMarket::Methods[] = {
5105 METHOD(LuaMarket, propose_trade),
5086 // TODO(sirver,trading): Implement and fix documentation.5106 // TODO(sirver,trading): Implement and fix documentation.
5087 // METHOD(LuaMarket, set_wares),5107 // METHOD(LuaMarket, set_wares),
5088 // METHOD(LuaMarket, get_wares),5108 // METHOD(LuaMarket, get_wares),
@@ -5106,6 +5126,35 @@
5106 ==========================================================5126 ==========================================================
5107 */5127 */
51085128
5129/* RST
5130 .. method:: propose_trade(other_market, num_batches, items_to_send, items_to_receive)
5131
5132 TODO(sirver,trading): document
5133
5134 :returns: :const:`nil`
5135*/
5136int LuaMarket::propose_trade(lua_State* L) {
5137 if (lua_gettop(L) != 5) {
5138 report_error(L, "Takes 4 arguments.");
5139 }
5140 Game& game = get_game(L);
5141 Market* self = get(L, game);
5142 Market* other_market = (*get_user_class<LuaMarket>(L, 2))->get(L, game);
5143 const int num_batches = luaL_checkinteger(L, 3);
5144
5145 const BillOfMaterials items_to_send = parse_wares_as_bill_of_material(L, 4, self->owner().tribe());
5146 // TODO(sirver,trading): unsure if correct. Test inter-tribe trading, i.e.
5147 // barbarians trading with empire, but shipping atlantean only wares.
5148 const BillOfMaterials items_to_receive = parse_wares_as_bill_of_material(L, 5, self->owner().tribe());
5149 const int trade_id = game.propose_trade(
5150 Trade{items_to_send, items_to_receive, num_batches, self->serial(), other_market->serial()});
5151
5152 // TODO(sirver,trading): Wrap 'Trade' into its own Lua class?
5153 lua_pushint32(L, trade_id);
5154 return 1;
5155}
5156
5157
5109/*5158/*
5110 ==========================================================5159 ==========================================================
5111 C METHODS5160 C METHODS
51125161
=== modified file 'src/scripting/lua_map.h'
--- src/scripting/lua_map.h 2017-09-20 21:27:25 +0000
+++ src/scripting/lua_map.h 2017-10-03 20:35:46 +0000
@@ -29,6 +29,7 @@
29#include "logic/game.h"29#include "logic/game.h"
30#include "logic/map_objects/tribes/constructionsite.h"30#include "logic/map_objects/tribes/constructionsite.h"
31#include "logic/map_objects/tribes/dismantlesite.h"31#include "logic/map_objects/tribes/dismantlesite.h"
32#include "logic/map_objects/tribes/market.h"
32#include "logic/map_objects/tribes/militarysite.h"33#include "logic/map_objects/tribes/militarysite.h"
33#include "logic/map_objects/tribes/productionsite.h"34#include "logic/map_objects/tribes/productionsite.h"
34#include "logic/map_objects/tribes/ship.h"35#include "logic/map_objects/tribes/ship.h"
@@ -1125,6 +1126,7 @@
1125 /*1126 /*
1126 * Lua Methods1127 * Lua Methods
1127 */1128 */
1129 int propose_trade(lua_State* L);
11281130
1129 /*1131 /*
1130 * C Methods1132 * C Methods
11311133
=== modified file 'src/wui/game_message_menu.cc'
--- src/wui/game_message_menu.cc 2017-08-17 15:34:45 +0000
+++ src/wui/game_message_menu.cc 2017-10-03 20:35:46 +0000
@@ -487,6 +487,7 @@
487 case Widelands::Message::Type::kWarfareSiteDefeated:487 case Widelands::Message::Type::kWarfareSiteDefeated:
488 case Widelands::Message::Type::kWarfareSiteLost:488 case Widelands::Message::Type::kWarfareSiteLost:
489 case Widelands::Message::Type::kWarfareUnderAttack:489 case Widelands::Message::Type::kWarfareUnderAttack:
490 case Widelands::Message::Type::kTradeOfferReceived:
490 set_filter_messages_tooltips();491 set_filter_messages_tooltips();
491 message_filter_ = Widelands::Message::Type::kAllMessages;492 message_filter_ = Widelands::Message::Type::kAllMessages;
492 geologistsbtn_->set_perm_pressed(false);493 geologistsbtn_->set_perm_pressed(false);
@@ -580,6 +581,7 @@
580 case Widelands::Message::Type::kWarfareSiteDefeated:581 case Widelands::Message::Type::kWarfareSiteDefeated:
581 case Widelands::Message::Type::kWarfareSiteLost:582 case Widelands::Message::Type::kWarfareSiteLost:
582 case Widelands::Message::Type::kWarfareUnderAttack:583 case Widelands::Message::Type::kWarfareUnderAttack:
584 case Widelands::Message::Type::kTradeOfferReceived:
583 return "images/wui/messages/message_new.png";585 return "images/wui/messages/message_new.png";
584 }586 }
585 NEVER_HERE();587 NEVER_HERE();
586588
=== modified file 'src/wui/ware_statistics_menu.cc'
--- src/wui/ware_statistics_menu.cc 2017-08-08 17:39:40 +0000
+++ src/wui/ware_statistics_menu.cc 2017-10-03 20:35:46 +0000
@@ -103,8 +103,7 @@
103 &registry,103 &registry,
104 kPlotWidth + 2 * kSpacing,104 kPlotWidth + 2 * kSpacing,
105 270,105 270,
106 _("Ware Statistics")),106 _("Ware Statistics")) {
107 parent_(&parent) {
108 uint8_t const nr_wares = parent.get_player()->egbase().tribes().nrwares();107 uint8_t const nr_wares = parent.get_player()->egbase().tribes().nrwares();
109108
110 // Init color sets109 // Init color sets
111110
=== modified file 'src/wui/ware_statistics_menu.h'
--- src/wui/ware_statistics_menu.h 2017-08-18 14:27:26 +0000
+++ src/wui/ware_statistics_menu.h 2017-10-03 20:35:46 +0000
@@ -37,7 +37,6 @@
37 void set_time(int32_t);37 void set_time(int32_t);
3838
39private:39private:
40 InteractivePlayer* parent_;
41 WuiPlotArea* plot_production_;40 WuiPlotArea* plot_production_;
42 WuiPlotArea* plot_consumption_;41 WuiPlotArea* plot_consumption_;
43 WuiPlotArea* plot_stock_;42 WuiPlotArea* plot_stock_;
4443
=== modified file 'test/maps/market_trading.wmf/scripting/init.lua'
--- test/maps/market_trading.wmf/scripting/init.lua 2017-09-18 13:43:08 +0000
+++ test/maps/market_trading.wmf/scripting/init.lua 2017-10-03 20:35:46 +0000
@@ -1,6 +1,6 @@
1include "scripting/lunit.lua"
2include "scripting/coroutine.lua"1include "scripting/coroutine.lua"
3include "scripting/infrastructure.lua"2include "scripting/infrastructure.lua"
3include "scripting/lunit.lua"
4include "scripting/ui.lua"4include "scripting/ui.lua"
55
6game = wl.Game()6game = wl.Game()
@@ -22,7 +22,17 @@
22 end22 end
23end23end
2424
25function place_markets()
26 prefilled_buildings(p1, { "barbarians_market", 22, 27 })
27 market_p1 = map:get_field(22, 27).immovable
28 connected_road(p1, market_p1.flag, "tr,tl|", true)
29
30 prefilled_buildings(p2, { "barbarians_market", 31, 27 })
31 market_p2 = map:get_field(31, 27).immovable
32 connected_road(p2, market_p2.flag, "tr,tl|", true)
33end
34
25full_headquarters(p1, 22, 25)35full_headquarters(p1, 22, 25)
26full_headquarters(p2, 32, 25)36full_headquarters(p2, 31, 25)
2737
28game.desired_speed = 5000038game.desired_speed = 50000
2939
=== 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'
--- test/maps/market_trading.wmf/scripting/test_market_can_be_build.lua 2017-09-18 13:43:08 +0000
+++ test/maps/market_trading.wmf/scripting/test_market_can_be_built.lua 2017-10-03 20:35:46 +0000
@@ -1,8 +1,8 @@
1run(function()1run(function()
2 sleep(2000)2 sleep(2000)
33
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)
5 connected_road(p2, market.flag, "l,l,l|", true)5 connected_road(p2, market.flag, "tr,tl|", true)
66
7 while #p2:get_buildings("barbarians_market") == 0 do7 while #p2:get_buildings("barbarians_market") == 0 do
8 sleep(10000)8 sleep(10000)
99
=== added file 'test/maps/market_trading.wmf/scripting/test_simple_trade.lua'
--- test/maps/market_trading.wmf/scripting/test_simple_trade.lua 1970-01-01 00:00:00 +0000
+++ test/maps/market_trading.wmf/scripting/test_simple_trade.lua 2017-10-03 20:35:46 +0000
@@ -0,0 +1,39 @@
1run(function()
2 sleep(2000)
3 place_markets()
4 market_p2:propose_trade(market_p1, 5, { log = 3 }, { granite = 2, iron = 1 })
5
6 local p1_initial = {
7 iron = p1:get_wares("iron"),
8 log = p1:get_wares("log"),
9 granite = p1:get_wares("granite"),
10 }
11 local p2_initial = {
12 iron = p1:get_wares("iron"),
13 log = p1:get_wares("log"),
14 granite = p1:get_wares("granite"),
15 }
16
17 -- We await until one ware we trade has the right count for one player.
18 -- Then, we'll sleep half as long as we already waited to make sure that no
19 -- additional batches are shipped. Then we check all stocks for the correct
20 -- numbers.
21 local start_time = game.time
22 while p2:get_wares("iron") - p2_initial["iron"] < 5 do
23 sleep(10000)
24 end
25
26 sleep(math.ceil((game.time - start_time) / 2))
27
28 assert_equal(5, p2:get_wares("iron") - p2_initial["iron"])
29 assert_equal(10, p2:get_wares("granite") - p2_initial["granite"])
30 assert_equal(-15, p2:get_wares("log") - p2_initial["log"])
31
32 assert_equal(-5, p1:get_wares("iron") - p1_initial["iron"])
33 assert_equal(-10, p1:get_wares("granite") - p1_initial["granite"])
34 assert_equal(15, p1:get_wares("log") - p1_initial["log"])
35
36 print("# All Tests passed.")
37 wl.ui.MapView():close()
38end)
39

Subscribers

People subscribed via source and target branches

to status/vote changes: