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/world/terrain_description.h"
21 
22 #include "base/i18n.h"
23 #include "graphic/animation/animation.h"
24 #include "graphic/graphic.h"
25 #include "graphic/texture.h"
26 #include "logic/game_data_error.h"
27 #include "logic/map_objects/world/editor_category.h"
28 #include "logic/map_objects/world/world.h"
29 #include "scripting/lua_table.h"
30 
31 namespace Widelands {
32 
33 namespace {
34 
35 // Parse a terrain type from the giving string.
terrain_type_from_string(const std::string & type)36 TerrainDescription::Is terrain_type_from_string(const std::string& type) {
37 	if (type == "arable") {
38 		return TerrainDescription::Is::kArable;
39 	}
40 	if (type == "walkable") {
41 		return TerrainDescription::Is::kWalkable;
42 	}
43 	if (type == "water") {
44 		return static_cast<TerrainDescription::Is>(TerrainDescription::Is::kWater |
45 		                                           TerrainDescription::Is::kUnwalkable);
46 	}
47 	if (type == "unreachable") {
48 		return static_cast<TerrainDescription::Is>(TerrainDescription::Is::kUnreachable |
49 		                                           TerrainDescription::Is::kUnwalkable);
50 	}
51 	if (type == "mineable") {
52 		return TerrainDescription::Is::kMineable;
53 	}
54 	if (type == "unwalkable") {
55 		return TerrainDescription::Is::kUnwalkable;
56 	}
57 	throw LuaError((boost::format("Invalid terrain \"is\" value '%s'") % type).str());
58 }
59 
60 }  // namespace
61 
Type(TerrainDescription::Is init_is)62 TerrainDescription::Type::Type(TerrainDescription::Is init_is) : is(init_is) {
63 	switch (is) {
64 	case Is::kArable:
65 		/** TRANSLATORS: This is a terrain type tooltip in the editor */
66 		descname = _("arable");
67 		icon = g_gr->images().get("images/wui/editor/terrain_arable.png");
68 		break;
69 	case Is::kWalkable:
70 		/** TRANSLATORS: This is a terrain type tooltip in the editor */
71 		descname = _("walkable");
72 		icon = g_gr->images().get("images/wui/editor/terrain_walkable.png");
73 		break;
74 	case Is::kWater:
75 		/** TRANSLATORS: This is a terrain type tooltip in the editor */
76 		descname = _("navigable");
77 		icon = g_gr->images().get("images/wui/editor/terrain_water.png");
78 		break;
79 	case Is::kUnreachable:
80 		/** TRANSLATORS: This is a terrain type tooltip in the editor */
81 		descname = _("unreachable");
82 		icon = g_gr->images().get("images/wui/editor/terrain_unreachable.png");
83 		break;
84 	case Is::kMineable:
85 		/** TRANSLATORS: This is a terrain type tooltip in the editor */
86 		descname = _("mineable");
87 		icon = g_gr->images().get("images/wui/editor/terrain_mineable.png");
88 		break;
89 	case Is::kUnwalkable:
90 		/** TRANSLATORS: This is a terrain type tooltip in the editor */
91 		descname = _("unwalkable");
92 		icon = g_gr->images().get("images/wui/editor/terrain_unwalkable.png");
93 		break;
94 	}
95 }
96 
TerrainDescription(const LuaTable & table,const Widelands::World & world)97 TerrainDescription::TerrainDescription(const LuaTable& table, const Widelands::World& world)
98    : name_(table.get_string("name")),
99      descname_(table.get_string("descname")),
100      is_(terrain_type_from_string(table.get_string("is"))),
101      default_resource_index_(world.resource_index(table.get_string("default_resource").c_str())),
102      default_resource_amount_(table.get_int("default_resource_amount")),
103      dither_layer_(table.get_int("dither_layer")),
104      temperature_(table.get_int("temperature")),
105      fertility_(table.get_int("fertility")),
106      humidity_(table.get_int("humidity")) {
107 
108 	if (table.has_key("tooltips")) {
109 		custom_tooltips_ = table.get_table("tooltips")->array_entries<std::string>();
110 	}
111 
112 	if (table.has_key("enhancement")) {
113 		enhancement_ = table.get_string("enhancement");
114 		if (enhancement_ == name_) {
115 			throw GameDataError("%s: a terrain cannot be enhanced to itself", name_.c_str());
116 		}
117 		// Other invalid terrains will be detected in World::postload
118 	} else {
119 		enhancement_ = "";
120 	}
121 
122 	if (!(0 < fertility_ && fertility_ < 1000)) {
123 		throw GameDataError("%s: fertility is not in (0, 1000).", name_.c_str());
124 	}
125 	if (!(0 < humidity_ && humidity_ < 1000)) {
126 		throw GameDataError("%s: humidity is not in (0, 1000).", name_.c_str());
127 	}
128 	if (temperature_ < 0) {
129 		throw GameDataError("%s: temperature is not possible.", name_.c_str());
130 	}
131 
132 	texture_paths_ = table.get_table("textures")->array_entries<std::string>();
133 	frame_length_ = kFrameLength;
134 	if (texture_paths_.empty()) {
135 		throw GameDataError("Terrain %s has no images.", name_.c_str());
136 	} else if (texture_paths_.size() == 1) {
137 		if (table.has_key("fps")) {
138 			throw GameDataError("Terrain %s with one images must not have fps.", name_.c_str());
139 		}
140 	} else {
141 		frame_length_ = 1000 / get_positive_int(table, "fps");
142 	}
143 
144 	for (const std::string& resource :
145 	     table.get_table("valid_resources")->array_entries<std::string>()) {
146 		valid_resources_.push_back(world.safe_resource_index(resource.c_str()));
147 	}
148 
149 	if (default_resource_amount_ > 0 && !is_resource_valid(default_resource_index_)) {
150 		throw GameDataError("Default resource is not in valid resources.\n");
151 	}
152 
153 	const DescriptionIndex editor_category_index =
154 	   world.editor_terrain_categories().get_index(table.get_string("editor_category"));
155 	if (editor_category_index == Widelands::INVALID_INDEX) {
156 		throw GameDataError(
157 		   "Unknown editor_category: %s\n", table.get_string("editor_category").c_str());
158 	}
159 	editor_category_ = world.editor_terrain_categories().get_mutable(editor_category_index);
160 }
161 
~TerrainDescription()162 TerrainDescription::~TerrainDescription() {
163 }
164 
get_texture(uint32_t gametime) const165 const Image& TerrainDescription::get_texture(uint32_t gametime) const {
166 	return *textures_.at((gametime / frame_length_) % textures_.size());
167 }
168 
add_texture(const Image * texture)169 void TerrainDescription::add_texture(const Image* texture) {
170 	if (texture->width() != kTextureSideLength || texture->height() != kTextureSideLength) {
171 		throw wexception("Tried to add a texture with wrong size.");
172 	}
173 	textures_.emplace_back(texture);
174 }
175 
texture_paths() const176 const std::vector<std::string>& TerrainDescription::texture_paths() const {
177 	return texture_paths_;
178 }
179 
get_is() const180 TerrainDescription::Is TerrainDescription::get_is() const {
181 	return is_;
182 }
183 
get_types() const184 const std::vector<TerrainDescription::Type> TerrainDescription::get_types() const {
185 	std::vector<TerrainDescription::Type> terrain_types;
186 
187 	if (is_ == Widelands::TerrainDescription::Is::kArable) {
188 		terrain_types.push_back(TerrainDescription::Type(TerrainDescription::Is::kArable));
189 	}
190 	if (is_ & Widelands::TerrainDescription::Is::kWalkable) {
191 		terrain_types.push_back(TerrainDescription::Type(TerrainDescription::Is::kWalkable));
192 	}
193 	if (is_ & Widelands::TerrainDescription::Is::kWater) {
194 		terrain_types.push_back(TerrainDescription::Type(TerrainDescription::Is::kWater));
195 	}
196 	if (is_ & Widelands::TerrainDescription::Is::kUnreachable) {
197 		terrain_types.push_back(TerrainDescription::Type(TerrainDescription::Is::kUnreachable));
198 	}
199 	if (is_ & Widelands::TerrainDescription::Is::kMineable) {
200 		terrain_types.push_back(TerrainDescription::Type(TerrainDescription::Is::kMineable));
201 	}
202 	if (is_ & Widelands::TerrainDescription::Is::kUnwalkable) {
203 		terrain_types.push_back(TerrainDescription::Type(TerrainDescription::Is::kUnwalkable));
204 	}
205 	return terrain_types;
206 }
207 
name() const208 const std::string& TerrainDescription::name() const {
209 	return name_;
210 }
211 
descname() const212 const std::string& TerrainDescription::descname() const {
213 	return descname_;
214 }
215 
editor_category() const216 const EditorCategory* TerrainDescription::editor_category() const {
217 	return editor_category_;
218 }
219 
get_valid_resource(DescriptionIndex index) const220 DescriptionIndex TerrainDescription::get_valid_resource(DescriptionIndex index) const {
221 	return valid_resources_[index];
222 }
223 
get_num_valid_resources() const224 size_t TerrainDescription::get_num_valid_resources() const {
225 	return valid_resources_.size();
226 }
227 
valid_resources() const228 std::vector<DescriptionIndex> TerrainDescription::valid_resources() const {
229 	return valid_resources_;
230 }
231 
is_resource_valid(const DescriptionIndex res) const232 bool TerrainDescription::is_resource_valid(const DescriptionIndex res) const {
233 	for (const DescriptionIndex resource_index : valid_resources_) {
234 		if (resource_index == res) {
235 			return true;
236 		}
237 	}
238 	return false;
239 }
240 
get_default_resource() const241 DescriptionIndex TerrainDescription::get_default_resource() const {
242 	return default_resource_index_;
243 }
244 
get_default_resource_amount() const245 ResourceAmount TerrainDescription::get_default_resource_amount() const {
246 	return default_resource_amount_;
247 }
248 
dither_layer() const249 int TerrainDescription::dither_layer() const {
250 	return dither_layer_;
251 }
252 
temperature() const253 int TerrainDescription::temperature() const {
254 	return temperature_;
255 }
256 
humidity() const257 int TerrainDescription::humidity() const {
258 	return humidity_;
259 }
260 
fertility() const261 int TerrainDescription::fertility() const {
262 	return fertility_;
263 }
264 
enhancement() const265 const std::string& TerrainDescription::enhancement() const {
266 	return enhancement_;
267 }
268 
set_minimap_color(const RGBColor & color)269 void TerrainDescription::set_minimap_color(const RGBColor& color) {
270 	for (int i = -128; i < 128; i++) {
271 		const int shade = 128 + i;
272 		int new_r = std::min<int>((color.r * shade) >> 7, 255);
273 		int new_g = std::min<int>((color.g * shade) >> 7, 255);
274 		int new_b = std::min<int>((color.b * shade) >> 7, 255);
275 		minimap_colors_[shade] = RGBColor(new_r, new_g, new_b);
276 	}
277 }
278 
get_minimap_color(int shade)279 const RGBColor& TerrainDescription::get_minimap_color(int shade) {
280 	assert(-128 <= shade && shade <= 127);
281 	return minimap_colors_[128 + shade];
282 }
283 
284 }  // namespace Widelands
285