1 /*
2 * Copyright (C) 2006-2020 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 #include "logic/map_objects/tribes/market.h"
21
22 #include <memory>
23
24 #include "base/i18n.h"
25 #include "logic/map_objects/tribes/productionsite.h"
26 #include "logic/map_objects/tribes/tribes.h"
27 #include "logic/player.h"
28
29 namespace Widelands {
30
MarketDescr(const std::string & init_descname,const LuaTable & table,const Tribes & tribes)31 MarketDescr::MarketDescr(const std::string& init_descname,
32 const LuaTable& table,
33 const Tribes& tribes)
34 : BuildingDescr(init_descname, MapObjectType::MARKET, table, tribes) {
35 i18n::Textdomain td("tribes");
36
37 DescriptionIndex const woi = tribes.worker_index(table.get_string("carrier"));
38 if (!tribes.worker_exists(woi)) {
39 throw wexception("The tribe does not define the worker in 'carrier'.");
40 }
41 carrier_ = woi;
42
43 // TODO(sirver,trading): Add actual logic here.
44 }
45
create_object() const46 Building& MarketDescr::create_object() const {
47 return *new Market(*this);
48 }
49
num_wares_per_batch() const50 int Market::TradeOrder::num_wares_per_batch() const {
51 int sum = 0;
52 for (const auto& item_pair : items) {
53 sum += item_pair.second;
54 }
55 return sum;
56 }
57
fulfilled() const58 bool Market::TradeOrder::fulfilled() const {
59 return num_shipped_batches == initial_num_batches;
60 }
61
62 // TODO(sirver,trading): This needs to implement 'set_economy'. Maybe common code can be shared.
Market(const MarketDescr & the_descr)63 Market::Market(const MarketDescr& the_descr) : Building(the_descr) {
64 }
65
~Market()66 Market::~Market() {
67 }
68
new_trade(const int trade_id,const BillOfMaterials & items,const int num_batches,const Serial other_side)69 void Market::new_trade(const int trade_id,
70 const BillOfMaterials& items,
71 const int num_batches,
72 const Serial other_side) {
73 trade_orders_[trade_id] = TradeOrder{items, num_batches, 0, other_side, 0, nullptr, {}};
74 auto& trade_order = trade_orders_[trade_id];
75
76 // Request one worker for each item in a batch.
77 trade_order.worker_request.reset(
78 new Request(*this, descr().carrier(), Market::worker_arrived_callback, wwWORKER));
79 trade_order.worker_request->set_count(trade_order.num_wares_per_batch());
80
81 // Make sure we have a wares queue for each item in this.
82 for (const auto& entry : items) {
83 ensure_wares_queue_exists(entry.first);
84 }
85 }
86
cancel_trade(const int trade_id)87 void Market::cancel_trade(const int trade_id) {
88 // TODO(sirver,trading): Launch workers, release no longer required wares and delete now unneeded
89 // 'WaresQueue's
90 trade_orders_.erase(trade_id);
91 }
92
worker_arrived_callback(Game & game,Request & rq,DescriptionIndex,Worker * const w,PlayerImmovable & target)93 void Market::worker_arrived_callback(
94 Game& game, Request& rq, DescriptionIndex /* widx */, Worker* const w, PlayerImmovable& target) {
95 auto& market = dynamic_cast<Market&>(target);
96
97 assert(w);
98 assert(w->get_location(game) == &market);
99
100 for (auto& trade_order_pair : market.trade_orders_) {
101 auto& trade_order = trade_order_pair.second;
102 if (trade_order.worker_request.get() != &rq) {
103 continue;
104 }
105
106 if (rq.get_count() == 0) {
107 // Erase this request.
108 trade_order.worker_request.reset();
109 }
110 w->start_task_idle(game, 0, -1);
111 trade_order.workers.push_back(w);
112 Notifications::publish(NoteBuilding(market.serial(), NoteBuilding::Action::kWorkersChanged));
113 market.try_launching_batch(&game);
114 return;
115 }
116 NEVER_HERE(); // We should have found and handled a match by now.
117 }
118
ware_arrived_callback(Game & g,InputQueue *,DescriptionIndex,Worker *,void * data)119 void Market::ware_arrived_callback(Game& g, InputQueue*, DescriptionIndex, Worker*, void* data) {
120 Market& market = *static_cast<Market*>(data);
121 market.try_launching_batch(&g);
122 }
123
try_launching_batch(Game * game)124 void Market::try_launching_batch(Game* game) {
125 for (auto& pair : trade_orders_) {
126 if (!is_ready_to_launch_batch(pair.first)) {
127 continue;
128 }
129
130 auto* other_market =
131 dynamic_cast<Market*>(game->objects().get_object(pair.second.other_side));
132 if (other_market == nullptr) {
133 // TODO(sirver,trading): Can this even happen? Where is this function called from?
134 // The other market seems to have vanished. The game tracks this and
135 // should soon delete this trade request from us. We just ignore it.
136 continue;
137 }
138 if (!other_market->is_ready_to_launch_batch(pair.first)) {
139 continue;
140 }
141 launch_batch(pair.first, game);
142 other_market->launch_batch(pair.first, game);
143 break;
144 }
145 }
146
is_ready_to_launch_batch(const int trade_id)147 bool Market::is_ready_to_launch_batch(const int trade_id) {
148 const auto it = trade_orders_.find(trade_id);
149 if (it == trade_orders_.end()) {
150 return false;
151 }
152 auto& trade_order = it->second;
153 assert(!trade_order.fulfilled());
154
155 // Do we have all necessary wares for a batch?
156 for (const auto& item_pair : trade_order.items) {
157 const auto wares_it = wares_queue_.find(item_pair.first);
158 if (wares_it == wares_queue_.end()) {
159 return false;
160 }
161 if (wares_it->second->get_filled() < item_pair.second) {
162 return false;
163 }
164 }
165
166 // Do we have enough people to carry wares?
167 int num_available_carriers = 0;
168 for (auto* worker : trade_order.workers) {
169 num_available_carriers += worker->is_idle() ? 1 : 0;
170 }
171 return num_available_carriers == trade_order.num_wares_per_batch();
172 }
173
launch_batch(const int trade_id,Game * game)174 void Market::launch_batch(const int trade_id, Game* game) {
175 assert(is_ready_to_launch_batch(trade_id));
176 auto& trade_order = trade_orders_.at(trade_id);
177
178 // Do we have all necessary wares for a batch?
179 int worker_index = 0;
180 for (const auto& item_pair : trade_order.items) {
181 for (size_t i = 0; i < item_pair.second; ++i) {
182 Worker* carrier = trade_order.workers.at(worker_index);
183 ++worker_index;
184 assert(carrier->is_idle());
185
186 // Give the carrier a ware.
187 WareInstance* ware =
188 new WareInstance(item_pair.first, game->tribes().get_ware_descr(item_pair.first));
189 ware->init(*game);
190 carrier->set_carried_ware(*game, ware);
191
192 // We have to remove this item from our economy. Otherwise it would be
193 // considered idle (since it has no transport associated with it) and
194 // the engine would want to transfer it to the next warehouse.
195 ware->set_economy(nullptr);
196 wares_queue_.at(item_pair.first)
197 ->set_filled(wares_queue_.at(item_pair.first)->get_filled() - 1);
198
199 // Send the carrier going.
200 carrier->reset_tasks(*game);
201 carrier->start_task_carry_trade_item(
202 *game, trade_id, ObjectPointer(game->objects().get_object(trade_order.other_side)));
203 }
204 }
205 }
206
ensure_wares_queue_exists(int ware_index)207 void Market::ensure_wares_queue_exists(int ware_index) {
208 if (wares_queue_.count(ware_index) > 0) {
209 return;
210 }
211 wares_queue_[ware_index] =
212 std::unique_ptr<WaresQueue>(new WaresQueue(*this, ware_index, kMaxPerItemTradeBatchSize));
213 wares_queue_[ware_index]->set_callback(Market::ware_arrived_callback, this);
214 }
215
inputqueue(DescriptionIndex index,WareWorker ware_worker)216 InputQueue& Market::inputqueue(DescriptionIndex index, WareWorker ware_worker) {
217 assert(ware_worker == wwWARE);
218 auto it = wares_queue_.find(index);
219 if (it != wares_queue_.end()) {
220 return *it->second;
221 }
222 // The parent will throw an exception.
223 return Building::inputqueue(index, ware_worker);
224 }
225
cleanup(EditorGameBase & egbase)226 void Market::cleanup(EditorGameBase& egbase) {
227 for (auto& pair : wares_queue_) {
228 pair.second->cleanup();
229 }
230 Building::cleanup(egbase);
231 }
232
traded_ware_arrived(const int trade_id,const DescriptionIndex ware_index,Game * game)233 void Market::traded_ware_arrived(const int trade_id,
234 const DescriptionIndex ware_index,
235 Game* game) {
236 auto& trade_order = trade_orders_.at(trade_id);
237
238 WareInstance* ware = new WareInstance(ware_index, game->tribes().get_ware_descr(ware_index));
239 ware->init(*game);
240
241 // TODO(sirver,trading): This is a hack. We should have a worker that
242 // carriers stuff out. At the moment this assumes this market is barbarians
243 // (which is always correct right now), creates a carrier for each received
244 // ware to drop it off. The carrier then leaves the building and goes home.
245 const WorkerDescr& w_desc =
246 *game->tribes().get_worker_descr(game->tribes().worker_index("barbarians_carrier"));
247 auto& worker = w_desc.create(*game, get_owner(), this, position_);
248 worker.start_task_dropoff(*game, *ware);
249 ++trade_order.received_traded_wares_in_this_batch;
250 get_owner()->ware_produced(ware_index);
251
252 auto* other_market = dynamic_cast<Market*>(game->objects().get_object(trade_order.other_side));
253 assert(other_market != nullptr);
254 other_market->get_owner()->ware_consumed(ware_index, 1);
255 auto& other_trade_order = other_market->trade_orders_.at(trade_id);
256 if (trade_order.received_traded_wares_in_this_batch == other_trade_order.num_wares_per_batch() &&
257 other_trade_order.received_traded_wares_in_this_batch == trade_order.num_wares_per_batch()) {
258 // This batch is completed.
259 ++trade_order.num_shipped_batches;
260 trade_order.received_traded_wares_in_this_batch = 0;
261 ++other_trade_order.num_shipped_batches;
262 other_trade_order.received_traded_wares_in_this_batch = 0;
263 if (trade_order.fulfilled()) {
264 assert(other_trade_order.fulfilled());
265 // TODO(sirver,trading): This is not quite correct. This should for
266 // example send a differnet message than actually canceling a trade.
267 game->cancel_trade(trade_id);
268 }
269 }
270 }
271
272 } // namespace Widelands
273