← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/editor_help into lp:widelands

 

GunChleoc has proposed merging lp:~widelands-dev/widelands/editor_help into lp:widelands.

Commit message:
Exposed terrain and resource descriptions to the Lua interface and added tree and terrain help to the editor.

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1204482 in widelands: "Improve the help system for the editor"
  https://bugs.launchpad.net/widelands/+bug/1204482

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/editor_help/+merge/283900

Added tree and terrain help to the editor.

The design is discussed in the forum:

https://wl.widelands.org/forum/topic/1870/

We are also thinking of making terrains on the map clickable to popup a smaller help window, or maybe adding it to the info tool, but that can be done in a separate branch.
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/editor_help into lp:widelands.
=== added directory 'scripting/editor'
=== added file 'scripting/editor/format_editor.lua'
--- scripting/editor/format_editor.lua	1970-01-01 00:00:00 +0000
+++ scripting/editor/format_editor.lua	2016-01-26 07:30:52 +0000
@@ -0,0 +1,27 @@
+include "scripting/formatting.lua"
+
+function picture_li(imagepath, text)
+   return "<rt image=" .. imagepath .. " image-align=left>" .. p(text) .. "</rt>"
+end
+
+function spacer()
+   return rt(p("font-size=3", ""))
+end
+
+-- RST
+-- .. function text_line(t1, t2[, imgstr = nil])
+--
+--    Creates a line of h3 formatted text followed by normal text and an image.
+--
+--    :arg t1: text in h3 format.
+--    :arg t2: text in p format.
+--    :arg imgstr: image aligned right.
+--    :returns: header followed by normal text and image.
+--
+function text_line(t1, t2, imgstr)
+   if imgstr then
+      return "<rt text-align=left image=" .. imgstr .. " image-align=right><p font-size=13 font-color=D1D1D1>" ..  t1 .. "</p><p line-spacing=3 font-size=12>" .. t2 .. "<br></p><p font-size=8> <br></p></rt>"
+   else
+      return "<rt text-align=left><p font-size=13 font-color=D1D1D1>" ..  t1 .. "</p><p line-spacing=3 font-size=12>" .. t2 .. "<br></p><p font-size=8> <br></p></rt>"
+   end
+end

=== added file 'scripting/editor/terrain_help.lua'
--- scripting/editor/terrain_help.lua	1970-01-01 00:00:00 +0000
+++ scripting/editor/terrain_help.lua	2016-01-26 07:30:52 +0000
@@ -0,0 +1,66 @@
+include "scripting/editor/format_editor.lua"
+
+return {
+   func = function(terrain_name)
+      set_textdomain("widelands")
+      local world = wl.World();
+      local terrain = wl.Editor():get_terrain_description(terrain_name)
+
+		local result = picture_li(terrain.representative_image, "")
+
+		-- Resources
+		local valid_resources = terrain.valid_resources_names
+		if (#valid_resources > 0) then
+			result = result .. spacer() .. rt(h2(_"Resources"))
+			if (#valid_resources > 0) then
+				-- TRANSLATORS: A header in the editor help
+				result = result .. rt(h3(ngettext("Valid Resource:", "Valid Resources:", #valid_resources)))
+				for count, resourcename in pairs(valid_resources) do
+					local valid_resource = wl.Editor():get_resource_description(resourcename)
+					result = result .. picture_li(valid_resource.representative_image, valid_resource.descname)
+				end
+			end
+
+			local default_resource_name = terrain.default_resource_name
+			if (default_resource_name ~= nil) then
+				local default_resource = wl.Editor():get_resource_description(default_resource_name)
+				-- TRANSLATORS: e.g. "5x Water"
+				result = result .. text_line(_"Default:", _"%1%x %2%":bformat(terrain.default_resource_amount, default_resource.descname), default_resource:editor_image(terrain.default_resource_amount))
+			end
+		end
+
+		-- Trees
+      local tree_list = {}
+      for i, tree_name in ipairs(world:immovable_descriptions("tree")) do
+			local probability = terrain:probability_to_grow(tree_name)
+			if (probability > 0.01) then
+				-- sort the trees by percentage
+				i = 1
+				while (tree_list[i] and (tree_list[i].probability_ > probability)) do
+					i = i + 1
+				end
+
+				for j = #tree_list, i, -1 do
+					tree_list[j+1] = tree_list[j]
+				end
+				tree_list[i] = {tree_name_ = tree_name, probability_ = probability}
+			end
+		end
+
+		local tree_string = ""
+		for k,v in ipairs(tree_list) do
+			local tree = wl.Editor():get_immovable_description(v.tree_name_)
+			tree_string = tree_string .. picture_li(tree.representative_image, tree.basename .. ("<br>%2.1f%%"):bformat(100 * v.probability_)) .. spacer()
+		end
+
+		-- TRANSLATORS: A header in the editor help
+		result = result .. spacer() .. rt(h2(_"Probability of trees growing")) .. spacer()
+
+		if (tree_string ~="") then
+			result = result .. tree_string
+		else
+			result = result .. rt(p(_"No trees will grow here."))
+		end
+      return result
+   end
+}

=== added file 'scripting/editor/tree_help.lua'
--- scripting/editor/tree_help.lua	1970-01-01 00:00:00 +0000
+++ scripting/editor/tree_help.lua	2016-01-26 07:30:52 +0000
@@ -0,0 +1,38 @@
+include "scripting/editor/format_editor.lua"
+
+return {
+   func = function(tree_name)
+      set_textdomain("widelands")
+      local world = wl.World();
+      local tree = wl.Editor():get_immovable_description(tree_name)
+      local result = picture_li(tree.representative_image, "")
+
+      -- TRANSLATORS: A header in the editor help. Terrains preferred by a type of tree.
+      result = result .. rt(p("font-size=3", "")) .. rt(h2(_"Preferred terrains")) .. spacer()
+		terrain_list = {}
+      for i, terrain_name in ipairs(world:terrain_descriptions()) do
+			local terrain = wl.Editor():get_terrain_description(terrain_name)
+			local probability = terrain:probability_to_grow(tree_name)
+			if (probability > 0.01) then
+				-- sort the terrains by percentage
+				i = 1
+				while (terrain_list[i] and (terrain_list[i].probability_ > probability)) do
+					i = i + 1
+				end
+
+				for j = #terrain_list, i, -1 do
+					terrain_list[j+1] = terrain_list[j]
+				end
+				terrain_list[i] = {terrain_name_ = terrain_name, probability_ = probability}
+			end
+		end
+
+		for k,v in ipairs(terrain_list) do
+			local terrain = wl.Editor():get_terrain_description(v.terrain_name_)
+			-- TRANSLATORS: Terrain name (Climate)
+			result = result .. picture_li(terrain.representative_image, (_"%1% (%2%)"):bformat(terrain.descname, terrain.editor_category.descname) .. "<br>" .. ("%2.1f%%"):bformat(100 * v.probability_)) .. spacer()
+		end
+
+      return result
+   end
+}

=== modified file 'src/editor/CMakeLists.txt'
--- src/editor/CMakeLists.txt	2016-01-14 22:17:50 +0000
+++ src/editor/CMakeLists.txt	2016-01-26 07:30:52 +0000
@@ -47,6 +47,8 @@
     tools/editor_tool_action.h
     tools/multi_select.h
     ui_menus/categorized_item_selection_menu.h
+    ui_menus/editor_help.cc
+    ui_menus/editor_help.h
     ui_menus/editor_main_menu.cc
     ui_menus/editor_main_menu.h
     ui_menus/editor_main_menu_load_map.cc

=== modified file 'src/editor/editorinteractive.cc'
--- src/editor/editorinteractive.cc	2016-01-16 15:57:31 +0000
+++ src/editor/editorinteractive.cc	2016-01-26 07:30:52 +0000
@@ -30,6 +30,7 @@
 #include "base/scoped_timer.h"
 #include "base/warning.h"
 #include "editor/tools/editor_delete_immovable_tool.h"
+#include "editor/ui_menus/editor_help.h"
 #include "editor/ui_menus/editor_main_menu.h"
 #include "editor/ui_menus/editor_main_menu_load_map.h"
 #include "editor/ui_menus/editor_main_menu_save_map.h"
@@ -69,7 +70,7 @@
 	//  Ok, we're doing something. First remove the current overlays.
 	if (note.old_resource != Widelands::kNoResource) {
 		const std::string str =
-		   world.get_resource(note.old_resource)->get_editor_pic(note.old_amount);
+		   world.get_resource(note.old_resource)->editor_image(note.old_amount);
 		const Image* pic = g_gr->images().get(str);
 		field_overlay_manager->remove_overlay(note.fc, pic);
 	}
@@ -78,7 +79,7 @@
 	const auto resource_type = note.fc.field->get_resources();
 	if (amount > 0 && resource_type != Widelands::kNoResource) {
 		const std::string str =
-		   world.get_resource(note.fc.field->get_resources())->get_editor_pic(amount);
+		   world.get_resource(note.fc.field->get_resources())->editor_image(amount);
 		const Image* pic = g_gr->images().get(str);
 		field_overlay_manager->register_overlay(note.fc, pic, 0);
 	}
@@ -123,7 +124,10 @@
 	 ("editor_undo", "undo", _("Undo"))),
 	redo_
 	(INIT_BUTTON
-	 ("editor_redo", "redo", _("Redo")))
+	 ("editor_redo", "redo", _("Redo"))),
+	toggle_help_
+	(INIT_BUTTON
+	 ("menu_help", "help", _("Help")))
 {
 	toggle_main_menu_.sigclicked.connect(boost::bind(&EditorInteractive::toggle_mainmenu, this));
 	toggle_tool_menu_.sigclicked.connect(boost::bind(&EditorInteractive::tool_menu_btn, this));
@@ -133,6 +137,7 @@
 	toggle_player_menu_.sigclicked.connect(boost::bind(&EditorInteractive::toggle_playermenu, this));
 	undo_.sigclicked.connect([this] {history_->undo_action(egbase().world());});
 	redo_.sigclicked.connect([this] {history_->redo_action(egbase().world());});
+	toggle_help_.sigclicked.connect(boost::bind(&EditorInteractive::toggle_help, this));
 
 	toolbar_.set_layout_toplevel(true);
 	toolbar_.add(&toggle_main_menu_,       UI::Box::AlignLeft);
@@ -143,6 +148,7 @@
 	toolbar_.add(&toggle_player_menu_,     UI::Box::AlignLeft);
 	toolbar_.add(&undo_,                   UI::Box::AlignLeft);
 	toolbar_.add(&redo_,                   UI::Box::AlignLeft);
+	toolbar_.add(&toggle_help_,            UI::Box::AlignLeft);
 	adjust_toolbar_position();
 
 #ifndef NDEBUG
@@ -184,7 +190,7 @@
 	iterate_Map_FCoords(map, extent, fc) {
 		if (uint8_t const amount = fc.field->get_resources_amount()) {
 			const std::string& immname =
-			   egbase().world().get_resource(fc.field->get_resources())->get_editor_pic(amount);
+			   egbase().world().get_resource(fc.field->get_resources())->editor_image(amount);
 			if (immname.size())
 				mutable_field_overlay_manager()->register_overlay(fc, g_gr->images().get(immname), 4);
 		}
@@ -349,6 +355,14 @@
 	}
 }
 
+void EditorInteractive::toggle_help() {
+	if (helpmenu_.window)
+		delete helpmenu_.window;
+	else
+		new EditorHelp(*this, helpmenu_);
+}
+
+
 
 bool EditorInteractive::handle_key(bool const down, SDL_Keysym const code) {
 	bool handled = InteractiveBase::handle_key(down, code);
@@ -476,6 +490,12 @@
 				history_->redo_action(egbase().world());
 			handled = true;
 			break;
+
+		case SDLK_F1:
+			toggle_help();
+			handled = true;
+			break;
+
 		default:
 			break;
 		}

=== modified file 'src/editor/editorinteractive.h'
--- src/editor/editorinteractive.h	2016-01-16 15:57:31 +0000
+++ src/editor/editorinteractive.h	2016-01-26 07:30:52 +0000
@@ -150,6 +150,7 @@
 	void toolsize_menu_btn();
 	void toggle_mainmenu();
 	void toggle_playermenu();
+	void toggle_help();
 
 	//  state variables
 	bool need_save_;
@@ -173,6 +174,7 @@
 	UI::UniqueWindow::Registry immovablemenu_;
 	UI::UniqueWindow::Registry bobmenu_;
 	UI::UniqueWindow::Registry resourcesmenu_;
+	UI::UniqueWindow::Registry helpmenu_;
 
 	UI::Button toggle_main_menu_;
 	UI::Button toggle_tool_menu_;
@@ -182,6 +184,7 @@
 	UI::Button toggle_player_menu_;
 	UI::Button undo_;
 	UI::Button redo_;
+	UI::Button toggle_help_;
 };
 
 #endif  // end of include guard: WL_EDITOR_EDITORINTERACTIVE_H

=== added file 'src/editor/ui_menus/editor_help.cc'
--- src/editor/ui_menus/editor_help.cc	1970-01-01 00:00:00 +0000
+++ src/editor/ui_menus/editor_help.cc	2016-01-26 07:30:52 +0000
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015-2016 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#include "editor/ui_menus/editor_help.h"
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <boost/format.hpp>
+
+#include "base/i18n.h"
+#include "editor/editorinteractive.h"
+#include "graphic/graphic.h"
+#include "graphic/texture.h"
+#include "io/filesystem/layered_filesystem.h"
+#include "logic/map_objects/world/editor_category.h"
+#include "logic/map_objects/world/terrain_description.h"
+#include "logic/map_objects/world/world.h"
+#include "scripting/lua_interface.h"
+#include "scripting/lua_table.h"
+
+#define WINDOW_WIDTH std::min(700, g_gr->get_xres() - 40)
+#define WINDOW_HEIGHT std::min(550, g_gr->get_yres() - 40)
+
+constexpr int kPadding = 5;
+constexpr int kTabHeight = 35;
+
+using namespace Widelands;
+
+inline EditorInteractive& EditorHelp::eia() const {
+	return dynamic_cast<EditorInteractive&>(*get_parent());
+}
+
+namespace {
+const std::string heading(const std::string& text) {
+	return ((boost::format("<rt><p font-size=18 font-weight=bold font-color=D1D1D1>"
+	                       "%s<br></p><p font-size=8> <br></p></rt>") %
+	         text).str());
+}
+}  // namespace
+
+EditorHelp::EditorHelp(EditorInteractive& parent, UI::UniqueWindow::Registry& registry)
+   : UI::UniqueWindow(&parent, "encyclopedia", &registry, WINDOW_WIDTH, WINDOW_HEIGHT, _("Help")),
+     tabs_(this, 0, 0, nullptr) {
+
+	const int contents_height = WINDOW_HEIGHT - kTabHeight - 2 * kPadding;
+	const int contents_width = WINDOW_WIDTH / 2 - 1.5 * kPadding;
+
+	std::vector<std::unique_ptr<HelpTab>> tab_definitions;
+
+	tab_definitions.push_back(
+	   std::unique_ptr<HelpTab>(new HelpTab("terrains",
+	                                        "pics/editor_menu_tool_set_terrain.png",
+	                                        _("Terrains"),
+	                                        "scripting/editor/terrain_help.lua",
+	                                        HelpEntry::Type::kTerrain)));
+
+	tab_definitions.push_back(
+	   std::unique_ptr<HelpTab>(new HelpTab("trees",
+	                                        "world/immovables/trees/alder/old/idle_0.png",
+	                                        _("Trees"),
+	                                        "scripting/editor/tree_help.lua",
+	                                        HelpEntry::Type::kTree)));
+
+	for (const auto& tab : tab_definitions) {
+		// Make sure that all paths exist
+		if (!g_fs->file_exists(tab->script_path)) {
+			throw wexception("Script path %s for tab %s does not exist!",
+			                 tab->script_path.c_str(),
+			                 tab->key.c_str());
+		}
+		if (!g_fs->file_exists(tab->image_filename)) {
+			throw wexception("Image path %s for tab %s does not exist!",
+			                 tab->image_filename.c_str(),
+			                 tab->key.c_str());
+		}
+
+		wrapper_boxes_.insert(std::make_pair(
+		   tab->key, std::unique_ptr<UI::Box>(new UI::Box(&tabs_, 0, 0, UI::Box::Horizontal))));
+
+		boxes_.insert(
+		   std::make_pair(tab->key,
+		                  std::unique_ptr<UI::Box>(new UI::Box(
+		                     wrapper_boxes_.at(tab->key).get(), 0, 0, UI::Box::Horizontal))));
+
+		lists_.insert(
+		   std::make_pair(tab->key,
+		                  std::unique_ptr<UI::Listselect<Widelands::DescriptionIndex>>(
+		                     new UI::Listselect<Widelands::DescriptionIndex>(
+		                        boxes_.at(tab->key).get(), 0, 0, contents_width, contents_height))));
+		lists_.at(tab->key)->selected.connect(
+		   boost::bind(&EditorHelp::entry_selected, this, tab->key, tab->script_path, tab->type));
+
+		contents_.insert(
+		   std::make_pair(tab->key,
+		                  std::unique_ptr<UI::MultilineTextarea>(new UI::MultilineTextarea(
+		                     boxes_.at(tab->key).get(), 0, 0, contents_width, contents_height))));
+
+		boxes_.at(tab->key)->add(lists_.at(tab->key).get(), UI::Align_Left);
+		boxes_.at(tab->key)->add_space(kPadding);
+		boxes_.at(tab->key)->add(contents_.at(tab->key).get(), UI::Align_Left);
+
+		wrapper_boxes_.at(tab->key)->add_space(kPadding);
+		wrapper_boxes_.at(tab->key)->add(boxes_.at(tab->key).get(), UI::Align_Left);
+
+		tabs_.add("editor_help_" + tab->key,
+		          g_gr->images().get(tab->image_filename),
+		          wrapper_boxes_.at(tab->key).get(),
+		          tab->tooltip);
+	}
+	tabs_.set_size(WINDOW_WIDTH, WINDOW_HEIGHT);
+
+	fill_terrains();
+	fill_trees();
+
+	if (get_usedefaultpos()) {
+		center_to_parent();
+	}
+}
+
+void EditorHelp::fill_entries(const char* key, std::vector<HelpEntry>& entries) {
+	std::sort(entries.begin(), entries.end());
+	for (uint32_t i = 0; i < entries.size(); i++) {
+		HelpEntry cur = entries[i];
+		lists_.at(key)->add(cur.descname, cur.index, cur.icon);
+	}
+	lists_.at(key)->select(0);
+}
+
+void EditorHelp::fill_terrains() {
+	const World& world = eia().egbase().world();
+	std::vector<HelpEntry> entries;
+
+	for (Widelands::DescriptionIndex i = 0; i < world.terrains().size(); ++i) {
+		const TerrainDescription& terrain = world.terrain_descr(i);
+		upcast(Image const, icon, &terrain.get_texture(0));
+		/** TRANSLATORS: Terrain name + editor category, e.g. Steppe (Summer) */
+		HelpEntry entry(i, (boost::format(_("%1% (%2%)"))
+								  % terrain.descname().c_str()
+								  % terrain.editor_category().descname()).str(), icon);
+		entries.push_back(entry);
+	}
+	fill_entries("terrains", entries);
+}
+
+void EditorHelp::fill_trees() {
+	const World& world = eia().egbase().world();
+	std::vector<HelpEntry> entries;
+
+	for (Widelands::DescriptionIndex i = 0; i < world.get_nr_immovables(); ++i) {
+		const ImmovableDescr* immovable = world.get_immovable_descr(i);
+		uint32_t attribute_id = immovable->get_attribute_id("tree");
+		if (immovable->has_attribute(attribute_id)) {
+			const Image* icon = immovable->representative_image();
+			HelpEntry entry(i, immovable->basename(), icon);
+			entries.push_back(entry);
+		}
+	}
+	fill_entries("trees", entries);
+}
+
+void EditorHelp::entry_selected(const std::string& key,
+                                const std::string& script_path,
+                                const HelpEntry::Type& type) {
+	try {
+		std::unique_ptr<LuaTable> table(eia().egbase().lua().run_script(script_path));
+		std::unique_ptr<LuaCoroutine> cr(table->get_coroutine("func"));
+
+		std::string descname = "";
+
+		switch (type) {
+		case (HelpEntry::Type::kTerrain): {
+			const TerrainDescription& descr =
+			   eia().egbase().world().terrain_descr(lists_.at(key)->get_selected());
+			/** TRANSLATORS: Terrain name + editor category, e.g. Steppe (Summer) */
+			descname = (boost::format(_("%1% (%2%)"))
+						  % descr.descname().c_str()
+						  % descr.editor_category().descname()).str();
+			cr->push_arg(descr.name());
+			break;
+		}
+		case (HelpEntry::Type::kTree): {
+			const ImmovableDescr* descr =
+			   eia().egbase().world().get_immovable_descr(lists_.at(key)->get_selected());
+			descname = descr->basename();
+			cr->push_arg(descr->name());
+			break;
+		}
+		default:
+			throw wexception("EditorHelp: No Type defined for tab.");
+		}
+
+		cr->resume();
+		const std::string help_text = cr->pop_string();
+		contents_.at(key)->set_text((boost::format("%s%s") % heading(descname) % help_text).str());
+
+	} catch (LuaError& err) {
+		contents_.at(key)->set_text(err.what());
+	}
+	contents_.at(key)->scroll_to_top();
+}

=== added file 'src/editor/ui_menus/editor_help.h'
--- src/editor/ui_menus/editor_help.h	1970-01-01 00:00:00 +0000
+++ src/editor/ui_menus/editor_help.h	2016-01-26 07:30:52 +0000
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015-2016 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifndef WL_EDITOR_UI_MENUS_EDITOR_HELP_H
+#define WL_EDITOR_UI_MENUS_EDITOR_HELP_H
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "logic/map_objects/map_object.h"
+#include "ui_basic/box.h"
+#include "ui_basic/listselect.h"
+#include "ui_basic/multilinetextarea.h"
+#include "ui_basic/table.h"
+#include "ui_basic/tabpanel.h"
+#include "ui_basic/unique_window.h"
+#include "ui_basic/window.h"
+
+class EditorInteractive;
+
+struct EditorHelp : public UI::UniqueWindow {
+	EditorHelp(EditorInteractive&, UI::UniqueWindow::Registry&);
+
+private:
+	struct HelpEntry {
+		enum class Type {
+			kTerrain,
+			kTree
+		};
+
+		HelpEntry(const HelpEntry& other) : HelpEntry(other.index, other.descname, other.icon) {
+		}
+		HelpEntry(const Widelands::DescriptionIndex _index,
+		          const std::string& _descname,
+		          const Image* _icon)
+		   : index(_index), descname(_descname), icon(_icon) {
+		}
+		Widelands::DescriptionIndex index;
+		std::string descname;
+		const Image* icon;
+
+		bool operator<(const HelpEntry other) const {
+			return descname < other.descname;
+		}
+	};
+
+	struct HelpTab {
+		HelpTab(const std::string& _key,
+		        const std::string& _image_filename,
+		        const std::string& _tooltip,
+		        const std::string& _script_path,
+		        const EditorHelp::HelpEntry::Type _type)
+		   : key(_key),
+		     image_filename(_image_filename),
+		     tooltip(_tooltip),
+		     script_path(_script_path),
+		     type(_type) {
+		}
+		const std::string key;
+		const std::string image_filename;
+		const std::string tooltip;
+		const std::string script_path;
+		const EditorHelp::HelpEntry::Type type;
+	};
+
+	EditorInteractive& eia() const;
+
+	// Fill table of contents
+	void fill_entries(const char* key, std::vector<HelpEntry>& entries);
+	void fill_terrains();
+	void fill_trees();
+
+	// Update contents when an entry is selected
+	void entry_selected(const std::string& key,
+	                    const std::string& script_path,
+	                    const HelpEntry::Type& type);
+
+	// UI elements
+	UI::TabPanel tabs_;
+
+	// Wrapper boxes so we can add some padding
+	std::map<std::string, std::unique_ptr<UI::Box>> wrapper_boxes_;
+	// Main contents boxes for each tab
+	std::map<std::string, std::unique_ptr<UI::Box>> boxes_;
+	// A tab's table of contents
+	std::map<std::string, std::unique_ptr<UI::Listselect<Widelands::DescriptionIndex>>> lists_;
+	// The contents shown when an entry is selected in a tab
+	std::map<std::string, std::unique_ptr<UI::MultilineTextarea>> contents_;
+};
+
+#endif  // end of include guard: WL_EDITOR_UI_MENUS_EDITOR_HELP_H

=== modified file 'src/editor/ui_menus/editor_tool_change_resources_options_menu.cc'
--- src/editor/ui_menus/editor_tool_change_resources_options_menu.cc	2016-01-18 19:35:25 +0000
+++ src/editor/ui_menus/editor_tool_change_resources_options_menu.cc	2016-01-26 07:30:52 +0000
@@ -139,7 +139,7 @@
 	//  Find the maximal width and height for the resource pictures.
 	int resource_pic_max_width = 0, resource_pic_max_height = 0;
 	for (Widelands::DescriptionIndex i = 0; i < nr_resources; ++i) {
-		const Image* pic = g_gr->images().get(world.get_resource(i)->get_editor_pic(100000));
+		const Image* pic = g_gr->images().get(world.get_resource(i)->representative_image());
 		resource_pic_max_width  = std::max(resource_pic_max_width,  pic->width());
 		resource_pic_max_height = std::max(resource_pic_max_height, pic->height());
 	}
@@ -170,7 +170,7 @@
 		m_radiogroup.add_button
 			(this,
 			 pos,
-			 g_gr->images().get(world.get_resource(i)->get_editor_pic(100000)));
+			 g_gr->images().get(world.get_resource(i)->representative_image()));
 	}
 	pos.y += resource_pic_max_height + vspacing();
 

=== modified file 'src/logic/field.h'
--- src/logic/field.h	2016-01-14 22:09:24 +0000
+++ src/logic/field.h	2016-01-26 07:30:52 +0000
@@ -134,7 +134,9 @@
 	uint16_t get_caps()     const {return caps;}
 
 	Terrains      get_terrains() const {return terrains;}
+	// The terrain on the downward triangle
 	DescriptionIndex terrain_d   () const {return terrains.d;}
+	// The terrain on the triangle to the right
 	DescriptionIndex terrain_r   () const {return terrains.r;}
 	void          set_terrains(const Terrains & i) {terrains = i;}
 	void set_terrain

=== modified file 'src/logic/map_objects/immovable.cc'
--- src/logic/map_objects/immovable.cc	2016-01-17 19:54:32 +0000
+++ src/logic/map_objects/immovable.cc	2016-01-26 07:30:52 +0000
@@ -189,7 +189,8 @@
 	MapObjectDescr(
 	MapObjectType::IMMOVABLE, table.get_string("name"), init_descname, table),
 	m_size(BaseImmovable::NONE),
-	owner_type_(input_type) {
+	owner_type_(input_type),
+	editor_category_(nullptr) {
 	if (!is_animation_known("idle")) {
 		throw GameDataError("Immovable %s has no idle animation", table.get_string("name").c_str());
 	}
@@ -203,8 +204,23 @@
 	}
 
 	if (table.has_key("attributes")) {
-		add_attributes(table.get_table("attributes")->
-							array_entries<std::string>(), {MapObject::Attribute::RESI});
+		std::vector<std::string> attributes = table.get_table("attributes")->array_entries<std::string>();
+		add_attributes(attributes, {MapObject::Attribute::RESI});
+
+		// Old trees get an extra basename so we can use it in help lists.
+		bool is_tree = false;
+		for (const std::string& attribute : attributes) {
+			if (attribute == "tree") {
+				is_tree = true;
+				break;
+			}
+		}
+		if (is_tree) {
+			if (!table.has_key("basename")) {
+				throw wexception("Immovable '%s' with type 'tree' must define a basename", name().c_str());
+			}
+			basename_ = table.get_string("basename");
+		}
 	}
 
 	std::unique_ptr<LuaTable> programs = table.get_table("programs");
@@ -247,6 +263,10 @@
 	}
 }
 
+bool ImmovableDescr::has_editor_category() const {
+	return editor_category_ != nullptr;
+}
+
 const EditorCategory& ImmovableDescr::editor_category() const {
 	return *editor_category_;
 }

=== modified file 'src/logic/map_objects/immovable.h'
--- src/logic/map_objects/immovable.h	2015-11-29 09:43:15 +0000
+++ src/logic/map_objects/immovable.h	2016-01-26 07:30:52 +0000
@@ -129,10 +129,15 @@
 
 	const Buildcost & buildcost() const {return m_buildcost;}
 
+	// Returns whether this immovable has an editor category. E.g. Tribe immovables never have one.
+	bool has_editor_category() const;
 
 	// Returns the editor category.
 	const EditorCategory& editor_category() const;
 
+	// A basic localized name for the immovable, used by trees
+	const std::string& basename() const {return basename_;}
+
 	// Every immovable that can 'grow' needs to have terrain affinity defined,
 	// all others do not. Returns true if this one has it defined.
 	bool has_terrain_affinity() const;
@@ -152,6 +157,8 @@
 	/// \see ActConstruction
 	Buildcost m_buildcost;
 
+	std::string basename_;
+
 private:
 	 // Common constructor functions for tribes and world.
 	ImmovableDescr(const std::string& init_descname, const LuaTable&, MapObjectDescr::OwnerType type);

=== modified file 'src/logic/map_objects/terrain_affinity.cc'
--- src/logic/map_objects/terrain_affinity.cc	2015-11-28 22:29:26 +0000
+++ src/logic/map_objects/terrain_affinity.cc	2016-01-26 07:30:52 +0000
@@ -37,6 +37,30 @@
 	return a * a;
 }
 
+// Helper function for probability_to_grow
+// Calculates the probability to grow for the given affinity and terrain values
+double calculate_probability_to_grow(const TerrainAffinity& affinity,
+												 double terrain_humidity,
+												 double terrain_fertility,
+												 double terrain_temperature) {
+
+	constexpr double kHumidityWeight = 0.500086642549548;
+	constexpr double kFertilityWeight = 0.5292268046607387;
+	constexpr double kTemperatureWeight = 61.31300863608306;
+
+	const double sigma_humidity = (1. - affinity.pickiness());
+	const double sigma_temperature = (1. - affinity.pickiness());
+	const double sigma_fertility = (1. - affinity.pickiness());
+
+	return exp((-pow2((affinity.preferred_fertility() - terrain_fertility) /
+							(kFertilityWeight * sigma_fertility)) -
+					pow2((affinity.preferred_humidity() - terrain_humidity) /
+						  (kHumidityWeight * sigma_humidity)) -
+					pow2((affinity.preferred_temperature() - terrain_temperature) /
+						  (kTemperatureWeight * sigma_temperature))) /
+				  2);
+}
+
 }  // namespace
 
 TerrainAffinity::TerrainAffinity(const LuaTable& table, const std::string& immovable_name)
@@ -111,21 +135,20 @@
 		average(ln.field->terrain_r());
 	}
 
-	constexpr double kHumidityWeight = 0.500086642549548;
-	constexpr double kFertilityWeight = 0.5292268046607387;
-	constexpr double kTemperatureWeight = 61.31300863608306;
-
-	const double sigma_humidity = (1. - affinity.pickiness());
-	const double sigma_temperature = (1. - affinity.pickiness());
-	const double sigma_fertility = (1. - affinity.pickiness());
-
-	return exp((-pow2((affinity.preferred_fertility() - terrain_fertility) /
-	                  (kFertilityWeight * sigma_fertility)) -
-	            pow2((affinity.preferred_humidity() - terrain_humidity) /
-	                 (kHumidityWeight * sigma_humidity)) -
-	            pow2((affinity.preferred_temperature() - terrain_temperature) /
-	                 (kTemperatureWeight * sigma_temperature))) /
-	           2);
+	return calculate_probability_to_grow(affinity,
+													 terrain_humidity,
+													 terrain_fertility,
+													 terrain_temperature);
+}
+
+
+double probability_to_grow(const TerrainAffinity& affinity,
+									const TerrainDescription& terrain) {
+
+	return calculate_probability_to_grow(affinity,
+													 terrain.humidity(),
+													 terrain.fertility(),
+													 terrain.temperature());
 }
 
 }  // namespace Widelands

=== modified file 'src/logic/map_objects/terrain_affinity.h'
--- src/logic/map_objects/terrain_affinity.h	2015-11-28 22:29:26 +0000
+++ src/logic/map_objects/terrain_affinity.h	2016-01-26 07:30:52 +0000
@@ -69,6 +69,10 @@
 	(const TerrainAffinity& immovable_affinity, const FCoords& fcoords,
 	 const Map& map, const DescriptionMaintainer<TerrainDescription>& terrains);
 
+// Probability to grow for a single terrain
+double probability_to_grow
+	(const TerrainAffinity& immovable_affinity, const TerrainDescription& terrain);
+
 }  // namespace Widelands
 
 #endif  // end of include guard: WL_LOGIC_MAP_OBJECTS_TERRAIN_AFFINITY_H

=== modified file 'src/logic/map_objects/tribes/worker.cc'
--- src/logic/map_objects/tribes/worker.cc	2016-01-17 19:54:32 +0000
+++ src/logic/map_objects/tribes/worker.cc	2016-01-26 07:30:52 +0000
@@ -960,7 +960,7 @@
 		if (rdescr && rdescr->detectable() && position.field->get_resources_amount()) {
 			const std::string message =
 					(boost::format("<rt image=%s><p font-face=serif font-size=14>%s</p></rt>")
-					 % rdescr->get_editor_pic(rdescr->max_amount())
+					 % rdescr->representative_image()
 					 % _("A geologist found resources.")).str();
 
 			Message::Type message_type = Message::Type::kGeologists;

=== modified file 'src/logic/map_objects/world/resource_description.cc'
--- src/logic/map_objects/world/resource_description.cc	2015-11-28 22:29:26 +0000
+++ src/logic/map_objects/world/resource_description.cc	2016-01-26 07:30:52 +0000
@@ -31,7 +31,8 @@
    : name_(table.get_string("name")),
      descname_(table.get_string("descname")),
      detectable_(table.get_bool("detectable")),
-     max_amount_(table.get_int("max_amount")) {
+	  max_amount_(table.get_int("max_amount")),
+	  representative_image_(table.get_string("representative_image")) {
 
 	std::unique_ptr<LuaTable> st = table.get_table("editor_pictures");
 	const std::set<int> keys = st->keys<int>();
@@ -44,7 +45,7 @@
 	}
 }
 
-const std::string & ResourceDescription::get_editor_pic
+const std::string & ResourceDescription::editor_image
 	(uint32_t const amount) const
 {
 	uint32_t bestmatch = 0;

=== modified file 'src/logic/map_objects/world/resource_description.h'
--- src/logic/map_objects/world/resource_description.h	2015-11-28 22:29:26 +0000
+++ src/logic/map_objects/world/resource_description.h	2016-01-26 07:30:52 +0000
@@ -53,13 +53,17 @@
 
 	/// Returns the path to the image that should be used in the editor to
 	/// represent an 'amount' of this resource.
-	const std::string& get_editor_pic(uint32_t amount) const;
+	const std::string& editor_image(uint32_t amount) const;
+
+	/// Returns the path to the image that should be used in menus to represent this resource
+	const std::string& representative_image() const {return representative_image_;}
 
 private:
 	const std::string name_;
 	const std::string descname_;
 	const bool detectable_;
 	const int32_t max_amount_;
+	const std::string representative_image_;
 	std::vector<EditorPicture> editor_pictures_;
 
 	DISALLOW_COPY_AND_ASSIGN(ResourceDescription);

=== modified file 'src/logic/map_objects/world/terrain_description.cc'
--- src/logic/map_objects/world/terrain_description.cc	2016-01-17 09:55:27 +0000
+++ src/logic/map_objects/world/terrain_description.cc	2016-01-26 07:30:52 +0000
@@ -222,6 +222,10 @@
 	return valid_resources_.size();
 }
 
+std::vector<uint8_t> TerrainDescription::valid_resources() const {
+	return valid_resources_;
+}
+
 bool TerrainDescription::is_resource_valid(const int res) const {
 	for (const uint8_t resource_index : valid_resources_) {
 		if (resource_index == res) {

=== modified file 'src/logic/map_objects/world/terrain_description.h'
--- src/logic/map_objects/world/terrain_description.h	2016-01-13 07:27:55 +0000
+++ src/logic/map_objects/world/terrain_description.h	2016-01-26 07:30:52 +0000
@@ -94,6 +94,9 @@
 	/// Returns the number of valid resources.
 	int get_num_valid_resources() const;
 
+	/// Returns the the valid resources.
+	std::vector<uint8_t> valid_resources() const;
+
 	/// Returns true if this resource can be found in this terrain type.
 	bool is_resource_valid(int32_t res) const;
 

=== modified file 'src/scripting/lua_bases.cc'
--- src/scripting/lua_bases.cc	2015-11-28 22:29:26 +0000
+++ src/scripting/lua_bases.cc	2016-01-26 07:30:52 +0000
@@ -27,6 +27,7 @@
 #include "logic/map_objects/tribes/tribe_descr.h"
 #include "logic/map_objects/tribes/tribes.h"
 #include "logic/map_objects/tribes/ware_descr.h"
+#include "logic/map_objects/world/world.h"
 #include "logic/player.h"
 #include "scripting/factory.h"
 #include "scripting/globals.h"
@@ -78,10 +79,13 @@
 
 const char LuaEditorGameBase::className[] = "EditorGameBase";
 const MethodType<LuaEditorGameBase> LuaEditorGameBase::Methods[] = {
+	METHOD(LuaEditorGameBase, get_immovable_description),
 	METHOD(LuaEditorGameBase, get_building_description),
 	METHOD(LuaEditorGameBase, get_tribe_description),
 	METHOD(LuaEditorGameBase, get_ware_description),
 	METHOD(LuaEditorGameBase, get_worker_description),
+	METHOD(LuaEditorGameBase, get_resource_description),
+	METHOD(LuaEditorGameBase, get_terrain_description),
 	{nullptr, nullptr},
 };
 const PropertyType<LuaEditorGameBase> LuaEditorGameBase::Properties[] = {
@@ -150,6 +154,38 @@
  */
 
 /* RST
+	.. function:: get_immovable_description(immovable_name)
+
+		:arg immovable_name: the name of the immovable
+
+		Registers an immovable description so Lua can reference it from the editor.
+
+		(RO) The :class:`~wl.Game.Immovable_description`.
+*/
+int LuaEditorGameBase::get_immovable_description(lua_State* L) {
+	if (lua_gettop(L) != 2) {
+		report_error(L, "Wrong number of arguments");
+	}
+	const std::string immovable_name = luaL_checkstring(L, 2);
+	const World& world = get_egbase(L).world();
+	DescriptionIndex idx = world.get_immovable_index(immovable_name);
+	if (idx != INVALID_INDEX) {
+		const ImmovableDescr* descr = world.get_immovable_descr(idx);
+		return to_lua<LuaMaps::LuaImmovableDescription>(L, new LuaMaps::LuaImmovableDescription(descr));
+	} else {
+		const Tribes& tribes = get_egbase(L).tribes();
+		idx = tribes.immovable_index(immovable_name);
+		if (!tribes.immovable_exists(idx)) {
+			report_error(L, "Immovable %s does not exist", immovable_name.c_str());
+		}
+		const ImmovableDescr* descr = tribes.get_immovable_descr(idx);
+		return to_lua<LuaMaps::LuaImmovableDescription>(L, new LuaMaps::LuaImmovableDescription(descr));
+	}
+	return 0;
+}
+
+
+/* RST
 	.. function:: get_building_description(building_description.name)
 
 		:arg building_name: the name of the building
@@ -244,6 +280,53 @@
 	return LuaMaps::upcasted_map_object_descr_to_lua(L, worker_description);
 }
 
+/* RST
+	.. function:: get_resource_description(resource_name)
+
+		:arg resource_name: the name of the resource
+
+		Registers a resource description so Lua can reference it from the editor.
+
+		(RO) The :class:`~wl.Game.Resource_description`.
+*/
+int LuaEditorGameBase::get_resource_description(lua_State* L) {
+	if (lua_gettop(L) != 2) {
+		report_error(L, "Wrong number of arguments");
+	}
+	const std::string resource_name = luaL_checkstring(L, 2);
+	const World& world = get_egbase(L).world();
+	DescriptionIndex idx = world.get_resource(resource_name.c_str());
+
+	if (idx == INVALID_INDEX) {
+		report_error(L, "Resource %s does not exist", resource_name.c_str());
+	}
+
+	const ResourceDescription* descr = world.get_resource(idx);
+	return to_lua<LuaMaps::LuaResourceDescription>(L, new LuaMaps::LuaResourceDescription(descr));
+}
+
+/* RST
+	.. function:: get_terrain_description(terrain_name)
+
+		:arg terrain_name: the name of the terrain
+
+		Registers a terrain description so Lua can reference it from the editor.
+
+		(RO) The :class:`~wl.Game.Terrain_description`.
+*/
+int LuaEditorGameBase::get_terrain_description(lua_State* L) {
+	if (lua_gettop(L) != 2) {
+		report_error(L, "Wrong number of arguments");
+	}
+	const std::string terrain_name = luaL_checkstring(L, 2);
+	const TerrainDescription* descr = get_egbase(L).world().get_ter(terrain_name.c_str());
+	if (!descr) {
+		report_error(L, "Terrain %s does not exist", terrain_name.c_str());
+	}
+	return to_lua<LuaMaps::LuaTerrainDescription>(L, new LuaMaps::LuaTerrainDescription(descr));
+}
+
+
 /*
  ==========================================================
  C METHODS

=== modified file 'src/scripting/lua_bases.h'
--- src/scripting/lua_bases.h	2015-04-11 08:58:15 +0000
+++ src/scripting/lua_bases.h	2016-01-26 07:30:52 +0000
@@ -58,10 +58,13 @@
 	/*
 	 * Lua methods
 	 */
+	int get_immovable_description(lua_State * L);
 	int get_building_description(lua_State * L);
 	int get_tribe_description(lua_State * L);
 	int get_ware_description(lua_State * L);
 	int get_worker_description(lua_State * L);
+	int get_resource_description(lua_State * L);
+	int get_terrain_description(lua_State * L);
 
 	/*
 	 * C methods

=== modified file 'src/scripting/lua_map.cc'
--- src/scripting/lua_map.cc	2016-01-24 17:01:59 +0000
+++ src/scripting/lua_map.cc	2016-01-26 07:30:52 +0000
@@ -31,11 +31,13 @@
 #include "logic/findimmovable.h"
 #include "logic/map_objects/checkstep.h"
 #include "logic/map_objects/immovable.h"
+#include "logic/map_objects/terrain_affinity.h"
 #include "logic/map_objects/tribes/carrier.h"
 #include "logic/map_objects/tribes/ship.h"
 #include "logic/map_objects/tribes/soldier.h"
 #include "logic/map_objects/tribes/tribes.h"
 #include "logic/map_objects/tribes/warelist.h"
+#include "logic/map_objects/world/editor_category.h"
 #include "logic/map_objects/world/resource_description.h"
 #include "logic/map_objects/world/terrain_description.h"
 #include "logic/map_objects/world/world.h"
@@ -605,6 +607,8 @@
 				return CAST_TO_LUA(WorkerDescr, LuaWorkerDescription);
 			case MapObjectType::SOLDIER:
 				return CAST_TO_LUA(WorkerDescr, LuaWorkerDescription);
+			case MapObjectType::IMMOVABLE:
+				return CAST_TO_LUA(ImmovableDescr, LuaImmovableDescription);
 			default:
 				return CAST_TO_LUA(MapObjectDescr, LuaMapObjectDescription);
 		}
@@ -1406,6 +1410,168 @@
 	return 1;
 }
 
+
+/* RST
+BuildingDescription
+-------------------
+
+.. class:: LuaImmovableDescription
+
+	A static description of a base immovable, so it can be used in help files
+	without having to access an actual immovalbe on the map.
+	See also class MapObjectDescription for more properties.
+*/
+const char LuaImmovableDescription::className[] = "ImmovableDescription";
+const MethodType<LuaImmovableDescription> LuaImmovableDescription::Methods[] = {
+	{nullptr, nullptr},
+};
+const PropertyType<LuaImmovableDescription> LuaImmovableDescription::Properties[] = {
+	PROP_RO(LuaImmovableDescription, basename),
+	PROP_RO(LuaImmovableDescription, build_cost),
+	PROP_RO(LuaImmovableDescription, editor_category),
+	PROP_RO(LuaImmovableDescription, has_terrain_affinity),
+	PROP_RO(LuaImmovableDescription, pickiness),
+	PROP_RO(LuaImmovableDescription, preferred_fertility),
+	PROP_RO(LuaImmovableDescription, preferred_humidity),
+	PROP_RO(LuaImmovableDescription, preferred_temperature),
+	PROP_RO(LuaImmovableDescription, owner_type),
+	PROP_RO(LuaImmovableDescription, size),
+	{nullptr, nullptr, nullptr},
+};
+
+
+void LuaImmovableDescription::__persist(lua_State* L) {
+	const ImmovableDescr* descr = get();
+	PERS_STRING("name", descr->name());
+}
+
+void LuaImmovableDescription::__unpersist(lua_State* L) {
+	std::string name;
+	UNPERS_STRING("name", name);
+	const World& world = get_egbase(L).world();
+	DescriptionIndex idx = world.get_immovable_index(name);
+	if (idx == INVALID_INDEX) {
+		throw LuaError((boost::format("Immovable '%s' doesn't exist.") % name).str());
+	}
+	set_description_pointer(world.get_immovable_descr(idx));
+}
+
+
+/* RST
+	.. attribute:: the basename of a tree for editor lists
+
+			(RO) the basename of the immovable, or an empty string if it has none.
+*/
+int LuaImmovableDescription::get_basename(lua_State * L) {
+	lua_pushstring(L, get()->basename());
+	return 1;
+}
+
+
+/* RST
+	.. attribute:: build_cost
+
+			(RO) a list of ware build cost for the immovable.
+*/
+int LuaImmovableDescription::get_build_cost(lua_State * L) {
+	return wares_or_workers_map_to_lua(L, get()->buildcost(), MapObjectType::WARE);
+}
+
+/* RST
+	.. attribute:: the name and descname of the editor category of this immovable
+
+			(RO) a table with "name" and "descname" entries for the editor category, or nil if it has none.
+*/
+int LuaImmovableDescription::get_editor_category(lua_State * L) {
+	if (get()->has_editor_category()) {
+		const EditorCategory& editor_category = get()->editor_category();
+		lua_newtable(L);
+		lua_pushstring(L, "name");
+		lua_pushstring(L, editor_category.name());
+		lua_settable(L, -3);
+		lua_pushstring(L, "descname");
+		lua_pushstring(L, editor_category.descname());
+		lua_settable(L, -3);
+	} else {
+		lua_pushnil(L);
+	}
+	return 1;
+}
+
+/* RST
+	.. attribute:: whether this immovable has terrain affinity
+
+			(RO) true if this immovable has terrain affinity
+*/
+int LuaImmovableDescription::get_has_terrain_affinity(lua_State * L) {
+	lua_pushboolean(L, get()->has_terrain_affinity());
+	return 1;
+}
+
+/* RST
+	.. attribute:: the terrain affinity's pickiness
+
+			(RO) the immovable's pickiness terrain affinity value. 0 if it has no terrain affinity.
+*/
+int LuaImmovableDescription::get_pickiness(lua_State * L) {
+	lua_pushnumber(L, get()->has_terrain_affinity() ? get()->terrain_affinity().pickiness() : 0);
+	return 1;
+}
+
+/* RST
+	.. attribute:: the terrain affinity's preferred_fertility
+
+			(RO) the immovable's preferred_fertility terrain affinity value. 0 if it has no terrain affinity.
+*/
+int LuaImmovableDescription::get_preferred_fertility(lua_State * L) {
+	lua_pushnumber(L, get()->has_terrain_affinity() ? get()->terrain_affinity().preferred_fertility() : 0);
+	return 1;
+}
+
+/* RST
+	.. attribute:: the terrain affinity's preferred_humidity
+
+			(RO) the immovable's preferred_humidity terrain affinity value. 0 if it has no terrain affinity.
+*/
+int LuaImmovableDescription::get_preferred_humidity(lua_State * L) {
+	lua_pushnumber(L, get()->has_terrain_affinity() ? get()->terrain_affinity().preferred_humidity() : 0);
+	return 1;
+}
+
+
+/* RST
+	.. attribute:: the terrain affinity's preferred_temperature
+
+			(RO) the immovable's preferred_temperature terrain affinity value. 0 if it has no terrain affinity.
+*/
+int LuaImmovableDescription::get_preferred_temperature(lua_State * L) {
+	lua_pushnumber(L, get()->has_terrain_affinity() ? get()->terrain_affinity().preferred_temperature() : 0);
+	return 1;
+}
+
+
+/* RST
+	.. attribute:: the owner type of this immovable
+
+			(RO) "world" for world immovables and "tribe" for tribe immovables.
+*/
+int LuaImmovableDescription::get_owner_type(lua_State * L) {
+	lua_pushstring(L, get()->owner_type() == MapObjectDescr::OwnerType::kWorld ? "world" : "tribe");
+	return 1;
+}
+
+
+/* RST
+	.. attribute:: size
+
+			(RO) the size of the immovable as an int.
+*/
+int LuaImmovableDescription::get_size(lua_State * L) {
+	lua_pushinteger(L, get()->get_size());
+	return 1;
+}
+
+
 /* RST
 BuildingDescription
 -------------------
@@ -2311,7 +2477,7 @@
 
 
 /* RST
-	.. attribute:: is_construction_material
+	.. .. method:: is_construction_material
 
 		:arg tribename: the name of the tribe that this ware gets checked for
 		:type tribename: :class:`string`
@@ -2477,6 +2643,340 @@
  ==========================================================
  */
 
+/* RST
+ResourceDescription
+--------------------
+.. class:: ResourceDescription
+
+	A static description of a resource.
+*/
+const char LuaResourceDescription::className[] = "ResourceDescription";
+const MethodType<LuaResourceDescription> LuaResourceDescription::Methods[] = {
+	METHOD(LuaResourceDescription, editor_image),
+	{nullptr, nullptr},
+};
+const PropertyType<LuaResourceDescription> LuaResourceDescription::Properties[] = {
+	PROP_RO(LuaResourceDescription, name),
+	PROP_RO(LuaResourceDescription, descname),
+	PROP_RO(LuaResourceDescription, is_detectable),
+	PROP_RO(LuaResourceDescription, max_amount),
+	PROP_RO(LuaResourceDescription, representative_image),
+	{nullptr, nullptr, nullptr},
+};
+
+void LuaResourceDescription::__persist(lua_State* L) {
+	const Widelands::ResourceDescription* descr = get();
+	PERS_STRING("name", descr->name());
+}
+
+void LuaResourceDescription::__unpersist(lua_State* L) {
+	std::string name;
+	UNPERS_STRING("name", name);
+	const World& world = get_egbase(L).world();
+	const ResourceDescription* descr = world.get_resource(world.safe_resource_index(name.c_str()));
+	set_description_pointer(descr);
+}
+
+/*
+ ==========================================================
+ PROPERTIES
+ ==========================================================
+ */
+
+/* RST
+	.. attribute:: name
+
+			(RO) the :class:`string` internal name of this resource
+*/
+
+int LuaResourceDescription::get_name(lua_State * L) {
+	lua_pushstring(L, get()->name());
+	return 1;
+}
+
+/* RST
+	.. attribute:: descname
+
+			(RO) the :class:`string` display name of this resource
+*/
+
+int LuaResourceDescription::get_descname(lua_State * L) {
+	lua_pushstring(L, get()->descname());
+	return 1;
+}
+
+/* RST
+	.. attribute:: is_detectable
+
+			(RO) true if geologists can find this resource
+*/
+
+int LuaResourceDescription::get_is_detectable(lua_State * L) {
+	lua_pushboolean(L, get()->detectable());
+	return 1;
+}
+
+/* RST
+	.. attribute:: max_amount
+
+			(RO) the maximum amount of this resource that a terrain can have
+*/
+
+int LuaResourceDescription::get_max_amount(lua_State * L) {
+	lua_pushinteger(L, get()->max_amount());
+	return 1;
+}
+
+/* RST
+	.. attribute:: representative_image
+
+			(RO) the :class:`string` path to the image representing this resource in the GUI
+*/
+int LuaResourceDescription::get_representative_image(lua_State * L) {
+	lua_pushstring(L, get()->representative_image());
+	return 1;
+}
+
+
+/*
+ ==========================================================
+ METHODS
+ ==========================================================
+ */
+
+/* RST
+	.. method:: representative_image(amount)
+
+		:arg amount: The amount of the resource what we want an overlay image for
+
+			(RO) the :class:`string` path to the image representing the specified amount of this resource
+*/
+int LuaResourceDescription::editor_image(lua_State * L) {
+	if (lua_gettop(L) != 2) {
+		report_error(L, "Takes only one argument.");
+	}
+	const uint32_t amount = luaL_checkuint32(L, 2);
+	lua_pushstring(L, get()->editor_image(amount));
+	return 1;
+}
+
+/* RST
+TerrainDescription
+--------------------
+.. class:: TerrainDescription
+
+	A static description of a terrain.
+*/
+const char LuaTerrainDescription::className[] = "TerrainDescription";
+const MethodType<LuaTerrainDescription> LuaTerrainDescription::Methods[] = {
+	METHOD(LuaTerrainDescription, probability_to_grow),
+	{nullptr, nullptr},
+};
+const PropertyType<LuaTerrainDescription> LuaTerrainDescription::Properties[] = {
+	PROP_RO(LuaTerrainDescription, name),
+	PROP_RO(LuaTerrainDescription, descname),
+	PROP_RO(LuaTerrainDescription, default_resource_name),
+	PROP_RO(LuaTerrainDescription, default_resource_amount),
+	PROP_RO(LuaTerrainDescription, editor_category),
+	PROP_RO(LuaTerrainDescription, fertility),
+	PROP_RO(LuaTerrainDescription, humidity),
+	PROP_RO(LuaTerrainDescription, representative_image),
+	PROP_RO(LuaTerrainDescription, temperature),
+	PROP_RO(LuaTerrainDescription, valid_resources_names),
+	{nullptr, nullptr, nullptr},
+};
+
+void LuaTerrainDescription::__persist(lua_State* L) {
+	const Widelands::TerrainDescription* descr = get();
+	PERS_STRING("name", descr->name());
+}
+
+void LuaTerrainDescription::__unpersist(lua_State* L) {
+	std::string name;
+	UNPERS_STRING("name", name);
+	set_description_pointer(get_egbase(L).world().get_ter(name.c_str()));
+}
+
+/*
+ ==========================================================
+ PROPERTIES
+ ==========================================================
+ */
+
+/* RST
+	.. attribute:: name
+
+			(RO) the :class:`string` internal name of this terrain
+*/
+
+int LuaTerrainDescription::get_name(lua_State * L) {
+	lua_pushstring(L, get()->name());
+	return 1;
+}
+
+/* RST
+	.. attribute:: descname
+
+			(RO) the :class:`string` display name of this terrain
+*/
+
+int LuaTerrainDescription::get_descname(lua_State * L) {
+	lua_pushstring(L, get()->descname());
+	return 1;
+}
+
+/* RST
+	.. attribute:: get_default_resource_descname
+
+			(RO) the :class:`string` description name of the default resource provided by this terrain, or
+				  nil if the terrain has no default resource.
+*/
+
+int LuaTerrainDescription::get_default_resource_name(lua_State * L) {
+	int res_index = get()->get_default_resource();
+	const World& world = get_egbase(L).world();
+	if (res_index != Widelands::kNoResource && res_index < world.get_nr_resources()) {
+		lua_pushstring(L, world.get_resource(res_index)->name());
+	} else {
+		lua_pushnil(L);
+	}
+	return 1;
+}
+
+/* RST
+	.. attribute:: default_resource_amount
+
+			(RO) the int amount of the default resource provided by this terrain.
+*/
+
+int LuaTerrainDescription::get_default_resource_amount(lua_State * L) {
+	lua_pushinteger(L, get()->get_default_resource_amount());
+	return 1;
+}
+
+
+/* RST
+	.. attribute:: the name and descname of the editor category of this terrain
+
+			(RO) a table with "name" and "descname" entries for the editor category, or nil if it has none.
+*/
+int LuaTerrainDescription::get_editor_category(lua_State * L) {
+	const EditorCategory& editor_category = get()->editor_category();
+	if (&editor_category) {
+		lua_newtable(L);
+		lua_pushstring(L, "name");
+		lua_pushstring(L, editor_category.name());
+		lua_settable(L, -3);
+		lua_pushstring(L, "descname");
+		lua_pushstring(L, editor_category.descname());
+		lua_settable(L, -3);
+	} else {
+		lua_pushnil(L);
+	}
+	return 1;
+}
+
+/* RST
+	.. attribute:: fertility
+
+			(RO) the :class:`double` fertility value for this terrain
+*/
+
+int LuaTerrainDescription::get_fertility(lua_State * L) {
+	lua_pushnumber(L, get()->fertility());
+	return 1;
+}
+
+/* RST
+	.. attribute:: humidity
+
+			(RO) the :class:`double` humidity value for this terrain
+*/
+
+int LuaTerrainDescription::get_humidity(lua_State * L) {
+	lua_pushnumber(L, get()->humidity());
+	return 1;
+}
+
+/* RST
+	.. attribute:: representative_image
+
+			(RO) the :class:`string` file path to a representative image
+*/
+
+int LuaTerrainDescription::get_representative_image(lua_State * L) {
+	lua_pushstring(L, *get()->texture_paths().begin());
+	return 1;
+}
+
+/* RST
+	.. attribute:: temperature
+
+			(RO) the :class:`double` temperature value for this terrain
+*/
+
+int LuaTerrainDescription::get_temperature(lua_State * L) {
+	lua_pushnumber(L, get()->temperature());
+	return 1;
+}
+
+
+/* RST
+	.. attribute:: valid_resources_names
+
+			(RO) a list of the names for all valid resources for this terrain.
+*/
+
+int LuaTerrainDescription::get_valid_resources_names(lua_State * L) {
+	const World& world = get_egbase(L).world();
+	lua_newtable(L);
+	int index = 1;
+	for (uint8_t res_index : get()->valid_resources()) {
+		if (res_index != Widelands::kNoResource && res_index < world.get_nr_resources()) {
+			lua_pushint32(L, index++);
+			lua_pushstring(L, world.get_resource(res_index)->name());
+			lua_settable(L, -3);
+		}
+	}
+	return 1;
+}
+
+
+/*
+ ==========================================================
+ METHODS
+ ==========================================================
+ */
+
+/* RST
+	.. method:: probability_to_grow
+
+		:arg treename: The tree that we are checking the probability for.
+		:type treename: :class:`string`
+
+		(RO) A double describing the probability that the given tree will grow on this terrain.
+*/
+int LuaTerrainDescription::probability_to_grow(lua_State * L) {
+	if (lua_gettop(L) != 2) {
+		report_error(L, "Takes only one argument.");
+	}
+	const std::string tree_name = luaL_checkstring(L, 2);
+	const World& world = get_egbase(L).world();
+	const DescriptionIndex index = world.get_immovable_index(tree_name);
+	if (index != INVALID_INDEX) {
+		const ImmovableDescr* tree = world.get_immovable_descr(index);
+		if (tree->has_terrain_affinity()) {
+			lua_pushnumber(L, Widelands::probability_to_grow(tree->terrain_affinity(), *get()));
+		} else {
+			lua_pushnil(L);
+		}
+	} else {
+		lua_pushnil(L);
+	}
+	return 1;
+}
+
+
 
 
 /* RST
@@ -4943,6 +5443,10 @@
 	register_class<LuaTribeDescription>(L, "map");
 	register_class<LuaMapObjectDescription>(L, "map");
 
+	register_class<LuaImmovableDescription>(L, "map", true);
+	add_parent<LuaImmovableDescription, LuaMapObjectDescription>(L);
+	lua_pop(L, 1); // Pop the meta table
+
 	register_class<LuaBuildingDescription>(L, "map", true);
 	add_parent<LuaBuildingDescription, LuaMapObjectDescription>(L);
 	lua_pop(L, 1); // Pop the meta table
@@ -4986,6 +5490,9 @@
 	add_parent<LuaWorkerDescription, LuaMapObjectDescription>(L);
 	lua_pop(L, 1); // Pop the meta table
 
+	register_class<LuaResourceDescription>(L, "map");
+	register_class<LuaTerrainDescription>(L, "map");
+
 	register_class<LuaField>(L, "map");
 	register_class<LuaPlayerSlot>(L, "map");
 	register_class<LuaMapObject>(L, "map");

=== modified file 'src/scripting/lua_map.h'
--- src/scripting/lua_map.h	2016-01-22 19:53:32 +0000
+++ src/scripting/lua_map.h	2016-01-26 07:30:52 +0000
@@ -35,6 +35,7 @@
 #include "logic/map_objects/tribes/trainingsite.h"
 #include "logic/map_objects/tribes/warehouse.h"
 #include "logic/map_objects/tribes/worker.h"
+#include "logic/map_objects/world/terrain_description.h"
 #include "scripting/lua.h"
 #include "scripting/luna.h"
 
@@ -43,8 +44,10 @@
 	class SoldierDescr;
 	class BuildingDescr;
 	class Bob;
+	class ResourceDescription;
 	class WareDescr;
 	class WorkerDescr;
+	class TerrainDescription;
 	class TribeDescr;
 }
 
@@ -203,6 +206,49 @@
 		return static_cast<const Widelands::klass*>(LuaMapObjectDescription::get());                  \
 	}
 
+class LuaImmovableDescription : public LuaMapObjectDescription {
+public:
+	LUNA_CLASS_HEAD(LuaImmovableDescription);
+
+	virtual ~LuaImmovableDescription() {}
+
+	LuaImmovableDescription() {}
+	LuaImmovableDescription(const Widelands::ImmovableDescr* const immovabledescr)
+		: LuaMapObjectDescription(immovabledescr) {
+	}
+	LuaImmovableDescription(lua_State* L) : LuaMapObjectDescription(L) {
+	}
+
+	void __persist(lua_State * L) override;
+	void __unpersist(lua_State * L) override;
+
+	/*
+	 * Properties
+	 */
+	int get_basename(lua_State *);
+	int get_build_cost(lua_State *);
+	int get_editor_category(lua_State *);
+	int get_has_terrain_affinity(lua_State *);
+	int get_pickiness(lua_State *);
+	int get_preferred_fertility(lua_State *);
+	int get_preferred_humidity(lua_State *);
+	int get_preferred_temperature(lua_State *);
+	int get_owner_type(lua_State *);
+	int get_size(lua_State *);
+
+	/*
+	 * Lua methods
+	 */
+
+	/*
+	 * C methods
+	 */
+
+private:
+	CASTED_GET_DESCRIPTION(ImmovableDescr)
+};
+
+
 class LuaBuildingDescription : public LuaMapObjectDescription {
 public:
 	LUNA_CLASS_HEAD(LuaBuildingDescription);
@@ -515,6 +561,110 @@
 
 #undef CASTED_GET_DESCRIPTION
 
+
+class LuaResourceDescription : public LuaMapModuleClass {
+public:
+	LUNA_CLASS_HEAD(LuaResourceDescription);
+
+	virtual ~LuaResourceDescription() {}
+
+	LuaResourceDescription() : resourcedescr_(nullptr) {}
+	LuaResourceDescription(const Widelands::ResourceDescription* const resourcedescr)
+		: resourcedescr_(resourcedescr) {}
+	LuaResourceDescription(lua_State* L) : resourcedescr_(nullptr) {
+		report_error(L, "Cannot instantiate a 'LuaResourceDescription' directly!");
+	}
+
+	void __persist(lua_State * L) override;
+	void __unpersist(lua_State * L) override;
+
+	/*
+	 * Properties
+	 */
+	int get_name(lua_State *);
+	int get_descname(lua_State *);
+	int get_is_detectable(lua_State *);
+	int get_max_amount(lua_State *);
+	int get_representative_image(lua_State *);
+
+	/*
+	 * Lua methods
+	 */
+
+	int editor_image(lua_State *);
+
+	/*
+	 * C methods
+	 */
+protected:
+	const Widelands::ResourceDescription* get() const {
+		assert(resourcedescr_ != nullptr);
+		return resourcedescr_;
+	}
+	// For persistence.
+	void set_description_pointer(const Widelands::ResourceDescription* pointer) {
+		resourcedescr_ = pointer;
+	}
+
+private:
+	const Widelands::ResourceDescription* resourcedescr_;
+};
+
+
+
+class LuaTerrainDescription : public LuaMapModuleClass {
+public:
+	LUNA_CLASS_HEAD(LuaTerrainDescription);
+
+	virtual ~LuaTerrainDescription() {}
+
+	LuaTerrainDescription() : terraindescr_(nullptr) {}
+	LuaTerrainDescription(const Widelands::TerrainDescription* const terraindescr)
+		: terraindescr_(terraindescr) {}
+	LuaTerrainDescription(lua_State* L) : terraindescr_(nullptr) {
+		report_error(L, "Cannot instantiate a 'LuaTerrainDescription' directly!");
+	}
+
+	void __persist(lua_State * L) override;
+	void __unpersist(lua_State * L) override;
+
+	/*
+	 * Properties
+	 */
+	int get_name(lua_State *);
+	int get_descname(lua_State *);
+	int get_default_resource_name(lua_State *);
+	int get_default_resource_amount(lua_State *);
+	int get_editor_category(lua_State *);
+	int get_fertility(lua_State *);
+	int get_humidity(lua_State *);
+	int get_representative_image(lua_State *);
+	int get_temperature(lua_State *);
+	int get_valid_resources_names(lua_State *);
+
+	/*
+	 * Lua methods
+	 */
+	int probability_to_grow(lua_State *);
+
+	/*
+	 * C methods
+	 */
+protected:
+	const Widelands::TerrainDescription* get() const {
+		assert(terraindescr_ != nullptr);
+		return terraindescr_;
+	}
+	// For persistence.
+	void set_description_pointer(const Widelands::TerrainDescription* pointer) {
+		terraindescr_ = pointer;
+	}
+
+private:
+	const Widelands::TerrainDescription* terraindescr_;
+};
+
+
 #define CASTED_GET(klass) \
 Widelands:: klass * get(lua_State * L, Widelands::EditorGameBase & egbase) { \
 	return static_cast<Widelands:: klass *> \

=== modified file 'src/scripting/lua_root.cc'
--- src/scripting/lua_root.cc	2015-11-28 22:29:26 +0000
+++ src/scripting/lua_root.cc	2016-01-26 07:30:52 +0000
@@ -309,6 +309,8 @@
 
 const char LuaWorld::className[] = "World";
 const MethodType<LuaWorld> LuaWorld::Methods[] = {
+	METHOD(LuaWorld, immovable_descriptions),
+	METHOD(LuaWorld, terrain_descriptions),
 	METHOD(LuaWorld, new_critter_type),
 	METHOD(LuaWorld, new_editor_immovable_category),
 	METHOD(LuaWorld, new_editor_terrain_category),
@@ -345,6 +347,55 @@
  */
 
 /* RST
+	.. method:: immovable_descriptions(attribute_name)
+
+		Returns a list of names with the immovables that have the attribute with the given attribute_name.
+
+		(RO) a list of immovable names, e.g. {"alder_summer_old", "cirrus_wasteland_old", ...}
+*/
+int LuaWorld::immovable_descriptions(lua_State* L) {
+	if (lua_gettop(L) != 2) {
+		report_error(L, "Takes only one argument.");
+	}
+	const World& world = get_egbase(L).world();
+	const std::string attribute_name = luaL_checkstring(L, 2);
+	lua_newtable(L);
+	int index = 1;
+	for (DescriptionIndex i = 0; i < world.get_nr_immovables(); ++i) {
+		const ImmovableDescr* immovable = world.get_immovable_descr(i);
+		uint32_t attribute_id = immovable->get_attribute_id(attribute_name);
+		if (immovable->has_attribute(attribute_id)) {
+			lua_pushint32(L, index++);
+			lua_pushstring(L, immovable->name());
+			lua_settable(L, -3);
+		}
+	}
+	return 1;
+}
+
+
+/* RST
+	.. method:: terrain_descriptions()
+
+		Returns a list of names with the terrains that are available in the worls.
+
+		(RO) a list of terrain names, e.g. {"wiese1", "wiese2", ...}
+*/
+int LuaWorld::terrain_descriptions(lua_State* L) {
+	const World& world = get_egbase(L).world();
+	lua_newtable(L);
+	int index = 1;
+	for (DescriptionIndex i = 0; i < world.terrains().size(); ++i) {
+		const TerrainDescription& terrain = world.terrain_descr(i);
+		lua_pushint32(L, index++);
+		lua_pushstring(L, terrain.name());
+		lua_settable(L, -3);
+	}
+	return 1;
+}
+
+
+/* RST
 	.. method:: new_resource_type(table)
 
 		Adds a new resource type that can be in the different maps. Takes a

=== modified file 'src/scripting/lua_root.h'
--- src/scripting/lua_root.h	2015-09-04 11:11:50 +0000
+++ src/scripting/lua_root.h	2016-01-26 07:30:52 +0000
@@ -111,6 +111,8 @@
 	/*
 	 * Lua methods
 	 */
+	int immovable_descriptions(lua_State* L);
+	int terrain_descriptions(lua_State* L);
 	int new_critter_type(lua_State* L);
 	int new_editor_immovable_category(lua_State* L);
 	int new_editor_terrain_category(lua_State* L);

=== modified file 'test/maps/lua_testsuite.wmf/scripting/immovables_descriptions.lua'
--- test/maps/lua_testsuite.wmf/scripting/immovables_descriptions.lua	2015-10-31 12:11:44 +0000
+++ test/maps/lua_testsuite.wmf/scripting/immovables_descriptions.lua	2016-01-26 07:30:52 +0000
@@ -13,6 +13,95 @@
    end)
 end
 
+
+--  =======================================================
+--  ***************** ImmovableDescription *****************
+--  =======================================================
+
+function test_descr:test_immovable_descr()
+   assert_error("Wrong immovable", function() egbase:get_immovable_description("XXX") end)
+   assert_error("Wrong number of parameters: 2", function() egbase:get_immovable_description("XXX", "YYY") end)
+   assert_error("Wrong number of parameters: 3", function() egbase:get_immovable_description("XXX","YYY","ZZZ") end)
+end
+
+function test_descr:test_immovable_basename()
+   assert_equal("", egbase:get_immovable_description("bush1").basename)
+   assert_equal("", egbase:get_immovable_description("cornfield_ripe").basename)
+   assert_equal("", egbase:get_immovable_description("alder_summer_sapling").basename)
+   assert_equal(_"Alder", egbase:get_immovable_description("alder_summer_old").basename)
+end
+
+function test_descr:test_immovable_build_cost()
+   local build_cost = egbase:get_immovable_description("atlanteans_shipconstruction").build_cost
+   assert_equal(10, build_cost["planks"])
+   assert_equal(2, build_cost["log"])
+   assert_equal(4, build_cost["spidercloth"])
+   assert_equal(nil, build_cost["wine"])
+end
+
+function test_descr:test_immovable_editor_category()
+   assert_equal("plants", egbase:get_immovable_description("bush1").editor_category.name)
+   assert_equal(_"Plants", egbase:get_immovable_description("bush1").editor_category.descname)
+   assert_equal(nil, egbase:get_immovable_description("cornfield_ripe").editor_category)
+   assert_equal("trees_deciduous", egbase:get_immovable_description("alder_summer_sapling").editor_category.name)
+   assert_equal(_"Deciduous Trees", egbase:get_immovable_description("alder_summer_sapling").editor_category.descname)
+   assert_equal("trees_deciduous", egbase:get_immovable_description("alder_summer_old").editor_category.name)
+end
+
+function test_descr:test_immovable_has_terrain_affinity()
+   assert_equal(false, egbase:get_immovable_description("bush1").has_terrain_affinity)
+   assert_equal(false, egbase:get_immovable_description("cornfield_ripe").has_terrain_affinity)
+   assert_equal(true, egbase:get_immovable_description("alder_summer_sapling").has_terrain_affinity)
+   assert_equal(true, egbase:get_immovable_description("alder_summer_old").has_terrain_affinity)
+end
+
+function test_descr:test_immovable_pickiness()
+   assert_equal(0, egbase:get_immovable_description("bush1").pickiness)
+   assert_equal(0, egbase:get_immovable_description("cornfield_ripe").pickiness)
+   assert_equal(0.6, egbase:get_immovable_description("alder_summer_sapling").pickiness)
+   assert_equal(0.6, egbase:get_immovable_description("alder_summer_old").pickiness)
+   assert_equal(0.6, egbase:get_immovable_description("mushroom_red_wasteland_sapling").pickiness)
+end
+
+function test_descr:test_immovable_preferred_fertility()
+   assert_equal(0, egbase:get_immovable_description("bush1").preferred_fertility)
+   assert_equal(0, egbase:get_immovable_description("cornfield_ripe").preferred_fertility)
+   assert_equal(0.6, egbase:get_immovable_description("alder_summer_sapling").preferred_fertility)
+   assert_equal(0.6, egbase:get_immovable_description("alder_summer_old").preferred_fertility)
+   assert_equal(0.85, egbase:get_immovable_description("mushroom_red_wasteland_sapling").preferred_fertility)
+end
+
+function test_descr:test_immovable_preferred_humidity()
+   assert_equal(0, egbase:get_immovable_description("bush1").preferred_humidity)
+   assert_equal(0, egbase:get_immovable_description("cornfield_ripe").preferred_humidity)
+   assert_equal(0.65, egbase:get_immovable_description("alder_summer_sapling").preferred_humidity)
+   assert_equal(0.65, egbase:get_immovable_description("alder_summer_old").preferred_humidity)
+   assert_equal(0.35, egbase:get_immovable_description("mushroom_red_wasteland_sapling").preferred_humidity)
+end
+
+function test_descr:test_immovable_preferred_temperature()
+   assert_equal(0, egbase:get_immovable_description("bush1").preferred_temperature)
+   assert_equal(0, egbase:get_immovable_description("cornfield_ripe").preferred_temperature)
+   assert_equal(125, egbase:get_immovable_description("alder_summer_sapling").preferred_temperature)
+   assert_equal(125, egbase:get_immovable_description("alder_summer_old").preferred_temperature)
+   assert_equal(80, egbase:get_immovable_description("mushroom_red_wasteland_sapling").preferred_temperature)
+end
+
+function test_descr:test_immovable_owner_type()
+   assert_equal("world", egbase:get_immovable_description("bush1").owner_type)
+   assert_equal("tribe", egbase:get_immovable_description("cornfield_ripe").owner_type)
+   assert_equal("world", egbase:get_immovable_description("alder_summer_sapling").owner_type)
+   assert_equal("world", egbase:get_immovable_description("alder_summer_old").owner_type)
+end
+
+function test_descr:test_immovable_size()
+   assert_equal(0, egbase:get_immovable_description("bush1").size)
+  assert_equal(1, egbase:get_immovable_description("cornfield_ripe").size)
+   assert_equal(1, egbase:get_immovable_description("alder_summer_sapling").size)
+   assert_equal(1, egbase:get_immovable_description("alder_summer_old").size)
+end
+
+
 --  =======================================================
 --  ***************** BuildingDescription *****************
 --  =======================================================

=== modified file 'test/maps/lua_testsuite.wmf/scripting/init.lua'
--- test/maps/lua_testsuite.wmf/scripting/init.lua	2015-10-31 12:11:44 +0000
+++ test/maps/lua_testsuite.wmf/scripting/init.lua	2016-01-26 07:30:52 +0000
@@ -33,6 +33,7 @@
 
 include "map:scripting/immovables.lua"
 include "map:scripting/immovables_descriptions.lua"
+include "map:scripting/terrains_resources_descriptions.lua"
 include "map:scripting/tribes_descriptions.lua"
 
 if not wl.editor then

=== added file 'test/maps/lua_testsuite.wmf/scripting/terrains_resources_descriptions.lua'
--- test/maps/lua_testsuite.wmf/scripting/terrains_resources_descriptions.lua	1970-01-01 00:00:00 +0000
+++ test/maps/lua_testsuite.wmf/scripting/terrains_resources_descriptions.lua	2016-01-26 07:30:52 +0000
@@ -0,0 +1,154 @@
+set_textdomain("tribes")
+
+test_terrains_resource_descr = lunit.TestCase("Terrains and resources descriptions test")
+
+--  =======================================================
+--  ***************** ResourceDescription *****************
+--  =======================================================
+
+function test_terrains_resource_descr:test_resource_descr()
+   assert_error("Wrong terrain", function() egbase:get_resource_description("XXX") end)
+   assert_error("Wrong number of parameters: 2", function() egbase:get_resource_description("XXX", "YYY") end)
+   assert_error("Wrong number of parameters: 3", function() egbase:get_resource_description("XXX","YYY","ZZZ") end)
+end
+
+function test_terrains_resource_descr:test_resource_descname()
+   assert_equal(_"Coal", egbase:get_resource_description("coal").descname)
+   assert_equal(_"Stones", egbase:get_resource_description("stones").descname)
+   assert_equal(_"Water", egbase:get_resource_description("water").descname)
+   assert_equal(_"Fish", egbase:get_resource_description("fish").descname)
+end
+
+function test_terrains_resource_descr:test_resource_name()
+   assert_equal("coal", egbase:get_resource_description("coal").name)
+   assert_equal("stones", egbase:get_resource_description("stones").name)
+   assert_equal("water", egbase:get_resource_description("water").name)
+   assert_equal("fish", egbase:get_resource_description("fish").name)
+end
+
+function test_terrains_resource_descr:test_resource_is_detectable()
+   assert_equal(true, egbase:get_resource_description("coal").is_detectable)
+   assert_equal(true, egbase:get_resource_description("stones").is_detectable)
+   assert_equal(true, egbase:get_resource_description("water").is_detectable)
+   assert_equal(false, egbase:get_resource_description("fish").is_detectable)
+end
+
+function test_terrains_resource_descr:test_resource_max_amount()
+   assert_equal(20, egbase:get_resource_description("coal").max_amount)
+   assert_equal(20, egbase:get_resource_description("stones").max_amount)
+   assert_equal(50, egbase:get_resource_description("water").max_amount)
+   assert_equal(20, egbase:get_resource_description("fish").max_amount)
+end
+
+function test_terrains_resource_descr:test_resource_representative_image()
+   assert_equal("world/resources/pics/coal4.png", egbase:get_resource_description("coal").representative_image)
+   assert_equal("world/resources/pics/stones4.png", egbase:get_resource_description("stones").representative_image)
+   assert_equal("world/resources/pics/water4.png", egbase:get_resource_description("water").representative_image)
+   assert_equal("world/resources/pics/fish.png", egbase:get_resource_description("fish").representative_image)
+end
+
+function test_terrains_resource_descr:test_resource_editor_image()
+   assert_equal("world/resources/pics/coal1.png", egbase:get_resource_description("coal"):editor_image(0))
+   assert_equal("world/resources/pics/coal1.png", egbase:get_resource_description("coal"):editor_image(5))
+   assert_equal("world/resources/pics/coal2.png", egbase:get_resource_description("coal"):editor_image(6))
+   assert_equal("world/resources/pics/coal2.png", egbase:get_resource_description("coal"):editor_image(10))
+   assert_equal("world/resources/pics/coal3.png", egbase:get_resource_description("coal"):editor_image(15))
+   assert_equal("world/resources/pics/coal4.png", egbase:get_resource_description("coal"):editor_image(16))
+   assert_equal("world/resources/pics/coal4.png", egbase:get_resource_description("coal"):editor_image(1000))
+end
+
+--  =======================================================
+--  ***************** TerrainDescription ******************
+--  =======================================================
+
+function test_terrains_resource_descr:test_terrain_descr()
+   assert_error("Wrong terrain", function() egbase:get_terrain_description("XXX") end)
+   assert_error("Wrong number of parameters: 2", function() egbase:get_terrain_description("XXX", "YYY") end)
+   assert_error("Wrong number of parameters: 3", function() egbase:get_terrain_description("XXX","YYY","ZZZ") end)
+end
+
+function test_terrains_resource_descr:test_terrain_descname()
+   assert_equal(_"Meadow", egbase:get_terrain_description("wiese1").descname)
+   assert_equal(_"Beach", egbase:get_terrain_description("wasteland_beach").descname)
+   assert_equal(_"Forested Mountain", egbase:get_terrain_description("desert_forested_mountain2").descname)
+   assert_equal(_"Water", egbase:get_terrain_description("winter_water").descname)
+end
+
+function test_terrains_resource_descr:test_terrain_name()
+   assert_equal("wiese1", egbase:get_terrain_description("wiese1").name)
+   assert_equal("wasteland_beach", egbase:get_terrain_description("wasteland_beach").name)
+   assert_equal("desert_forested_mountain2", egbase:get_terrain_description("desert_forested_mountain2").name)
+   assert_equal("winter_water", egbase:get_terrain_description("winter_water").name)
+end
+
+function test_terrains_resource_descr:test_terrain_default_resource_name()
+   assert_equal("water", egbase:get_terrain_description("wiese1").default_resource_name)
+   assert_equal(nil, egbase:get_terrain_description("wasteland_beach").default_resource_name)
+   assert_equal(nil, egbase:get_terrain_description("desert_forested_mountain2").default_resource_name)
+   assert_equal("fish", egbase:get_terrain_description("winter_water").default_resource_name)
+end
+
+function test_terrains_resource_descr:test_terrain_default_resource_amount()
+   assert_equal(10, egbase:get_terrain_description("wiese1").default_resource_amount)
+   assert_equal(0, egbase:get_terrain_description("wasteland_beach").default_resource_amount)
+   assert_equal(0, egbase:get_terrain_description("desert_forested_mountain2").default_resource_amount)
+   assert_equal(4, egbase:get_terrain_description("winter_water").default_resource_amount)
+end
+
+function test_terrains_resource_descr:test_terrain_editor_category()
+   assert_equal("green", egbase:get_terrain_description("wiese1").editor_category.name)
+   assert_equal(_"Summer", egbase:get_terrain_description("wiese1").editor_category.descname)
+   assert_equal("wasteland", egbase:get_terrain_description("wasteland_beach").editor_category.name)
+   assert_equal(_"Wasteland", egbase:get_terrain_description("wasteland_beach").editor_category.descname)
+   assert_equal("desert", egbase:get_terrain_description("desert_forested_mountain2").editor_category.name)
+   assert_equal(_"Desert", egbase:get_terrain_description("desert_forested_mountain2").editor_category.descname)
+   assert_equal("winter", egbase:get_terrain_description("winter_water").editor_category.name)
+   assert_equal(_"Winter", egbase:get_terrain_description("winter_water").editor_category.descname)
+end
+
+function test_terrains_resource_descr:test_terrain_fertility()
+   assert_equal(0.7, egbase:get_terrain_description("wiese1").fertility)
+   assert_equal(0.2, egbase:get_terrain_description("wasteland_beach").fertility)
+   assert_equal(0.5, egbase:get_terrain_description("desert_forested_mountain2").fertility)
+   assert_equal(0.001, egbase:get_terrain_description("winter_water").fertility)
+end
+
+function test_terrains_resource_descr:test_terrain_humidity()
+   assert_equal(0.6, egbase:get_terrain_description("wiese1").humidity)
+   assert_equal(0.4, egbase:get_terrain_description("wasteland_beach").humidity)
+   assert_equal(0.5, egbase:get_terrain_description("desert_forested_mountain2").humidity)
+   assert_equal(0.999, egbase:get_terrain_description("winter_water").humidity)
+end
+
+function test_terrains_resource_descr:test_terrain_temperature()
+   assert_equal(100, egbase:get_terrain_description("wiese1").temperature)
+   assert_equal(60, egbase:get_terrain_description("wasteland_beach").temperature)
+   assert_equal(120, egbase:get_terrain_description("desert_forested_mountain2").temperature)
+   assert_equal(50, egbase:get_terrain_description("winter_water").temperature)
+end
+
+function test_terrains_resource_descr:test_terrain_representative_image()
+   assert_equal("world/terrains/pics/green/wiese1_00.png", egbase:get_terrain_description("wiese1").representative_image)
+   assert_equal("world/terrains/pics/wasteland/strand_00.png", egbase:get_terrain_description("wasteland_beach").representative_image)
+   assert_equal("world/terrains/pics/desert/forested_mountain2_00.png", egbase:get_terrain_description("desert_forested_mountain2").representative_image)
+   assert_equal("world/terrains/pics/winter/water_00.png", egbase:get_terrain_description("winter_water").representative_image)
+end
+
+function test_terrains_resource_descr:test_valid_resources_names()
+   assert_equal("water", egbase:get_terrain_description("wiese1").valid_resources_names[1])
+   assert_equal(0, #egbase:get_terrain_description("wasteland_beach").valid_resources_names)
+   assert_equal(4, #egbase:get_terrain_description("desert_forested_mountain2").valid_resources_names)
+   assert_equal("fish", egbase:get_terrain_description("winter_water").valid_resources_names[1])
+end
+
+function test_terrains_resource_descr:test_terrain_probability_to_grow()
+	-- Using comparisons in order to not run into trouble with floating point numbers
+   assert_true(egbase:get_terrain_description("wiese1"):probability_to_grow("alder_summer_sapling") < 0.6)
+   assert_true(egbase:get_terrain_description("wiese1"):probability_to_grow("alder_summer_sapling") > 0.4)
+   assert_true(egbase:get_terrain_description("wasteland_beach"):probability_to_grow("alder_summer_sapling") < 0.003)
+   assert_true(egbase:get_terrain_description("wasteland_beach"):probability_to_grow("alder_summer_sapling") > 0.002)
+   assert_true(egbase:get_terrain_description("desert_forested_mountain2"):probability_to_grow("alder_summer_sapling") < 0.662)
+   assert_true(egbase:get_terrain_description("desert_forested_mountain2"):probability_to_grow("alder_summer_sapling") > 0.660)
+   assert_true(egbase:get_terrain_description("winter_water"):probability_to_grow("alder_summer_sapling") < 0.000038)
+   assert_true(egbase:get_terrain_description("winter_water"):probability_to_grow("alder_summer_sapling") > 0.000037)
+end

=== modified file 'tribes/buildings/trainingsites/atlanteans/labyrinth/init.lua'
--- tribes/buildings/trainingsites/atlanteans/labyrinth/init.lua	2016-01-02 21:39:43 +0000
+++ tribes/buildings/trainingsites/atlanteans/labyrinth/init.lua	2016-01-26 07:30:52 +0000
@@ -34,8 +34,8 @@
    },
 
    aihints = {
-      prohibited_till=900,
-      forced_after=1500,
+      prohibited_till = 900,
+      forced_after = 1500,
       trainingsite_type = "basic",
       very_weak_ai_limit = 1,
       weak_ai_limit = 2

=== modified file 'world/immovables/trees/alder/init.lua'
--- world/immovables/trees/alder/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/alder/init.lua	2016-01-26 07:30:52 +0000
@@ -87,6 +87,7 @@
 world:new_immovable_type{
    name = "alder_summer_old",
    descname = _ "Alder (Old)",
+   basename = _ "Alder",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/aspen/init.lua'
--- world/immovables/trees/aspen/init.lua	2015-11-20 18:21:27 +0000
+++ world/immovables/trees/aspen/init.lua	2016-01-26 07:30:52 +0000
@@ -82,6 +82,7 @@
 world:new_immovable_type{
    name = "aspen_summer_old",
    descname = _ "Aspen (Old)",
+   basename = _ "Aspen",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/beech/init.lua'
--- world/immovables/trees/beech/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/beech/init.lua	2016-01-26 07:30:52 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "beech_summer_old",
    descname = _ "Beech (Old)",
+   basename = _ "Beech",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/birch/init.lua'
--- world/immovables/trees/birch/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/birch/init.lua	2016-01-26 07:30:52 +0000
@@ -82,6 +82,7 @@
 world:new_immovable_type{
    name = "birch_summer_old",
    descname = _ "Birch (Old)",
+   basename = _ "Birch",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/cirrus/init.lua'
--- world/immovables/trees/cirrus/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/cirrus/init.lua	2016-01-26 07:30:52 +0000
@@ -83,6 +83,8 @@
    name = "cirrus_wasteland_old",
    -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
    descname = _ "Cirrus Tree (Old)",
+   -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
+   basename = _ "Cirrus Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/larch/init.lua'
--- world/immovables/trees/larch/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/larch/init.lua	2016-01-26 07:30:52 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "larch_summer_old",
    descname = _ "Larch (Old)",
+   basename = _ "Larch",
    editor_category = "trees_coniferous",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/liana/init.lua'
--- world/immovables/trees/liana/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/liana/init.lua	2016-01-26 07:30:52 +0000
@@ -86,6 +86,8 @@
    name = "liana_wasteland_old",
    -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
    descname = _ "Liana Tree (Old)",
+   -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
+   basename = _ "Liana Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/maple/init.lua'
--- world/immovables/trees/maple/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/maple/init.lua	2016-01-26 07:30:52 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "maple_winter_old",
    descname = _ "Maple (Old)",
+   basename = _ "Maple",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/mushroom_dark/init.lua'
--- world/immovables/trees/mushroom_dark/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/mushroom_dark/init.lua	2016-01-26 07:30:52 +0000
@@ -83,6 +83,8 @@
    name = "mushroom_dark_wasteland_old",
    -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
    descname = _ "Dark Mushroom Tree (Old)",
+   -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
+   basename = _ "Dark Mushroom Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/mushroom_green/init.lua'
--- world/immovables/trees/mushroom_green/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/mushroom_green/init.lua	2016-01-26 07:30:52 +0000
@@ -83,6 +83,8 @@
    name = "mushroom_green_wasteland_old",
    -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
    descname = _ "Green Mushroom Tree (Old)",
+   -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
+   basename = _ "Green Mushroom Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/mushroom_red/init.lua'
--- world/immovables/trees/mushroom_red/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/mushroom_red/init.lua	2016-01-26 07:30:52 +0000
@@ -86,6 +86,8 @@
    name = "mushroom_red_wasteland_old",
    -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
    descname = _ "Red Mushroom Tree (Old)",
+   -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
+   basename = _ "Red Mushroom Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/oak/init.lua'
--- world/immovables/trees/oak/init.lua	2015-11-20 18:21:27 +0000
+++ world/immovables/trees/oak/init.lua	2016-01-26 07:30:52 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "oak_summer_old",
    descname = _ "Oak (Old)",
+   basename = _ "Oak",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/palm_borassus/init.lua'
--- world/immovables/trees/palm_borassus/init.lua	2016-01-21 10:39:57 +0000
+++ world/immovables/trees/palm_borassus/init.lua	2016-01-26 07:30:52 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "palm_borassus_desert_old",
    descname = _ "Borassus Palm (Old)",
+   basename = _ "Borassus Palm",
    editor_category = "trees_palm",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/palm_coconut/init.lua'
--- world/immovables/trees/palm_coconut/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/palm_coconut/init.lua	2016-01-26 07:30:52 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "palm_coconut_desert_old",
    descname = _ "Coconut Palm (Old)",
+   basename = _ "Coconut Palm",
    editor_category = "trees_palm",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/palm_date/init.lua'
--- world/immovables/trees/palm_date/init.lua	2016-01-22 08:01:22 +0000
+++ world/immovables/trees/palm_date/init.lua	2016-01-26 07:30:52 +0000
@@ -82,6 +82,7 @@
 world:new_immovable_type{
    name = "palm_date_desert_old",
    descname = _ "Date Palm (Old)",
+   basename = _ "Date Palm",
    editor_category = "trees_palm",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/palm_oil/init.lua'
--- world/immovables/trees/palm_oil/init.lua	2016-01-21 10:39:57 +0000
+++ world/immovables/trees/palm_oil/init.lua	2016-01-26 07:30:52 +0000
@@ -83,6 +83,7 @@
 world:new_immovable_type{
    name = "palm_oil_desert_old",
    descname = _ "Oil Palm (Old)",
+   basename = _ "Oil Palm",
    editor_category = "trees_palm",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/palm_roystonea/init.lua'
--- world/immovables/trees/palm_roystonea/init.lua	2016-01-21 10:39:57 +0000
+++ world/immovables/trees/palm_roystonea/init.lua	2016-01-26 07:30:52 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "palm_roystonea_desert_old",
    descname = _ "Roystonea regia Palm (Old)",
+   basename = _ "Roystonea regia Palm",
    editor_category = "trees_palm",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/rowan/init.lua'
--- world/immovables/trees/rowan/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/rowan/init.lua	2016-01-26 07:30:52 +0000
@@ -82,6 +82,7 @@
 world:new_immovable_type{
    name = "rowan_summer_old",
    descname = _ "Rowan (Old)",
+   basename = _ "Rowan",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/spruce/init.lua'
--- world/immovables/trees/spruce/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/spruce/init.lua	2016-01-26 07:30:52 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "spruce_summer_old",
    descname = _ "Spruce (Old)",
+   basename = _ "Spruce",
    editor_category = "trees_coniferous",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/twine/init.lua'
--- world/immovables/trees/twine/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/twine/init.lua	2016-01-26 07:30:52 +0000
@@ -83,6 +83,8 @@
    name = "twine_wasteland_old",
    -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
    descname = _ "Twine Tree (Old)",
+   -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
+   basename = _ "Twine Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/umbrella_green/init.lua'
--- world/immovables/trees/umbrella_green/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/umbrella_green/init.lua	2016-01-26 07:30:52 +0000
@@ -83,6 +83,8 @@
    name = "umbrella_green_wasteland_old",
    -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
    descname = _ "Green Umbrella Tree (Old)",
+   -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
+   basename = _ "Green Umbrella Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/immovables/trees/umbrella_red/init.lua'
--- world/immovables/trees/umbrella_red/init.lua	2015-11-03 18:18:27 +0000
+++ world/immovables/trees/umbrella_red/init.lua	2016-01-26 07:30:52 +0000
@@ -86,6 +86,8 @@
    name = "umbrella_red_wasteland_old",
    -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
    descname = _ "Red Umbrella Tree (Old)",
+   -- TRANSLATORS: This is a fictitious tree. Be creative if you want.
+   basename = _ "Red Umbrella Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'world/resources/init.lua'
--- world/resources/init.lua	2016-01-11 22:13:36 +0000
+++ world/resources/init.lua	2016-01-26 07:30:52 +0000
@@ -9,6 +9,8 @@
    max_amount = 20,
    -- A geologist can find it, otherwise false (see Fish)
    detectable = true,
+   -- This represents the resource in menus etc.
+   representative_image = pics_dir .. "coal4.png",
    -- Picture that is used to indicate the amount of resource on the map
    -- [5] means amount 0 to 5; next line means amount 6 to 10 and so on
    -- The picture with highest number is additionally used in ui
@@ -25,6 +27,7 @@
    descname = _ "Gold",
    max_amount = 20,
    detectable = true,
+   representative_image = pics_dir .. "gold4.png",
    editor_pictures = {
       [5] = pics_dir .. "gold1.png",
       [10] = pics_dir .. "gold2.png",
@@ -38,6 +41,7 @@
    descname = _ "Iron",
    max_amount = 20,
    detectable = true,
+   representative_image = pics_dir .. "iron4.png",
    editor_pictures = {
       [5] = pics_dir .. "iron1.png",
       [10] = pics_dir .. "iron2.png",
@@ -51,6 +55,7 @@
    descname = _ "Stones",
    max_amount = 20,
    detectable = true,
+   representative_image = pics_dir .. "stones4.png",
    editor_pictures = {
       [5] = pics_dir .. "stones1.png",
       [10] = pics_dir .. "stones2.png",
@@ -64,6 +69,7 @@
    descname = _ "Water",
    max_amount = 50,
    detectable = true,
+   representative_image = pics_dir .. "water4.png",
    editor_pictures = {
       [10] = pics_dir .."water1.png",
       [20] = pics_dir .."water2.png",
@@ -77,14 +83,11 @@
    descname = _ "Fish",
    max_amount = 20,
    detectable = false,
+   representative_image = pics_dir .. "fish.png",
    editor_pictures = {
       [5] = pics_dir .. "fish1.png",
       [10] = pics_dir .. "fish2.png",
       [15] = pics_dir .. "fish3.png",
       [1000] = pics_dir .. "fish4.png",
-      -- Clutch: The editor chooses the image with the highest number for the
-      -- UI. So we keep a nice picture for this purpose at the top of this
-      -- list.
-      [1001] = pics_dir .. "fish.png",
    }
 }


Follow ups