widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #11222
[Merge] lp:~widelands-dev/widelands/market1 into lp:widelands
SirVer has proposed merging lp:~widelands-dev/widelands/market1 into lp:widelands.
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
Requested reviews:
Widelands Developers (widelands-dev)
For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/market1/+merge/331233
--
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/market1 into lp:widelands.
=== 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-09-22 21:01:30 +0000
@@ -50,7 +50,5 @@
prohibited_till = 1000
},
- working_positions = {
- barbarians_ox = 10,
- },
+ carrier = "barbarians_ox",
}
=== modified file 'src/economy/request.h'
--- src/economy/request.h 2017-01-25 18:55:59 +0000
+++ src/economy/request.h 2017-09-22 21:01:30 +0000
@@ -123,6 +123,7 @@
// callbacks for WareInstance/Worker code
void transfer_finish(Game&, Transfer&);
void transfer_fail(Game&, Transfer&);
+ void cancel_transfer(uint32_t idx);
void set_requirements(const Requirements& r) {
requirements_ = r;
@@ -131,13 +132,9 @@
return requirements_;
}
+
private:
int32_t get_base_required_time(EditorGameBase&, uint32_t nr) const;
-
-public:
- void cancel_transfer(uint32_t idx);
-
-private:
void remove_transfer(uint32_t idx);
uint32_t find_transfer(Transfer&);
=== 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-09-22 21:01:30 +0000
@@ -309,8 +309,9 @@
// Update whether we have a Supply or not
if (!transfer_ || !transfer_->get_request()) {
- if (!supply_)
+ if (!supply_) {
supply_ = new IdleWareSupply(*this);
+ }
} else {
delete supply_;
supply_ = nullptr;
=== 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-09-22 21:01:30 +0000
@@ -61,6 +61,8 @@
// Write gametime
fw.unsigned_32(game.gametime_);
+ // TODO(sirver,trading): save/load trade_agreements and related data.
+
// We do not care for players, since they were set
// on game initialization to match Map::scenario_player_[names|tribes]
// or vice versa, so this is handled by map loader
=== modified file 'src/logic/CMakeLists.txt'
--- src/logic/CMakeLists.txt 2017-09-15 12:33:58 +0000
+++ src/logic/CMakeLists.txt 2017-09-22 21:01:30 +0000
@@ -95,14 +95,14 @@
findimmovable.h
findnode.cc
findnode.h
+ game.cc
+ game.h
game_data_error.cc
game_data_error.h
- game.cc
- game.h
+ map.cc
+ map.h
map_revision.cc
map_revision.h
- map.cc
- map.h
mapastar.cc
mapastar.h
mapdifferenceregion.cc
@@ -114,18 +114,18 @@
mapregion.h
maptriangleregion.cc
maptriangleregion.h
+ message.h
message_id.h
message_queue.h
- message.h
nodecaps.h
objective.h
path.cc
path.h
pathfield.cc
pathfield.h
- player_area.h
player.cc
player.h
+ player_area.h
playercommand.cc
playercommand.h
playersmanager.cc
@@ -138,6 +138,7 @@
roadtype.h
save_handler.cc
save_handler.h
+ trade_agreement.h
widelands_geometry_io.cc
widelands_geometry_io.h
map_objects/bob.cc
=== modified file 'src/logic/game.cc'
--- src/logic/game.cc 2017-08-30 12:01:47 +0000
+++ src/logic/game.cc 2017-09-22 21:01:30 +0000
@@ -49,6 +49,7 @@
#include "logic/cmd_luascript.h"
#include "logic/game_settings.h"
#include "logic/map_objects/tribes/carrier.h"
+#include "logic/map_objects/tribes/market.h"
#include "logic/map_objects/tribes/militarysite.h"
#include "logic/map_objects/tribes/ship.h"
#include "logic/map_objects/tribes/soldier.h"
@@ -753,6 +754,87 @@
get_gametime(), ship.get_owner()->player_number(), ship.serial()));
}
+void Game::send_player_propose_trade(const Trade& trade) {
+ auto* object = objects().get_object(trade.initiator);
+ assert(object != nullptr);
+ send_player_command(
+ *new CmdProposeTrade(get_gametime(), object->get_owner()->player_number(), trade));
+}
+
+int Game::propose_trade(const Trade& trade) {
+ // TODO(sirver,trading): Check if a trade is possible (i.e. if there is a
+ // path between the two markets);
+ const int id = next_trade_agreement_id_;
+ ++next_trade_agreement_id_;
+
+ auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator));
+ auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver));
+ // This is only ever called through a PlayerCommand and that already made
+ // sure that the objects still exist. Since no time has passed, they should
+ // not have vanished under us.
+ assert(initiator != nullptr);
+ assert(receiver != nullptr);
+
+ receiver->removed.connect(
+ [this, id](const uint32_t /* serial */) { cancel_trade(id); });
+ initiator->removed.connect(
+ [this, id](const uint32_t /* serial */) { cancel_trade(id); });
+
+ receiver->send_message(*this, Message::Type::kTradeOfferReceived, receiver->descr().descname(),
+ receiver->descr().icon_filename(), receiver->descr().descname(),
+ _("This Market received a new trade offer."), true);
+ trade_agreements_[id] = TradeAgreement{TradeAgreement::State::kProposed, trade};
+
+ // TODO(sirver): this should be done through another player_command, but I
+ // want to get to the trade logic implementation now.
+ accept_trade(id);
+ return id;
+}
+
+void Game::accept_trade(const int trade_id) {
+ auto it = trade_agreements_.find(trade_id);
+ if (it == trade_agreements_.end()) {
+ log("Game::accept_trade: Trade %d has vanished. Ignoring.\n", trade_id);
+ return;
+ }
+ const Trade& trade = it->second.trade;
+ auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator));
+ auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver));
+ if (initiator == nullptr || receiver == nullptr) {
+ cancel_trade(trade_id);
+ return;
+ }
+
+ initiator->new_trade(trade_id, trade.send_items, trade.num_batches, trade.receiver);
+ receiver->new_trade(trade_id, trade.received_items, trade.num_batches, trade.initiator);
+
+ // TODO(sirver,trading): Message the users that the trade has been accepted.
+}
+
+void Game::cancel_trade(int trade_id) {
+ // The trade id might be long gone - since we never disconnect from the
+ // 'removed' signal of the two buildings, we might be invoked long after the
+ // trade was deleted for other reasons.
+ const auto it = trade_agreements_.find(trade_id);
+ if (it == trade_agreements_.end()) {
+ return;
+ }
+ const auto& trade = it->second.trade;
+
+ auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator));
+ if (initiator != nullptr) {
+ initiator->cancel_trade(trade_id);
+ // TODO(sirver,trading): Send message to owner that the trade has been canceled.
+ }
+
+ auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver));
+ if (receiver != nullptr) {
+ receiver->cancel_trade(trade_id);
+ // TODO(sirver,trading): Send message to owner that the trade has been canceled.
+ }
+ trade_agreements_.erase(trade_id);
+}
+
LuaGameInterface& Game::lua() {
return static_cast<LuaGameInterface&>(EditorGameBase::lua());
}
=== modified file 'src/logic/game.h'
--- src/logic/game.h 2017-08-18 00:17:39 +0000
+++ src/logic/game.h 2017-09-22 21:01:30 +0000
@@ -27,6 +27,7 @@
#include "logic/cmd_queue.h"
#include "logic/editor_game_base.h"
#include "logic/save_handler.h"
+#include "logic/trade_agreement.h"
#include "random/random.h"
#include "scripting/logic.h"
@@ -207,6 +208,7 @@
void send_player_ship_explore_island(Ship&, IslandExploreDirection);
void send_player_sink_ship(Ship&);
void send_player_cancel_expedition_ship(Ship&);
+ void send_player_propose_trade(const Trade& trade);
InteractivePlayer* get_ipl();
@@ -244,6 +246,11 @@
void set_auto_speed(bool);
+ // TODO(sirver,trading): document these functions once the interface settles.
+ int propose_trade(const Trade& trade);
+ void accept_trade(int trade_id);
+ void cancel_trade(int trade_id);
+
private:
void sync_reset();
@@ -308,6 +315,9 @@
std::unique_ptr<ReplayWriter> replaywriter_;
GeneralStatsVector general_stats_;
+ int next_trade_agreement_id_ = 1;
+ // Maps from trade agreement id to the agreement.
+ std::map<int, TradeAgreement> trade_agreements_;
/// For save games and statistics generation
std::string win_condition_displayname_;
=== 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-09-22 21:01:30 +0000
@@ -390,6 +390,10 @@
state.ivar1 = 0;
}
+bool Bob::is_idle() {
+ return get_state(taskIdle);
+}
+
/**
* Move along a predefined path.
* \par ivar1 the step number.
=== 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-09-22 21:01:30 +0000
@@ -278,6 +278,7 @@
// TODO(feature-Hasi50): correct (?) Send a signal that may switch to some other \ref Task
void send_signal(Game&, char const*);
void start_task_idle(Game&, uint32_t anim, int32_t timeout);
+ bool is_idle();
/// This can fail (and return false). Therefore the caller must check the
/// result and find something else for the bob to do. Otherwise there will
=== 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-09-22 21:01:30 +0000
@@ -19,8 +19,11 @@
#include "logic/map_objects/tribes/market.h"
+#include <memory>
+
#include "base/i18n.h"
#include "logic/map_objects/tribes/productionsite.h"
+#include "logic/map_objects/tribes/tribes.h"
namespace Widelands {
@@ -30,7 +33,12 @@
: BuildingDescr(init_descname, MapObjectType::MARKET, table, egbase) {
i18n::Textdomain td("tribes");
- parse_working_positions(egbase, table.get_table("working_positions").get(), &working_positions_);
+ DescriptionIndex const woi = egbase.tribes().worker_index(table.get_string("carrier"));
+ if (!egbase.tribes().worker_exists(woi)) {
+ throw wexception("invalid");
+ }
+ carrier_ = woi;
+
// TODO(sirver,trading): Add actual logic here.
}
@@ -38,10 +46,223 @@
return *new Market(*this);
}
+int Market::TradeOrder::num_wares_per_batch() const {
+ int sum = 0;
+ for (const auto& item_pair : items) {
+ sum += item_pair.second;
+ }
+ return sum;
+}
+
+bool Market::TradeOrder::fulfilled() const {
+ return num_shipped_batches == initial_num_batches;
+}
+
+// TODO(sirver,trading): This needs to implement 'set_economy'. Maybe common code can be shared.
Market::Market(const MarketDescr& the_descr) : Building(the_descr) {
}
Market::~Market() {
}
+void Market::new_trade(const int trade_id,
+ const BillOfMaterials& items,
+ const int num_batches,
+ const Serial other_side) {
+ trade_orders_[trade_id] = TradeOrder{items, num_batches, 0, other_side, 0, nullptr, {}, {}};
+ auto& trade_order = trade_orders_[trade_id];
+
+ // Request one worker for each item in a batch.
+ trade_order.worker_request.reset(
+ new Request(*this, descr().carrier(), Market::worker_arrived_callback, wwWORKER));
+ trade_order.worker_request->set_count(trade_order.num_wares_per_batch());
+
+ // Make sure we have a wares queue for each item in this.
+ for (const auto& entry : items) {
+ ensure_wares_queue_exists(entry.first);
+ }
+}
+
+void Market::cancel_trade(const int trade_id) {
+ // TODO(sirver,trading): Launch workers, release no longer required wares and delete now unneeded 'WaresQueue's
+ trade_orders_.erase(trade_id);
+}
+
+void Market::worker_arrived_callback(
+ Game& game, Request& rq, DescriptionIndex /* widx */, Worker* const w, PlayerImmovable& target) {
+ auto& market = dynamic_cast<Market&>(target);
+
+ assert(w);
+ assert(w->get_location(game) == &market);
+
+ for (auto& trade_order_pair : market.trade_orders_) {
+ auto& trade_order = trade_order_pair.second;
+ if (trade_order.worker_request.get() != &rq) {
+ continue;
+ }
+
+ if (rq.get_count() == 0) {
+ // Erase this request.
+ trade_order.worker_request.reset();
+ }
+ w->start_task_idle(game, 0, -1);
+ trade_order.workers.push_back(w);
+ Notifications::publish(NoteBuilding(market.serial(), NoteBuilding::Action::kWorkersChanged));
+ market.try_launching_batch(&game);
+ return;
+ }
+ NEVER_HERE(); // We should have found and handled a match by now.
+}
+
+void Market::ware_arrived_callback(Game& g, InputQueue*, DescriptionIndex, Worker*, void* data) {
+ Market& market = *static_cast<Market*>(data);
+ market.try_launching_batch(&g);
+}
+
+void Market::try_launching_batch(Game* game) {
+ for (auto& pair : trade_orders_) {
+ if (!is_ready_to_launch_batch(pair.first)) {
+ continue;
+ }
+
+ auto* other_market =
+ dynamic_cast<Market*>(game->objects().get_object(pair.second.other_side));
+ if (other_market == nullptr) {
+ // TODO(sirver,trading): Can this even happen? Where is this function called from.
+ // The other market seems to have vanished. The game tracks this and
+ // should soon delete this trade request from us. We just ignore it.
+ continue;
+ }
+ if (!other_market->is_ready_to_launch_batch(pair.first)) {
+ continue;
+ }
+ launch_batch(pair.first, game);
+ other_market->launch_batch(pair.first, game);
+ break;
+ }
+}
+
+bool Market::is_ready_to_launch_batch(const int trade_id) {
+ const auto it = trade_orders_.find(trade_id);
+ if (it == trade_orders_.end()) {
+ return false;
+ }
+ auto& trade_order = it->second;
+ assert(!trade_order.fulfilled());
+
+ // Do we have all necessary wares for a batch?
+ for (const auto& item_pair : trade_order.items) {
+ const auto wares_it = wares_queue_.find(item_pair.first);
+ if (wares_it == wares_queue_.end()) {
+ return false;
+ }
+ if (wares_it->second->get_filled() < item_pair.second) {
+ return false;
+ }
+ }
+
+ // Do we have enough people to carry wares?
+ int num_available_carriers = 0;
+ for (auto* worker : trade_order.workers) {
+ num_available_carriers += worker->is_idle() ? 1 : 0;
+ }
+ return num_available_carriers == trade_order.num_wares_per_batch();
+}
+
+void Market::launch_batch(const int trade_id, Game* game) {
+ assert(is_ready_to_launch_batch(trade_id));
+ auto& trade_order = trade_orders_.at(trade_id);
+
+ // Do we have all necessary wares for a batch?
+ int worker_index = 0;
+ for (const auto& item_pair : trade_order.items) {
+ for (size_t i = 0; i < item_pair.second; ++i) {
+ Worker* carrier = trade_order.workers.at(worker_index);
+ ++worker_index;
+ assert(carrier->is_idle());
+
+ // Give the carrier a ware.
+ WareInstance* ware =
+ new WareInstance(item_pair.first, game->tribes().get_ware_descr(item_pair.first));
+ ware->init(*game);
+ carrier->set_carried_ware(*game, ware);
+
+ // We have to remove this item from our economy. Otherwise it would be
+ // considered idle (since it has no transport associated with it) and
+ // the engine would want to transfer it to the next warehouse.
+ ware->set_economy(nullptr);
+ wares_queue_.at(item_pair.first)
+ ->set_filled(wares_queue_.at(item_pair.first)->get_filled() - 1);
+
+ // Send the carrier going.
+ carrier->reset_tasks(*game);
+ carrier->start_task_carry_trade_item(
+ *game, trade_id, ObjectPointer(game->objects().get_object(trade_order.other_side)));
+ }
+ }
+}
+
+void Market::ensure_wares_queue_exists(int ware_index) {
+ if (wares_queue_.count(ware_index) > 0) {
+ return;
+ }
+ wares_queue_[ware_index] =
+ std::unique_ptr<WaresQueue>(new WaresQueue(*this, ware_index, kMaxPerItemTradeBatchSize));
+ wares_queue_[ware_index]->set_callback(Market::ware_arrived_callback, this);
+}
+
+InputQueue& Market::inputqueue(DescriptionIndex index, WareWorker ware_worker) {
+ assert(ware_worker == wwWARE);
+ auto it = wares_queue_.find(index);
+ if (it != wares_queue_.end()) {
+ return *it->second;
+ }
+ // The parent will throw an exception.
+ return Building::inputqueue(index, ware_worker);
+}
+
+void Market::cleanup(EditorGameBase& egbase) {
+ for (auto& pair : wares_queue_) {
+ pair.second->cleanup();
+ }
+ Building::cleanup(egbase);
+}
+
+void Market::traded_ware_arrived(const int trade_id, const DescriptionIndex ware_index, Game* game) {
+ auto& trade_order = trade_orders_.at(trade_id);
+
+ WareInstance* ware = new WareInstance(ware_index, game->tribes().get_ware_descr(ware_index));
+ ware->init(*game);
+
+ // TODO(sirver,trading): This is a hack. We should have a worker that
+ // carriers stuff out. At the moment this assumes this market is barbarians
+ // (which is always correct right now), creates a carrier for each received
+ // ware to drop it off. The carrier then leaves the building and goes home.
+ const WorkerDescr& w_desc =
+ *game->tribes().get_worker_descr(game->tribes().worker_index("barbarians_carrier"));
+ auto& worker = w_desc.create(*game, owner(), this, position_);
+ worker.start_task_dropoff(*game, *ware);
+ trade_order.received_traded_wares_in_this_batch += 1;
+ owner().ware_produced(ware_index);
+
+ auto* other_market = dynamic_cast<Market*>(game->objects().get_object(trade_order.other_side));
+ assert(other_market != nullptr);
+ other_market->owner().ware_consumed(ware_index, 1);
+ auto& other_trade_order = other_market->trade_orders_.at(trade_id);
+ if (trade_order.received_traded_wares_in_this_batch == other_trade_order.num_wares_per_batch() &&
+ other_trade_order.received_traded_wares_in_this_batch == trade_order.num_wares_per_batch()) {
+ // This batch is completed.
+ ++trade_order.num_shipped_batches;
+ trade_order.received_traded_wares_in_this_batch = 0;
+ ++other_trade_order.num_shipped_batches;
+ other_trade_order.received_traded_wares_in_this_batch = 0;
+ if (trade_order.fulfilled()) {
+ assert(other_trade_order.fulfilled());
+ // TODO(sirver,trading): This is not quite correct. This should for
+ // example send a differnet message than actually canceling a trade.
+ game->cancel_trade(trade_id);
+ }
+ }
+}
+
} // namespace Widelands
=== 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-09-22 21:01:30 +0000
@@ -20,6 +20,10 @@
#ifndef WL_LOGIC_MAP_OBJECTS_TRIBES_MARKET_H
#define WL_LOGIC_MAP_OBJECTS_TRIBES_MARKET_H
+#include <memory>
+
+#include "economy/request.h"
+#include "economy/wares_queue.h"
#include "logic/map_objects/tribes/building.h"
namespace Widelands {
@@ -32,8 +36,10 @@
Building& create_object() const override;
+ DescriptionIndex carrier() const { return carrier_; }
+
private:
- BillOfMaterials working_positions_;
+ DescriptionIndex carrier_;
};
class Market : public Building {
@@ -42,7 +48,55 @@
explicit Market(const MarketDescr& descr);
~Market() override;
+ void new_trade(int trade_id, const BillOfMaterials& items, int num_batches, Serial other_side);
+ void cancel_trade(int trade_id);
+
+ InputQueue& inputqueue(DescriptionIndex, WareWorker) override;
+ void cleanup(EditorGameBase&) override;
+
+ void try_launching_batch(Game* game);
+ void traded_ware_arrived(int trade_id, DescriptionIndex ware_index, Game* game);
+
private:
+ struct WareRequest {
+ int index;
+ std::unique_ptr<Request> request;
+ };
+
+ struct TradeOrder {
+ BillOfMaterials items;
+ int initial_num_batches;
+ int num_shipped_batches;
+ Serial other_side;
+
+ int received_traded_wares_in_this_batch;
+
+ // The invariant here is that worker.size() + worker_request.get_count()
+ // == 'num_wares_per_batch()'
+ std::unique_ptr<Request> worker_request;
+ std::vector<Worker*> workers;
+
+ std::vector<WareRequest> ware_requests;
+
+ // The number of individual wares in 'items', i.e. the sum of all '.second's.
+ int num_wares_per_batch() const;
+
+ // True if the 'num_shipped_batches' equals the 'initial_num_batches'
+ bool fulfilled() const;
+ };
+
+ static void
+ worker_arrived_callback(Game&, Request&, DescriptionIndex, Worker*, PlayerImmovable&);
+ static void
+ ware_arrived_callback(Game& g, InputQueue* q, DescriptionIndex ware, Worker* worker, void* data);
+
+ void ensure_wares_queue_exists(int ware_index);
+ bool is_ready_to_launch_batch(int trade_id);
+ void launch_batch(int trade_id, Game* game);
+
+ std::map<int, TradeOrder> trade_orders_; // Key is 'trade_id's.
+ std::map<int, std::unique_ptr<WaresQueue>> wares_queue_; // Key is 'ware_index'.
+
DISALLOW_COPY_AND_ASSIGN(Market);
};
=== 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-09-22 21:01:30 +0000
@@ -49,8 +49,9 @@
constexpr size_t STATISTICS_VECTOR_LENGTH = 20;
-} // namespace
-
+// Parses the descriptions of the working positions from 'items_table' and
+// fills in 'working_positions'. Throws an error if the table contains invalid
+// values.
void parse_working_positions(const EditorGameBase& egbase,
LuaTable* items_table,
BillOfMaterials* working_positions) {
@@ -71,6 +72,8 @@
}
}
+} // namespace
+
/*
==============================================================================
=== 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-09-22 21:01:30 +0000
@@ -375,13 +375,6 @@
}
};
-// Parses the descriptions of the working positions from 'items_table' and
-// fills in 'working_positions'. Throws an error if the table contains invalid
-// values.
-void parse_working_positions(const EditorGameBase& egbase,
- LuaTable* items_table,
- BillOfMaterials* working_positions);
-
} // namespace Widelands
#endif // end of include guard: WL_LOGIC_MAP_OBJECTS_TRIBES_PRODUCTIONSITE_H
=== 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-09-22 21:01:30 +0000
@@ -24,6 +24,7 @@
#include "base/wexception.h"
#include "graphic/graphic.h"
#include "logic/game_data_error.h"
+#include "logic/map_objects/tribes/market.h"
namespace Widelands {
=== 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-09-22 21:01:30 +0000
@@ -38,7 +38,6 @@
#include "logic/map_objects/tribes/tribe_descr.h"
#include "logic/map_objects/tribes/ware_descr.h"
#include "logic/map_objects/tribes/warehouse.h"
-#include "logic/map_objects/tribes/market.h"
#include "logic/map_objects/tribes/worker_descr.h"
#include "scripting/lua_table.h"
=== 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-09-22 21:01:30 +0000
@@ -47,6 +47,7 @@
#include "logic/map_objects/terrain_affinity.h"
#include "logic/map_objects/tribes/carrier.h"
#include "logic/map_objects/tribes/dismantlesite.h"
+#include "logic/map_objects/tribes/market.h"
#include "logic/map_objects/tribes/soldier.h"
#include "logic/map_objects/tribes/tribe_descr.h"
#include "logic/map_objects/tribes/warehouse.h"
@@ -1590,7 +1591,89 @@
* is finished.
*/
void Worker::update_task_buildingwork(Game& game) {
- if (top_state().task == &taskBuildingwork)
+ if (top_state().task == &taskCarryTradeItem)
+ send_signal(game, "update");
+}
+
+// The task when a worker is part of the caravane that is trading items.
+const Bob::Task Worker::taskCarryTradeItem = {
+ "carry_trade_item", static_cast<Bob::Ptr>(&Worker::carry_trade_item_update), nullptr, nullptr, true};
+
+void Worker::start_task_carry_trade_item(Game& game,
+ const int trade_id,
+ ObjectPointer other_market) {
+ push_task(game, taskCarryTradeItem);
+ auto& state = top_state();
+ state.ivar1 = 0;
+ state.ivar2 = trade_id;
+ state.objvar1 = other_market;
+}
+
+// This is a state machine: leave building, go to the other market, drop off
+// wares, and return.
+void Worker::carry_trade_item_update(Game& game, State& state) {
+ // Reset any signals that are not related to location
+ std::string signal = get_signal();
+ signal_handled();
+ if (!signal.empty()) {
+ // TODO(sirver,trading): Remove once signals are correctly handled.
+ log("carry_trade_item_update: signal received: %s\n", signal.c_str());
+ }
+ if (signal == "evict") {
+ return pop_task(game);
+ }
+
+ // First of all, make sure we're outside
+ if (state.ivar1 == 0) {
+ start_task_leavebuilding(game, false);
+ ++state.ivar1;
+ return;
+ }
+
+ auto* other_market = dynamic_cast<Market*>(state.objvar1.get(game));
+ if (state.ivar1 == 1) {
+ // Arrived on site. Move to the building and advance our state.
+ if (other_market->base_flag().get_position() == get_position()) {
+ ++state.ivar1;
+ return start_task_move(
+ game, WALK_NW, descr().get_right_walk_anims(does_carry_ware()), true);
+ }
+
+ // Otherwise continue making progress towards the other market.
+ if (!start_task_movepath(game, other_market->base_flag().get_position(), 5,
+ descr().get_right_walk_anims(does_carry_ware()))) {
+ molog("carry_trade_item_update: Could not move to other flag.\n");
+ // TODO(sirver,trading): something needs to happen here.
+ }
+ return;
+ }
+
+ if (state.ivar1 == 2) {
+ WareInstance* const ware = fetch_carried_ware(game);
+ other_market->traded_ware_arrived(state.ivar2, ware->descr_index(), &game);
+ ware->remove(game);
+ ++state.ivar1;
+ start_task_move(game, WALK_SE, descr().get_right_walk_anims(does_carry_ware()), true);
+ return;
+ }
+
+ if (state.ivar1 == 3) {
+ ++state.ivar1;
+ start_task_return(game, false);
+ return;
+ }
+
+ if (state.ivar1 == 4) {
+ pop_task(game);
+ start_task_idle(game, 0, -1);
+ dynamic_cast<Market*>(get_location(game))->try_launching_batch(&game);
+ return;
+ }
+ NEVER_HERE();
+}
+
+void Worker::update_task_carry_trade_item(Game& game) {
+ if (top_state().task == &taskCarryTradeItem)
send_signal(game, "update");
}
=== 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-09-22 21:01:30 +0000
@@ -169,6 +169,9 @@
void start_task_leavebuilding(Game&, bool changelocation);
void start_task_fugitive(Game&);
+ void start_task_carry_trade_item(Game& game, int trade_id, ObjectPointer other_market);
+ void update_task_carry_trade_item(Game&);
+
void
start_task_geologist(Game&, uint8_t attempts, uint8_t radius, const std::string& subcommand);
@@ -208,6 +211,7 @@
static const Task taskFugitive;
static const Task taskGeologist;
static const Task taskScout;
+ static const Task taskCarryTradeItem;
private:
// task details
@@ -232,6 +236,7 @@
void fugitive_update(Game&, State&);
void geologist_update(Game&, State&);
void scout_update(Game&, State&);
+ void carry_trade_item_update(Game&, State&);
// Program commands
bool run_mine(Game&, State&, const Action&);
=== modified file 'src/logic/message.h'
--- src/logic/message.h 2017-02-19 16:56:59 +0000
+++ src/logic/message.h 2017-09-22 21:01:30 +0000
@@ -48,7 +48,8 @@
kWarfare, // everything starting from here is warfare
kWarfareSiteDefeated,
kWarfareSiteLost,
- kWarfareUnderAttack
+ kWarfareUnderAttack,
+ kTradeOfferReceived,
};
/**
=== modified file 'src/logic/playercommand.cc'
--- src/logic/playercommand.cc 2017-06-25 21:56:53 +0000
+++ src/logic/playercommand.cc 2017-09-22 21:01:30 +0000
@@ -29,6 +29,7 @@
#include "io/streamwrite.h"
#include "logic/game.h"
#include "logic/map_objects/map_object.h"
+#include "logic/map_objects/tribes/market.h"
#include "logic/map_objects/tribes/soldier.h"
#include "logic/map_objects/tribes/tribe_descr.h"
#include "logic/player.h"
@@ -52,6 +53,25 @@
return mol.get<T>(object_index).serial();
}
+void serialize_bill_of_materials(const BillOfMaterials& bill, StreamWrite* ser) {
+ ser->unsigned_32(bill.size());
+ for (const WareAmount& amount : bill) {
+ ser->unsigned_8(amount.first);
+ ser->unsigned_32(amount.second);
+ }
+}
+
+BillOfMaterials deserialize_bill_of_materials(StreamRead* des) {
+ BillOfMaterials bill;
+ const int count = des->unsigned_32();
+ for (int i = 0; i < count; ++i) {
+ const auto index = des->unsigned_8();
+ const auto amount = des->unsigned_32();
+ bill.push_back(std::make_pair(index, amount));
+ }
+ return bill;
+}
+
} // namespace
// NOTE keep numbers of existing entries as they are to ensure backward compatible savegame loading
@@ -86,7 +106,8 @@
PLCMD_SHIP_EXPLORE = 27,
PLCMD_SHIP_CONSTRUCT = 28,
PLCMD_SHIP_SINK = 29,
- PLCMD_SHIP_CANCELEXPEDITION = 30
+ PLCMD_SHIP_CANCELEXPEDITION = 30,
+ PLCMD_PROPOSE_TRADE = 31,
};
/*** class PlayerCommand ***/
@@ -1784,4 +1805,75 @@
fw.unsigned_8(ware_);
fw.unsigned_8(static_cast<uint8_t>(policy_));
}
+
+CmdProposeTrade::CmdProposeTrade(uint32_t time, PlayerNumber pn, const Trade& trade)
+ : PlayerCommand(time, pn), trade_(trade) {
+}
+
+CmdProposeTrade::CmdProposeTrade() : PlayerCommand() {
+}
+
+void CmdProposeTrade::execute(Game& game) {
+ Player* plr = game.get_player(sender());
+ if (plr == nullptr) {
+ return;
+ }
+
+ Market* initiator = dynamic_cast<Market*>(game.objects().get_object(trade_.initiator));
+ if (initiator == nullptr) {
+ log("CmdProposeTrade: initiator vanished or is not a market.\n");
+ return;
+ }
+ if (&initiator->owner() != plr) {
+ log("CmdProposeTrade: sender %u, but market owner %u\n", sender(),
+ initiator->owner().player_number());
+ return;
+ }
+ Market* receiver = dynamic_cast<Market*>(game.objects().get_object(trade_.receiver));
+ if (receiver == nullptr) {
+ log("CmdProposeTrade: receiver vanished or is not a market.\n");
+ return;
+ }
+ if (&initiator->owner() == &receiver->owner()) {
+ log("CmdProposeTrade: Sending and receiving player are the same.\n");
+ return;
+ }
+
+ // TODO(sirver,trading): Maybe check connectivity between markets here and
+ // report errors.
+ game.propose_trade(trade_);
+}
+
+CmdProposeTrade::CmdProposeTrade(StreamRead& des) : PlayerCommand(0, des.unsigned_8()) {
+ trade_.initiator = des.unsigned_32();
+ trade_.receiver = des.unsigned_32();
+ trade_.send_items = deserialize_bill_of_materials(&des);
+ trade_.received_items = deserialize_bill_of_materials(&des);
+ trade_.num_batches = des.signed_32();
+}
+
+void CmdProposeTrade::serialize(StreamWrite& ser) {
+ ser.unsigned_8(PLCMD_PROPOSE_TRADE);
+ ser.unsigned_8(sender());
+ ser.unsigned_32(trade_.initiator);
+ ser.unsigned_32(trade_.receiver);
+ serialize_bill_of_materials(trade_.send_items, &ser);
+ serialize_bill_of_materials(trade_.received_items, &ser);
+ ser.signed_32(trade_.num_batches);
+}
+
+void CmdProposeTrade::read(FileRead& /* fr */,
+ EditorGameBase& /* egbase */,
+ MapObjectLoader& /* mol */) {
+ // TODO(sirver,trading): Implement this.
+ NEVER_HERE();
+}
+
+void CmdProposeTrade::write(FileWrite& /* fw */,
+ EditorGameBase& /* egbase */,
+ MapObjectSaver& /* mos */) {
+ // TODO(sirver,trading): Implement this.
+ NEVER_HERE();
+}
+
}
=== modified file 'src/logic/playercommand.h'
--- src/logic/playercommand.h 2017-08-16 13:23:15 +0000
+++ src/logic/playercommand.h 2017-09-22 21:01:30 +0000
@@ -846,6 +846,29 @@
DescriptionIndex ware_;
Warehouse::StockPolicy policy_;
};
+
+struct CmdProposeTrade : PlayerCommand {
+ CmdProposeTrade(uint32_t time, PlayerNumber pn, const Trade& trade);
+
+ QueueCommandTypes id() const override {
+ return QueueCommandTypes::kProposeTrade;
+ }
+
+ void execute(Game& game) override;
+
+ // Network (de-)serialization
+ explicit CmdProposeTrade(StreamRead& des);
+ void serialize(StreamWrite& ser) override;
+
+ // Savegame functions
+ CmdProposeTrade();
+ void write(FileWrite&, EditorGameBase&, MapObjectSaver&) override;
+ void read(FileRead&, EditorGameBase&, MapObjectLoader&) override;
+
+private:
+ Trade trade_;
+};
+
}
#endif // end of include guard: WL_LOGIC_PLAYERCOMMAND_H
=== 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-09-22 21:01:30 +0000
@@ -78,6 +78,8 @@
return *new CmdEvictWorker();
case QueueCommandTypes::kMilitarysiteSetSoldierPreference:
return *new CmdMilitarySiteSetSoldierPreference();
+ case QueueCommandTypes::kProposeTrade:
+ return *new CmdProposeTrade();
case QueueCommandTypes::kSinkShip:
return *new CmdShipSink();
case QueueCommandTypes::kShipCancelExpedition:
=== 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-09-22 21:01:30 +0000
@@ -70,7 +70,8 @@
kEvictWorker,
- kMilitarysiteSetSoldierPreference, // 26
+ kMilitarysiteSetSoldierPreference,
+ kProposeTrade, // 27
kSinkShip = 121,
kShipCancelExpedition,
=== 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-09-22 21:01:30 +0000
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2006-2017 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef WL_LOGIC_TRADE_AGREEMENT_H
+#define WL_LOGIC_TRADE_AGREEMENT_H
+
+#include "logic/map_objects/map_object.h"
+
+namespace Widelands {
+
+// TODO(sirver,trading): Document everything in here.
+
+// Maximal number of a single ware that can be contained in a trade batch.
+constexpr int kMaxPerItemTradeBatchSize = 15;
+
+struct Trade {
+ BillOfMaterials send_items;
+ BillOfMaterials received_items;
+ int num_batches;
+ Serial initiator;
+ Serial receiver;
+};
+
+// TODO(sirver,trading): This class should probably be private to 'Game'.
+struct TradeAgreement {
+ enum class State {
+ kProposed,
+ kRunning,
+ };
+
+ State state;
+ Trade trade;
+};
+
+} // namespace Widelands
+
+
+#endif // end of include guard: WL_LOGIC_TRADE_AGREEMENT_H
=== 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-09-22 21:01:30 +0000
@@ -339,6 +339,7 @@
}
}
+ // TODO(sirver,trading): Pull out and reuse this for market workers.
assert(warehouse.incorporated_workers_.empty());
{
uint16_t const nrworkers = fr.unsigned_16();
=== 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-09-22 21:01:30 +0000
@@ -33,6 +33,7 @@
#include "logic/map_objects/immovable.h"
#include "logic/map_objects/terrain_affinity.h"
#include "logic/map_objects/tribes/carrier.h"
+#include "logic/map_objects/tribes/market.h"
#include "logic/map_objects/tribes/ship.h"
#include "logic/map_objects/tribes/soldier.h"
#include "logic/map_objects/tribes/tribes.h"
@@ -594,6 +595,52 @@
}
return 0;
}
+
+// Parses a table of name/count pairs as given from Lua.
+void parse_wares_workers(lua_State* L,
+ int table_index,
+ const TribeDescr& tribe,
+ InputMap* ware_workers_list,
+ bool is_ware) {
+ luaL_checktype(L, table_index, LUA_TTABLE);
+ lua_pushnil(L);
+ while (lua_next(L, table_index) != 0) {
+ if (is_ware) {
+ if (tribe.ware_index(luaL_checkstring(L, -2)) == INVALID_INDEX) {
+ report_error(L, "Illegal ware %s", luaL_checkstring(L, -2));
+ }
+ } else {
+ if (tribe.worker_index(luaL_checkstring(L, -2)) == INVALID_INDEX) {
+ report_error(L, "Illegal worker %s", luaL_checkstring(L, -2));
+ }
+ }
+
+ if (is_ware) {
+ ware_workers_list->insert(
+ std::make_pair(std::make_pair(tribe.ware_index(luaL_checkstring(L, -2)),
+ Widelands::WareWorker::wwWARE),
+ luaL_checkuint32(L, -1)));
+ } else {
+ ware_workers_list->insert(
+ std::make_pair(std::make_pair(tribe.worker_index(luaL_checkstring(L, -2)),
+ Widelands::WareWorker::wwWORKER),
+ luaL_checkuint32(L, -1)));
+ }
+ lua_pop(L, 1);
+ }
+}
+
+BillOfMaterials parse_wares_as_bill_of_material(lua_State* L, int table_index,
+ const TribeDescr& tribe) {
+ InputMap input_map;
+ parse_wares_workers(L, table_index, tribe, &input_map, true /* is_ware */);
+ BillOfMaterials result;
+ for (const auto& pair : input_map) {
+ result.push_back(std::make_pair(pair.first.first, pair.second));
+ }
+ return result;
+}
+
} // namespace
/*
@@ -782,7 +829,6 @@
// We either received, two items string,int:
if (nargs == 3) {
-
result = RequestedWareWorker::kSingle;
if (is_ware) {
if (tribe.ware_index(luaL_checkstring(L, 2)) == INVALID_INDEX) {
@@ -803,32 +849,7 @@
} else {
result = RequestedWareWorker::kList;
// or we got a table with name:quantity
- luaL_checktype(L, 2, LUA_TTABLE);
- lua_pushnil(L);
- while (lua_next(L, 2) != 0) {
- if (is_ware) {
- if (tribe.ware_index(luaL_checkstring(L, -2)) == INVALID_INDEX) {
- report_error(L, "Illegal ware %s", luaL_checkstring(L, -2));
- }
- } else {
- if (tribe.worker_index(luaL_checkstring(L, -2)) == INVALID_INDEX) {
- report_error(L, "Illegal worker %s", luaL_checkstring(L, -2));
- }
- }
-
- if (is_ware) {
- ware_workers_list->insert(
- std::make_pair(std::make_pair(tribe.ware_index(luaL_checkstring(L, -2)),
- Widelands::WareWorker::wwWARE),
- luaL_checkuint32(L, -1)));
- } else {
- ware_workers_list->insert(
- std::make_pair(std::make_pair(tribe.worker_index(luaL_checkstring(L, -2)),
- Widelands::WareWorker::wwWORKER),
- luaL_checkuint32(L, -1)));
- }
- lua_pop(L, 1);
- }
+ parse_wares_workers(L, 2, tribe, ware_workers_list, is_ware);
}
return result;
}
@@ -2762,6 +2783,8 @@
trading over land with other players. See the parent classes for more
properties.
*/
+// TODO(sirver,trading): Expose the properties of MarketDescription here once
+// the interface settles.
const char LuaMarketDescription::className[] = "MarketDescription";
const MethodType<LuaMarketDescription> LuaMarketDescription::Methods[] = {
{nullptr, nullptr},
@@ -5083,6 +5106,7 @@
*/
const char LuaMarket::className[] = "Market";
const MethodType<LuaMarket> LuaMarket::Methods[] = {
+ METHOD(LuaMarket, propose_trade),
// TODO(sirver,trading): Implement and fix documentation.
// METHOD(LuaMarket, set_wares),
// METHOD(LuaMarket, get_wares),
@@ -5106,6 +5130,35 @@
==========================================================
*/
+/* RST
+ .. method:: propose_trade(other_market, num_batches, send_items, received_items)
+
+ TODO(sirver,trading): document
+
+ :returns: :const:`nil`
+*/
+int LuaMarket::propose_trade(lua_State* L) {
+ if (lua_gettop(L) != 5) {
+ report_error(L, "Takes 4 arguments.");
+ }
+ Game& game = get_game(L);
+ Market* self = get(L, game);
+ Market* other_market = (*get_user_class<LuaMarket>(L, 2))->get(L, game);
+ const int num_batches = luaL_checkinteger(L, 3);
+
+ const BillOfMaterials send_items = parse_wares_as_bill_of_material(L, 4, self->owner().tribe());
+ // TODO(sirver,trading): unsure if correct. Test inter-tribe trading, i.e.
+ // barbarians trading with empire, but shipping atlantean only wares.
+ const BillOfMaterials received_items = parse_wares_as_bill_of_material(L, 5, self->owner().tribe());
+ const int trade_id = game.propose_trade(
+ Trade{send_items, received_items, num_batches, self->serial(), other_market->serial()});
+
+ // TODO(sirver,trading): Wrap 'Trade' into its own Lua class?
+ lua_pushint32(L, trade_id);
+ return 1;
+}
+
+
/*
==========================================================
C METHODS
=== 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-09-22 21:01:30 +0000
@@ -29,6 +29,7 @@
#include "logic/game.h"
#include "logic/map_objects/tribes/constructionsite.h"
#include "logic/map_objects/tribes/dismantlesite.h"
+#include "logic/map_objects/tribes/market.h"
#include "logic/map_objects/tribes/militarysite.h"
#include "logic/map_objects/tribes/productionsite.h"
#include "logic/map_objects/tribes/ship.h"
@@ -1125,6 +1126,7 @@
/*
* Lua Methods
*/
+ int propose_trade(lua_State* L);
/*
* C Methods
=== 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-09-22 21:01:30 +0000
@@ -487,6 +487,7 @@
case Widelands::Message::Type::kWarfareSiteDefeated:
case Widelands::Message::Type::kWarfareSiteLost:
case Widelands::Message::Type::kWarfareUnderAttack:
+ case Widelands::Message::Type::kTradeOfferReceived:
set_filter_messages_tooltips();
message_filter_ = Widelands::Message::Type::kAllMessages;
geologistsbtn_->set_perm_pressed(false);
@@ -580,6 +581,7 @@
case Widelands::Message::Type::kWarfareSiteDefeated:
case Widelands::Message::Type::kWarfareSiteLost:
case Widelands::Message::Type::kWarfareUnderAttack:
+ case Widelands::Message::Type::kTradeOfferReceived:
return "images/wui/messages/message_new.png";
}
NEVER_HERE();
=== 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-09-22 21:01:30 +0000
@@ -103,8 +103,7 @@
®istry,
kPlotWidth + 2 * kSpacing,
270,
- _("Ware Statistics")),
- parent_(&parent) {
+ _("Ware Statistics")) {
uint8_t const nr_wares = parent.get_player()->egbase().tribes().nrwares();
// Init color sets
=== 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-09-22 21:01:30 +0000
@@ -37,7 +37,6 @@
void set_time(int32_t);
private:
- InteractivePlayer* parent_;
WuiPlotArea* plot_production_;
WuiPlotArea* plot_consumption_;
WuiPlotArea* plot_stock_;
=== 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-09-22 21:01:30 +0000
@@ -1,6 +1,6 @@
-include "scripting/lunit.lua"
include "scripting/coroutine.lua"
include "scripting/infrastructure.lua"
+include "scripting/lunit.lua"
include "scripting/ui.lua"
game = wl.Game()
@@ -22,7 +22,17 @@
end
end
+function place_markets()
+ prefilled_buildings(p1, { "barbarians_market", 24, 25 })
+ market_p1 = map:get_field(24, 25).immovable
+ connected_road(p1, market_p1.flag, "l,l|", true)
+
+ prefilled_buildings(p2, { "barbarians_market", 29, 25 })
+ market_p2 = map:get_field(29, 25).immovable
+ connected_road(p2, market_p2.flag, "r,r|", true)
+end
+
full_headquarters(p1, 22, 25)
-full_headquarters(p2, 32, 25)
+full_headquarters(p2, 31, 25)
game.desired_speed = 50000
=== 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'
=== 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-09-22 21:01:30 +0000
@@ -0,0 +1,39 @@
+run(function()
+ sleep(2000)
+ place_markets()
+ market_p2:propose_trade(market_p1, 5, { log = 3 }, { granite = 2, iron = 1 })
+
+ local p1_initial = {
+ iron = p1:get_wares("iron"),
+ log = p1:get_wares("log"),
+ granite = p1:get_wares("granite"),
+ }
+ local p2_initial = {
+ iron = p1:get_wares("iron"),
+ log = p1:get_wares("log"),
+ granite = p1:get_wares("granite"),
+ }
+
+ -- We await until one ware we trade has the right count for one player.
+ -- Then, we'll sleep half as long as we already waited to make sure that no
+ -- additional batches are shipped. Then we check all stocks for the correct
+ -- numbers.
+ local start_time = game.time
+ while p2:get_wares("iron") - p2_initial["iron"] < 5 do
+ sleep(10000)
+ end
+
+ sleep(math.ceil((game.time - start_time) / 2))
+
+ assert_equal(5, p2:get_wares("iron") - p2_initial["iron"])
+ assert_equal(10, p2:get_wares("granite") - p2_initial["granite"])
+ assert_equal(-15, p2:get_wares("log") - p2_initial["log"])
+
+ assert_equal(-5, p1:get_wares("iron") - p1_initial["iron"])
+ assert_equal(-10, p1:get_wares("granite") - p1_initial["granite"])
+ assert_equal(15, p1:get_wares("log") - p1_initial["log"])
+
+ print("# All Tests passed.")
+ wl.ui.MapView():close()
+end)
+
Follow ups
-
Re: [Merge] lp:~widelands-dev/widelands/market1 into lp:widelands
From: GunChleoc, 2017-10-04
-
[Merge] lp:~widelands-dev/widelands/market1 into lp:widelands
From: noreply, 2017-10-04
-
Re: [Merge] lp:~widelands-dev/widelands/market1 into lp:widelands
From: SirVer, 2017-10-03
-
Re: [Merge] lp:~widelands-dev/widelands/market1 into lp:widelands
From: GunChleoc, 2017-09-29
-
Re: [Merge] lp:~widelands-dev/widelands/market1 into lp:widelands
From: TiborB, 2017-09-26
-
[Merge] lp:~widelands-dev/widelands/market1 into lp:widelands
From: bunnybot, 2017-09-23
-
Re: [Merge] lp:~widelands-dev/widelands/market1 into lp:widelands
From: SirVer, 2017-09-23
-
Re: [Merge] lp:~widelands-dev/widelands/market1 into lp:widelands
From: SirVer, 2017-09-23
-
[Merge] lp:~widelands-dev/widelands/market1 into lp:widelands
From: bunnybot, 2017-09-23
-
Re: [Merge] lp:~widelands-dev/widelands/market1 into lp:widelands
From: SirVer, 2017-09-22