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