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