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