1 /*
2  * Copyright (C) 2002-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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  *
18  */
19 
20 #include "logic/map_objects/tribes/tribe_descr.h"
21 
22 #include <memory>
23 
24 #include "base/i18n.h"
25 #include "base/wexception.h"
26 #include "graphic/animation/animation_manager.h"
27 #include "graphic/graphic.h"
28 #include "io/filesystem/layered_filesystem.h"
29 #include "logic/game.h"
30 #include "logic/game_data_error.h"
31 #include "logic/map_objects/immovable.h"
32 #include "logic/map_objects/tribes/carrier.h"
33 #include "logic/map_objects/tribes/constructionsite.h"
34 #include "logic/map_objects/tribes/dismantlesite.h"
35 #include "logic/map_objects/tribes/militarysite.h"
36 #include "logic/map_objects/tribes/ship.h"
37 #include "logic/map_objects/tribes/soldier.h"
38 #include "logic/map_objects/tribes/trainingsite.h"
39 #include "logic/map_objects/tribes/warehouse.h"
40 #include "logic/map_objects/tribes/worker.h"
41 #include "logic/map_objects/world/resource_description.h"
42 #include "logic/map_objects/world/world.h"
43 #include "scripting/lua_table.h"
44 
45 namespace Widelands {
46 
47 /**
48  * The contents of 'table' are documented in
49  * /data/tribes/atlanteans.lua
50  */
TribeDescr(const LuaTable & table,const Widelands::TribeBasicInfo & info,const Tribes & init_tribes)51 TribeDescr::TribeDescr(const LuaTable& table,
52                        const Widelands::TribeBasicInfo& info,
53                        const Tribes& init_tribes)
54    : name_(table.get_string("name")),
55      descname_(info.descname),
56      tribes_(init_tribes),
57      bridge_height_(table.get_int("bridge_height")) {
58 
59 	try {
60 		initializations_ = info.initializations;
61 
62 		std::unique_ptr<LuaTable> items_table = table.get_table("roads");
63 		const auto load_roads = [&items_table](
64 		   const std::string& road_type, std::vector<std::string>* images) {
65 			std::vector<std::string> roads =
66 			   items_table->get_table(road_type)->array_entries<std::string>();
67 			for (const std::string& filename : roads) {
68 				if (g_fs->file_exists(filename)) {
69 					images->push_back(filename);
70 				} else {
71 					throw GameDataError("File '%s' for %s road texture doesn't exist", filename.c_str(),
72 					                    road_type.c_str());
73 				}
74 			}
75 			if (images->empty()) {
76 				throw GameDataError("Tribe has no %s roads.", road_type.c_str());
77 			}
78 		};
79 		load_roads("normal", &normal_road_paths_);
80 		load_roads("busy", &busy_road_paths_);
81 		load_roads("waterway", &waterway_paths_);
82 
83 		const auto load_bridge_if_present = [this](
84 		   const LuaTable& animations_table, const std::string& animation_directory,
85 		   Animation::Type animation_type, std::string s_dir, std::string s_type, uint32_t* id) {
86 			const std::string directional_name("bridge_" + s_type + "_" + s_dir);
87 			if (animations_table.has_key(directional_name)) {
88 				std::unique_ptr<LuaTable> animation_table =
89 				   animations_table.get_table(directional_name);
90 				*id = g_gr->animations().load(name_ + std::string("_") + directional_name,
91 				                              *animation_table, directional_name, animation_directory,
92 				                              animation_type);
93 			}
94 		};
95 		// Frontier and flag animations can be a mix of file and spritesheet animations
96 		const auto load_animations = [this, load_bridge_if_present](
97 		   const LuaTable& animations_table, const std::string& animation_directory,
98 		   Animation::Type animation_type) {
99 			if (animations_table.has_key("frontier")) {
100 				std::unique_ptr<LuaTable> animation_table = animations_table.get_table("frontier");
101 				frontier_animation_id_ =
102 				   g_gr->animations().load(name_ + std::string("_frontier"), *animation_table,
103 				                           "frontier", animation_directory, animation_type);
104 			}
105 			if (animations_table.has_key("flag")) {
106 				std::unique_ptr<LuaTable> animation_table = animations_table.get_table("flag");
107 				flag_animation_id_ =
108 				   g_gr->animations().load(name_ + std::string("_flag"), *animation_table, "flag",
109 				                           animation_directory, animation_type);
110 			}
111 			load_bridge_if_present(animations_table, animation_directory, animation_type, "e",
112 			                       "normal", &bridges_normal_.e);
113 			load_bridge_if_present(animations_table, animation_directory, animation_type, "se",
114 			                       "normal", &bridges_normal_.se);
115 			load_bridge_if_present(animations_table, animation_directory, animation_type, "sw",
116 			                       "normal", &bridges_normal_.sw);
117 			load_bridge_if_present(
118 			   animations_table, animation_directory, animation_type, "e", "busy", &bridges_busy_.e);
119 			load_bridge_if_present(
120 			   animations_table, animation_directory, animation_type, "se", "busy", &bridges_busy_.se);
121 			load_bridge_if_present(
122 			   animations_table, animation_directory, animation_type, "sw", "busy", &bridges_busy_.sw);
123 		};
124 
125 		std::string animation_directory = table.get_string("animation_directory");
126 		if (table.has_key("animations")) {
127 			load_animations(
128 			   *table.get_table("animations"), animation_directory, Animation::Type::kFiles);
129 		}
130 		if (table.has_key("spritesheets")) {
131 			load_animations(
132 			   *table.get_table("spritesheets"), animation_directory, Animation::Type::kSpritesheet);
133 		}
134 
135 		items_table = table.get_table("wares_order");
136 		for (const int key : items_table->keys<int>()) {
137 			std::vector<DescriptionIndex> column;
138 			std::vector<std::string> warenames =
139 			   items_table->get_table(key)->array_entries<std::string>();
140 			for (size_t rowindex = 0; rowindex < warenames.size(); ++rowindex) {
141 				try {
142 					DescriptionIndex wareindex = tribes_.safe_ware_index(warenames[rowindex]);
143 					if (has_ware(wareindex)) {
144 						throw GameDataError(
145 						   "Duplicate definition of ware '%s'", warenames[rowindex].c_str());
146 					}
147 					wares_.insert(wareindex);
148 					column.push_back(wareindex);
149 				} catch (const WException& e) {
150 					throw GameDataError(
151 					   "Failed adding ware '%s: %s", warenames[rowindex].c_str(), e.what());
152 				}
153 			}
154 			if (!column.empty()) {
155 				wares_order_.push_back(column);
156 			}
157 		}
158 
159 		items_table = table.get_table("workers_order");
160 		for (const int key : items_table->keys<int>()) {
161 			std::vector<DescriptionIndex> column;
162 			for (const std::string& workername :
163 			     items_table->get_table(key)->array_entries<std::string>()) {
164 				add_worker(workername, column);
165 			}
166 			if (!column.empty()) {
167 				workers_order_.push_back(column);
168 			}
169 		}
170 
171 		for (const std::string& immovablename :
172 		     table.get_table("immovables")->array_entries<std::string>()) {
173 			try {
174 				DescriptionIndex index = tribes_.safe_immovable_index(immovablename);
175 				if (immovables_.count(index) == 1) {
176 					throw GameDataError("Duplicate definition of immovable '%s'", immovablename.c_str());
177 				}
178 				immovables_.insert(index);
179 			} catch (const WException& e) {
180 				throw GameDataError(
181 				   "Failed adding immovable '%s': %s", immovablename.c_str(), e.what());
182 			}
183 		}
184 
185 		items_table = table.get_table("resource_indicators");
186 		for (std::string resource : items_table->keys<std::string>()) {
187 			ResourceIndicatorList resis;
188 			std::unique_ptr<LuaTable> tbl = items_table->get_table(resource);
189 			const std::set<int> keys = tbl->keys<int>();
190 			for (int upper_limit : keys) {
191 				resis[upper_limit] = tribes_.safe_immovable_index(tbl->get_string(upper_limit));
192 			}
193 			if (resis.empty()) {
194 				throw GameDataError("Tribe has no indicators for resource %s.", resource.c_str());
195 			}
196 			resource_indicators_[resource] = resis;
197 		}
198 
199 		ship_names_ = table.get_table("ship_names")->array_entries<std::string>();
200 
201 		for (const std::string& buildingname :
202 		     table.get_table("buildings")->array_entries<std::string>()) {
203 			add_building(buildingname);
204 		}
205 
206 		// Special types
207 		builder_ = add_special_worker(table.get_string("builder"));
208 		carrier_ = add_special_worker(table.get_string("carrier"));
209 		carrier2_ = add_special_worker(table.get_string("carrier2"));
210 		geologist_ = add_special_worker(table.get_string("geologist"));
211 		soldier_ = add_special_worker(table.get_string("soldier"));
212 		ferry_ = add_special_worker(table.get_string("ferry"));
213 
214 		const std::string shipname = table.get_string("ship");
215 		try {
216 			ship_ = tribes_.safe_ship_index(shipname);
217 		} catch (const WException& e) {
218 			throw GameDataError("Failed adding ship '%s': %s", shipname.c_str(), e.what());
219 		}
220 
221 		port_ = add_special_building(table.get_string("port"));
222 
223 		if (table.has_key<std::string>("toolbar")) {
224 			toolbar_image_set_.reset(new ToolbarImageset(*table.get_table("toolbar")));
225 		}
226 	} catch (const GameDataError& e) {
227 		throw GameDataError("tribe %s: %s", name_.c_str(), e.what());
228 	}
229 }
230 
231 /**
232  * Access functions
233  */
234 
name() const235 const std::string& TribeDescr::name() const {
236 	return name_;
237 }
descname() const238 const std::string& TribeDescr::descname() const {
239 	return descname_;
240 }
241 
get_nrwares() const242 size_t TribeDescr::get_nrwares() const {
243 	return wares_.size();
244 }
get_nrworkers() const245 size_t TribeDescr::get_nrworkers() const {
246 	return workers_.size();
247 }
248 
buildings() const249 const std::vector<DescriptionIndex>& TribeDescr::buildings() const {
250 	return buildings_;
251 }
wares() const252 const std::set<DescriptionIndex>& TribeDescr::wares() const {
253 	return wares_;
254 }
workers() const255 const std::set<DescriptionIndex>& TribeDescr::workers() const {
256 	return workers_;
257 }
immovables() const258 const std::set<DescriptionIndex>& TribeDescr::immovables() const {
259 	return immovables_;
260 }
resource_indicators() const261 const ResourceIndicatorSet& TribeDescr::resource_indicators() const {
262 	return resource_indicators_;
263 }
264 
has_building(const DescriptionIndex & index) const265 bool TribeDescr::has_building(const DescriptionIndex& index) const {
266 	return std::find(buildings_.begin(), buildings_.end(), index) != buildings_.end();
267 }
has_ware(const DescriptionIndex & index) const268 bool TribeDescr::has_ware(const DescriptionIndex& index) const {
269 	return wares_.count(index) == 1;
270 }
has_worker(const DescriptionIndex & index) const271 bool TribeDescr::has_worker(const DescriptionIndex& index) const {
272 	return workers_.count(index) == 1;
273 }
has_immovable(const DescriptionIndex & index) const274 bool TribeDescr::has_immovable(const DescriptionIndex& index) const {
275 	return immovables_.count(index) == 1;
276 }
is_construction_material(const DescriptionIndex & index) const277 bool TribeDescr::is_construction_material(const DescriptionIndex& index) const {
278 	return construction_materials_.count(index) == 1;
279 }
280 
building_index(const std::string & buildingname) const281 DescriptionIndex TribeDescr::building_index(const std::string& buildingname) const {
282 	return tribes_.building_index(buildingname);
283 }
284 
immovable_index(const std::string & immovablename) const285 DescriptionIndex TribeDescr::immovable_index(const std::string& immovablename) const {
286 	return tribes_.immovable_index(immovablename);
287 }
ware_index(const std::string & warename) const288 DescriptionIndex TribeDescr::ware_index(const std::string& warename) const {
289 	return tribes_.ware_index(warename);
290 }
worker_index(const std::string & workername) const291 DescriptionIndex TribeDescr::worker_index(const std::string& workername) const {
292 	return tribes_.worker_index(workername);
293 }
294 
safe_building_index(const std::string & buildingname) const295 DescriptionIndex TribeDescr::safe_building_index(const std::string& buildingname) const {
296 	return tribes_.safe_building_index(buildingname);
297 }
298 
safe_ware_index(const std::string & warename) const299 DescriptionIndex TribeDescr::safe_ware_index(const std::string& warename) const {
300 	return tribes_.safe_ware_index(warename);
301 }
safe_worker_index(const std::string & workername) const302 DescriptionIndex TribeDescr::safe_worker_index(const std::string& workername) const {
303 	return tribes_.safe_worker_index(workername);
304 }
305 
get_ware_descr(const DescriptionIndex & index) const306 WareDescr const* TribeDescr::get_ware_descr(const DescriptionIndex& index) const {
307 	return tribes_.get_ware_descr(index);
308 }
get_worker_descr(const DescriptionIndex & index) const309 WorkerDescr const* TribeDescr::get_worker_descr(const DescriptionIndex& index) const {
310 	return tribes_.get_worker_descr(index);
311 }
312 
get_building_descr(const DescriptionIndex & index) const313 BuildingDescr const* TribeDescr::get_building_descr(const DescriptionIndex& index) const {
314 	return tribes_.get_building_descr(index);
315 }
get_immovable_descr(const DescriptionIndex & index) const316 ImmovableDescr const* TribeDescr::get_immovable_descr(const DescriptionIndex& index) const {
317 	return tribes_.get_immovable_descr(index);
318 }
319 
builder() const320 DescriptionIndex TribeDescr::builder() const {
321 	assert(tribes_.worker_exists(builder_));
322 	return builder_;
323 }
carrier() const324 DescriptionIndex TribeDescr::carrier() const {
325 	assert(tribes_.worker_exists(carrier_));
326 	return carrier_;
327 }
carrier2() const328 DescriptionIndex TribeDescr::carrier2() const {
329 	assert(tribes_.worker_exists(carrier2_));
330 	return carrier2_;
331 }
geologist() const332 DescriptionIndex TribeDescr::geologist() const {
333 	assert(tribes_.worker_exists(geologist_));
334 	return geologist_;
335 }
soldier() const336 DescriptionIndex TribeDescr::soldier() const {
337 	assert(tribes_.worker_exists(soldier_));
338 	return soldier_;
339 }
ship() const340 DescriptionIndex TribeDescr::ship() const {
341 	assert(tribes_.ship_exists(ship_));
342 	return ship_;
343 }
port() const344 DescriptionIndex TribeDescr::port() const {
345 	assert(tribes_.building_exists(port_));
346 	return port_;
347 }
ferry() const348 DescriptionIndex TribeDescr::ferry() const {
349 	assert(tribes_.worker_exists(ferry_));
350 	return ferry_;
351 }
352 
trainingsites() const353 const std::vector<DescriptionIndex>& TribeDescr::trainingsites() const {
354 	return trainingsites_;
355 }
worker_types_without_cost() const356 const std::vector<DescriptionIndex>& TribeDescr::worker_types_without_cost() const {
357 	return worker_types_without_cost_;
358 }
359 
frontier_animation() const360 uint32_t TribeDescr::frontier_animation() const {
361 	return frontier_animation_id_;
362 }
363 
flag_animation() const364 uint32_t TribeDescr::flag_animation() const {
365 	return flag_animation_id_;
366 }
367 
bridge_animation(uint8_t dir,bool busy) const368 uint32_t TribeDescr::bridge_animation(uint8_t dir, bool busy) const {
369 	switch (dir) {
370 	case WALK_E:
371 		return (busy ? bridges_busy_ : bridges_normal_).e;
372 	case WALK_SE:
373 		return (busy ? bridges_busy_ : bridges_normal_).se;
374 	case WALK_SW:
375 		return (busy ? bridges_busy_ : bridges_normal_).sw;
376 	default:
377 		NEVER_HERE();
378 	}
379 }
380 
bridge_height() const381 uint32_t TribeDescr::bridge_height() const {
382 	return bridge_height_;
383 }
384 
normal_road_paths() const385 const std::vector<std::string>& TribeDescr::normal_road_paths() const {
386 	return normal_road_paths_;
387 }
388 
busy_road_paths() const389 const std::vector<std::string>& TribeDescr::busy_road_paths() const {
390 	return busy_road_paths_;
391 }
392 
waterway_paths() const393 const std::vector<std::string>& TribeDescr::waterway_paths() const {
394 	return waterway_paths_;
395 }
396 
add_normal_road_texture(const Image * texture)397 void TribeDescr::add_normal_road_texture(const Image* texture) {
398 	road_textures_.add_normal_road_texture(texture);
399 }
400 
add_busy_road_texture(const Image * texture)401 void TribeDescr::add_busy_road_texture(const Image* texture) {
402 	road_textures_.add_busy_road_texture(texture);
403 }
404 
add_waterway_texture(const Image * texture)405 void TribeDescr::add_waterway_texture(const Image* texture) {
406 	road_textures_.add_waterway_texture(texture);
407 }
408 
road_textures() const409 const RoadTextures& TribeDescr::road_textures() const {
410 	return road_textures_;
411 }
412 
413 /*
414 ==============
415 Find the best matching indicator for the given amount.
416 ==============
417 */
get_resource_indicator(ResourceDescription const * const res,const ResourceAmount amount) const418 DescriptionIndex TribeDescr::get_resource_indicator(ResourceDescription const* const res,
419                                                     const ResourceAmount amount) const {
420 	if (!res || !amount) {
421 		auto list = resource_indicators_.find("");
422 		if (list == resource_indicators_.end() || list->second.empty()) {
423 			throw GameDataError("Tribe '%s' has no indicator for no resources!", name_.c_str());
424 		}
425 		return list->second.begin()->second;
426 	}
427 
428 	auto list = resource_indicators_.find(res->name());
429 	if (list == resource_indicators_.end() || list->second.empty()) {
430 		throw GameDataError(
431 		   "Tribe '%s' has no indicators for resource '%s'!", name_.c_str(), res->name().c_str());
432 	}
433 
434 	uint32_t lowest = 0;
435 	for (const auto& resi : list->second) {
436 		if (resi.first < amount) {
437 			continue;
438 		} else if (lowest < amount || resi.first < lowest) {
439 			lowest = resi.first;
440 		}
441 	}
442 
443 	if (lowest < amount) {
444 		throw GameDataError("Tribe '%s' has no indicators for amount %i of resource '%s' (highest "
445 		                    "possible amount is %i)!",
446 		                    name_.c_str(), amount, res->name().c_str(), lowest);
447 	}
448 
449 	return list->second.find(lowest)->second;
450 }
451 
add_building(const std::string & buildingname)452 void TribeDescr::add_building(const std::string& buildingname) {
453 	try {
454 		DescriptionIndex index = tribes_.safe_building_index(buildingname);
455 		if (has_building(index)) {
456 			throw GameDataError("Duplicate definition of building '%s'", buildingname.c_str());
457 		}
458 		buildings_.push_back(index);
459 
460 		// Register trainigsites
461 		if (get_building_descr(index)->type() == MapObjectType::TRAININGSITE) {
462 			trainingsites_.push_back(index);
463 		}
464 
465 		// Register construction materials
466 		for (const auto& build_cost : get_building_descr(index)->buildcost()) {
467 			if (!is_construction_material(build_cost.first)) {
468 				construction_materials_.insert(build_cost.first);
469 			}
470 		}
471 		for (const auto& enhancement_cost : get_building_descr(index)->enhancement_cost()) {
472 			if (!is_construction_material(enhancement_cost.first)) {
473 				construction_materials_.insert(enhancement_cost.first);
474 			}
475 		}
476 	} catch (const WException& e) {
477 		throw GameDataError("Failed adding building '%s': %s", buildingname.c_str(), e.what());
478 	}
479 }
480 
add_worker(const std::string & workername,std::vector<DescriptionIndex> & workers_order_column)481 void TribeDescr::add_worker(const std::string& workername,
482                             std::vector<DescriptionIndex>& workers_order_column) {
483 	try {
484 		DescriptionIndex workerindex = tribes_.safe_worker_index(workername);
485 		if (has_worker(workerindex)) {
486 			throw GameDataError("Duplicate definition of worker '%s'", workername.c_str());
487 		}
488 		workers_.insert(workerindex);
489 		workers_order_column.push_back(workerindex);
490 
491 		const WorkerDescr& worker_descr = *tribes_.get_worker_descr(workerindex);
492 		if (worker_descr.is_buildable() && worker_descr.buildcost().empty()) {
493 			worker_types_without_cost_.push_back(workerindex);
494 		}
495 	} catch (const WException& e) {
496 		throw GameDataError("Failed adding worker '%s: %s", workername.c_str(), e.what());
497 	}
498 }
499 
add_worker(const std::string & workername)500 void TribeDescr::add_worker(const std::string& workername) {
501 	add_worker(workername, workers_order_.back());
502 }
503 
toolbar_image_set() const504 ToolbarImageset* TribeDescr::toolbar_image_set() const {
505 	return toolbar_image_set_.get();
506 }
507 
508 /**
509  * Helper functions
510  */
511 
add_special_worker(const std::string & workername)512 DescriptionIndex TribeDescr::add_special_worker(const std::string& workername) {
513 	try {
514 		DescriptionIndex worker = tribes_.safe_worker_index(workername);
515 		if (!has_worker(worker)) {
516 			throw GameDataError("This tribe doesn't have the worker '%s'", workername.c_str());
517 		}
518 		return worker;
519 	} catch (const WException& e) {
520 		throw GameDataError("Failed adding special worker '%s': %s", workername.c_str(), e.what());
521 	}
522 }
523 
add_special_building(const std::string & buildingname)524 DescriptionIndex TribeDescr::add_special_building(const std::string& buildingname) {
525 	try {
526 		DescriptionIndex building = tribes_.safe_building_index(buildingname);
527 		if (!has_building(building)) {
528 			throw GameDataError("This tribe doesn't have the building '%s'", buildingname.c_str());
529 		}
530 		return building;
531 	} catch (const WException& e) {
532 		throw GameDataError(
533 		   "Failed adding special building '%s': %s", buildingname.c_str(), e.what());
534 	}
535 }
add_special_ware(const std::string & warename)536 DescriptionIndex TribeDescr::add_special_ware(const std::string& warename) {
537 	try {
538 		DescriptionIndex ware = tribes_.safe_ware_index(warename);
539 		if (!has_ware(ware)) {
540 			throw GameDataError("This tribe doesn't have the ware '%s'", warename.c_str());
541 		}
542 		return ware;
543 	} catch (const WException& e) {
544 		throw GameDataError("Failed adding special ware '%s': %s", warename.c_str(), e.what());
545 	}
546 }
547 }  // namespace Widelands
548