← 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:
  SirVer (sirver)
  kaputtnik (franku): functionality
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 subscribed to branch lp:~widelands-dev/widelands/editor_help.
=== added directory 'data/scripting/editor'
=== added file 'data/scripting/editor/format_editor.lua'
--- data/scripting/editor/format_editor.lua	1970-01-01 00:00:00 +0000
+++ data/scripting/editor/format_editor.lua	2016-02-12 11:47:09 +0000
@@ -0,0 +1,54 @@
+-- RST
+-- format_editor.lua
+-- ---------------
+
+-- Functions used in the ingame editor help windows for formatting the text and pictures.
+
+include "scripting/formatting.lua"
+
+-- RST
+-- .. function picture_li(imagepath, text)
+--
+--    Places a paragraph of text to the right of an image
+
+--    :arg imagepath: the full path to the image file
+--    :arg text: the text to be placed next to the image
+--
+--    :returns: the text wrapped in a paragraph and placed next to the image
+function picture_li(imagepath, text)
+   return "<rt image=" .. imagepath .. " image-align=left>"
+      .. p(text) .. "</rt>"
+end
+
+-- RST
+-- .. function spacer()
+--
+--    Adds a little space between two paragraphs
+--
+--    :returns: a small empty paragraph
+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 'data/scripting/editor/terrain_help.lua'
--- data/scripting/editor/terrain_help.lua	1970-01-01 00:00:00 +0000
+++ data/scripting/editor/terrain_help.lua	2016-02-12 11:47:09 +0000
@@ -0,0 +1,77 @@
+-- RST
+-- terrain_help.lua
+-- -------------
+
+
+-- This file returns a formatted entry for the terrain help in the editor.
+
+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
+      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, resource in pairs(valid_resources) do
+               result = result .. picture_li(
+                  resource.representative_image, resource.descname)
+            end
+         end
+
+         local default_resource = terrain.default_resource
+         if (default_resource ~= nil) then
+            -- TRANSLATORS: e.g. "5x Water"
+            result = result .. text_line(_"Default:", _"%1%x %2%":bformat(
+               terrain.default_resource_amount, default_resource.descname))
+         end
+      end
+
+      -- Trees
+      local tree_list = {}
+      for i, immovable_name in ipairs(world.immovable_descriptions) do
+         local immovable = wl.Editor():get_immovable_description(immovable_name)
+         if (immovable:has_attribute("tree")) then
+            local probability = immovable:probability_to_grow(terrain.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 = immovable, probability = probability}
+            end
+         end
+      end
+
+      local tree_string = ""
+      for k,v in ipairs(tree_list) do
+         tree_string = tree_string .. picture_li(v.tree.representative_image,
+            v.tree.species .. ("<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 'data/scripting/editor/tree_help.lua'
--- data/scripting/editor/tree_help.lua	1970-01-01 00:00:00 +0000
+++ data/scripting/editor/tree_help.lua	2016-02-12 11:47:09 +0000
@@ -0,0 +1,45 @@
+-- RST
+-- tree_help.lua
+-- -------------
+
+-- This file returns a formatted entry for the tree help in the editor.
+
+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 probability = tree:probability_to_grow(terrain_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 'data/scripting/lunit.lua'
--- data/scripting/lunit.lua	2015-10-31 11:59:04 +0000
+++ data/scripting/lunit.lua	2016-02-12 11:47:09 +0000
@@ -110,6 +110,17 @@
   return actual
 end
 
+-- For testing float numbers - they can turn out to be a bit different from system
+-- to system, so we need a tolerance range.
+function assert_near(expected, actual, tolerance, msg)
+  lunit_stats_inc("assertions")
+  lunit_check_msg("assert_equal", msg)
+  lunit_do_assert(expected + tolerance >= actual, "expected '"..tostring(expected).."' with a tolerance of '"..tostring(tolerance).."' but was '"..tostring(actual).."'", msg)
+  lunit_do_assert(expected - tolerance <= actual, "expected '"..tostring(expected).."' with a tolerance of '"..tostring(tolerance).."' but was '"..tostring(actual).."'", msg)
+  return actual
+end
+
+
 function assert_match(pattern, actual, msg)
   lunit_stats_inc("assertions")
   lunit_check_msg("assert_match", msg)

=== modified file 'data/tribes/buildings/trainingsites/atlanteans/labyrinth/init.lua'
--- data/tribes/buildings/trainingsites/atlanteans/labyrinth/init.lua	2016-01-02 21:39:43 +0000
+++ data/tribes/buildings/trainingsites/atlanteans/labyrinth/init.lua	2016-02-12 11:47:09 +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 'data/txts/developers.lua'
--- data/txts/developers.lua	2016-01-28 06:19:02 +0000
+++ data/txts/developers.lua	2016-02-12 11:47:09 +0000
@@ -1,3 +1,3 @@
 -- Do not edit this file - it is automatically generated
 -- by utils/update_authors.py from developers.json.
-function developers() return {{heading = _"Chieftain",image = "images/players/genstats_enable_plr_01.png",entries = {{members = {"Holger Rapp (SirVer)",},},},},{heading = _"Elders",image = "images/players/genstats_enable_plr_04.png",entries = {{subheading = _"Graphics",members = {"Chuck Wilder (chuckw)",},},{subheading = _"Homepage",members = {"Markus Pfitzner (janus)",},},{subheading = _"Sound",members = {"Jan Bruns (solatis)",},},{subheading = _"Tongues",members = {"GunChleoc",},},},},{heading = _"Coders",image = "images/wui/stats/genstats_nrwares.png",entries = {{members = {"Holger Rapp (SirVer)","Nicolai Hähnle (ixprefect)","Florian Bluemel","Florian Falkner (foldrian)","Florian Weber (Bedouin)","Philipp Engelhard","Stefan Boettner","Tron","Martin Quinson","Raul Ferriz","Willem Jan Palenstijn","Josef Spillner","Christof Petig","Erik Sigra (sigra)","Nanne Wams","Surgery","Andrius R. (knutux)","Jari Hautio (jarih)","Peter Schwanemann (Nasenbaer)","Victor Pelt (Dwarik)","Axel Gehlert (dunkelbrauer)","András Eisenberger (Kiscsirke)","Andi","Timo","Hannes","TimoW","Jens Beyer (Qcumber-some)","Andreas Breitschopp (ab-tools)","Joachim Breitner (nomeata)","Nizamov Shawkat","Carl-Philip Hänsch (carli)","Martin Prussak (martin)","David Allwicher (aber)","Nathan Peters (nathanpeters)","Leif Sandstede (lcsand)","Matthias Horne (shevonar)","Borim (borim)","Angelo Locritani (alocritani)","Gabriel Margiani (gamag)","Anthony J. Bentley (anthonyjbentley)","Peter Waller (iri)","Johannes Ebke (sirius-in4matiker)","Andreas Eriksson","Mark Scott","Teppo Mäenpää","Steven De Herdt","Charly Ghislain (cghislai)","Tino Miegel (TinoM)","Tibor Bamhor (tiborb95)","GunChleoc","Ferdinand Thiessen (f-thiessen)","Willy Scheibel (willyscheibel)","Martin Schmidt (mars)","Simon Eilting","Alexander Kartzow (daAlx1)","Łukasz Majcher","Paul Mehrer (meitis)","Miroslav Remák (MiroslavR)","Janosch Peters",},},},},{heading = _"Graphicians",image = "images/wui/stats/genstats_nrbuildings.png",entries = {{members = {"Albert Jasiowka","Holger Rapp (SirVer)","Marz","Philipp Engelhard","Yannick Warnier","Delia","Florian Neuerburg","Jerome Rosinski","Mats Olsson","Odin Omdal","Wolfgang Weidner","Andreas Baier","Juri Chomé","Toralf Bethke (bithunter32)","Peter Schwanemann (Nasenbaer)","Alexia Death","Repsa Jih","Geoffroy Schmitlin (Trimard)","Benedikt Freisen (Objpaswriter)","Stefano Guidoni (Ilguido)","Samith Sandanayake (samithdisal)","Chuck Wilder (chuckw)","Astuur","Gerrit Familiegrosskopf (kingcreole)","Florian Angermeier (fraang)",},},},},{heading = _"Musicians",image = "images/wui/overlays/workarea123.png",entries = {{members = {"Kristian","MiddleFinger","Valerio Orlandini (Symbiosis)","Barry van Oudtshoorn (barryvan)","Jan Bruns (Solatis)","Nikola Whallon (Saturn)","Joshua O'Leary (Joshun)",},},},},{heading = _"Sound Effects",image = "images/wui/overlays/workarea123.png",entries = {{members = {"Stefan de Konik","Peter Schwanemann (Nasenbaer)","Stephan","Adam Piggott (_aD)","Stanisław Gackowski (Soeb)",},},},},{heading = _"Maps and Missions",image = "images/wui/stats/genstats_landsize.png",entries = {{members = {"Michal Szopa (Winterwind)","Jan-Henrik Kluth (Isch)","Peter Schwanemann (Nasenbaer)","Sven (deviant)","Tuxlands","Kamil Wilczek (Another Barbarian)","Tarvo Reim (Tarrei)","Manuel Holzmeier (Quappo)","ivh","Hanna Podewski (kristin)","Teppo Mäenpää","fk","Einstein13","Jenia","Robnick","wl-zocker",},},},},{heading = _"Campaign Story",image = "images/ui_basic/ls_wlmap.png",entries = {{subheading = _"Barbarian",members = {"Bastian Rapp","Alexander Kahl (wolfpac)","Peter Schwanemann (Nasenbaer)",},},{subheading = _"Empire",members = {"Peter Schwanemann (Nasenbaer)",},},{subheading = _"Atlantean",members = {"Holger Rapp (SirVer)","Peter Schwanemann (Nasenbaer)",},},},},{heading = _"Translators",image = "images/wui/editor/fsel_editor_set_height.png",entries = {{subheading = "العربية (Arabic)",members = {"abdXelrhman","m-abudrais","someone",},},{subheading = "Asturianu (Asturian)",members = {"Xuacu Saturio",},},{subheading = "Български (Bulgarian)",members = {"А. Ташев","Любомир Василев",},},{subheading = "Català (Catalan)",members = {"Guybrush88","Joan Josep","Oriol",},},{subheading = "Čeština (Czech)",members = {"Adam Matoušek","David Spanel","Jens Beyer","Jezevec","Jiří Locker","Konki","Marek Donar (Markus7cz)","Martin Volf","Martin Vecera (Marvec)","MaSo_CZ","Matej Svrcek (prometheus)","Milan Fašina","prom","Vit Hrachovy","Zbyněk Schwarz",},},{subheading = "Dansk (Danish)",members = {"Ask Hjorth Larsen","beer","Daniel Ejsing-Duun","David Lamhauge","Erik Soe Sorensen","Esben Aaberg","hulagutten","Joe Hansen (joedalton)","larsch","Nikolaj Sejergaard","silentStatic","Simon Stubben","Ville Witt",},},{subheading = "Deutsch (German)",members = {"Andreas Breitschopp","Astuur","Benedikt Tröster","Bob Johns","Borim","Clemens Dinkel","Daniel Kutrowatz","Daniel Winzen","Das MC","David Allwicher","DelphiMarkus","Dirk Stöcker","Fenris Wolf","Ferdinand T.","FetteNase","Flames_in_Paradise","fraang","Frank Kubitschek","Gabriel Margiani","Hagen","Hanna Podewski (kristin)","herbert","hurz","Johannes (nuefke)","Johannes Haupt","Kaste","Klappstuhl","Koneu","kraileth","LAZA","LennStar","Macedon","Marc Wischnowsky","Markus Pfitzner (janus)","Martin","Matthias Krüger","Max","meru","Mirian Margiani","MirkoWodtke","Mister Pi","Mr. Anderson","Ole","Peter Schwanemann (Nasenbaer)","Philipp Niemann (Azagtoth)","Provetin","Ralf-J. Block","raymond","ronny","Shevonar","SirVer","Sonnrain","Thomas","Tim O.","Timowi","Tino Miegel (TinoM)","Tobias Margiani","Venatrix","wl-zocker","Wolfgang Kurz","Wolfs","Wuzzy",},},{subheading = "Ελληνικά (Greek)",members = {"ptr","Γιάννης Ανθυμίδης",},},{subheading = "Canadian English",members = {"Ne-1",},},{subheading = "British English",members = {"_aD","Alex Denvir","Andi Chandler","Anthony Harrington","Biffaboy","Heber","Jackson Doak","James Thorrold","Jon Senior","LiSrt","Luis Miguel D.P.","mrx5682","Terry Jones","Tinker","UndiFineD","Vladimir Oka",},},{subheading = "US American English",members = {"DragonAtma",},},{subheading = "Esperanto",members = {"alms21","Fenris Wolf","Ivan Camilo Quintero Santacruz","Jens Beyer","Kristjan SCHMIDT","LaPingvino","Manuel Berkemeier","Michael Moroni",},},{subheading = "Español (Spanish)",members = {"Adolfo Jayme","Agustín Vela","Alberto D.V.","Alejandro Pérez","Antonio Trueba (Fasser)","David Mitos","David Pérez","DiegoJ","Dishito","Eduardo Alberto Calvo","Gerardb","ironfisher","Ivan","Ivan Camilo Quintero Santacruz","Javi Sol","Jonay","Joseph Molina","JoseRoberto","Juan Eduardo Riva","Kiibakun","Luis Miguel D.P.","MadkaT","Martín V.","Miguel adre","Miguel de Dios","Monkey","Pablo Frigerio","Paco Molinero","Paulomorales","pescamillam","Rafael Augusto Maguiña Yrivarren","Rafael Medina","Raul Ferriz","Roberto López","schimmm","simon","Siz","WalterCool","zer berros",},},{subheading = "Eesti keel (Estonian)",members = {"gert7","rm87",},},{subheading = "Euskara (Basque)",members = {"Mikel Alzibar",},},{subheading = "فارسی (Persian)",members = {"katy Zahedi",},},{subheading = "Suomi (Finnish)",members = {"Jari Hautio","Juhani Numminen","Markus Hällfors","Pekka Järvinen (Raspi)","Sampo Harjula","Sini Ruohomaa (Byakushin)","Teppo Mäenpää","Tommi Nirha","Vazde",},},{subheading = "Français (French)",members = {"AGuechoum","AnubiS","Audiger Jeremy","Aurelien Pavel","Benjamin Subtil","Bertram","bouchard renaud","Bruno Veilleux","clark17","crep4ever","David .","El Pensador","Eliovir","Emmanuel Andry (Eandry)","fk","François Rousselet","Gilles Aubert","Guillaume Brant","Guybrush88","Immunoman","Jean-Pierre Gemble","Hanna Podewski (kristin)","londumas","Michael Colignon","Michael DOUBEZ","Mohamed SEDKI","NonoSan","Pierre Rudloff","Sébastien Duthil","Sevy Ride","Tarou","Thomas Jungers","tomtom","Tubuntu","Ubuntu1988","verdy_p","wl-zocker","YS1","Yves MATHIEU",},},{subheading = "Gàidhlig (Scottish Gaelic)",members = {"GunChleoc",},},{subheading = "Galego (Galician)",members = {"Adrián Chaves Fernández","Antonio Trueba (Fasser)","Xosé",},},{subheading = "עברית (Hebrew)",members = {"Danny Albocher","Liel Fridman","Michael DOUBEZ","Solomon Gruber (Piql7)","Yaron",},},{subheading = "हिन्दी (Hindi)",members = {"girdhari rao",},},{subheading = "Hrvatski (Croatian)",members = {"Mario Dautović",},},{subheading = "Magyar (Hungarian)",members = {"cn4ij","Dániel Varga (EuroF)","Ferenc Nagy","Gyönki Bendegúz","HUNStree","István Kiss","jzombi","Kiscsirke","Major Gabesz","Muszela Balázs","Papp Bence","Richard Somlói","Robert Roth","SanskritFritz","Szűcs Kornél Géza",},},{subheading = "Interlingua",members = {"alms21",},},{subheading = "Bahasa Indonesia (Indonesian)",members = {"dadanhrn",},},{subheading = "Italiano (Italian)",members = {"Angelo Locritani","Colin Gibson","DarkSaivor","Davidus","Dom De Felice","Doukas7","Eulogy","Gabriel Rota","Guybrush88","ido","Loris Turchetti","Oibaf","Pierpaolo Pierozzi","pierusch","Pietro Battiston","Roberto Sciascia","Sergio Spinatelli","sgargel","simone.sandri",},},{subheading = "日本語 (Japanese)",members = {"alms21","Dios","guess880","Midori","SevyRide","tubame",},},{subheading = "Basa jawa (Javanese)",members = {"zaenal arifin",},},{subheading = "ქართული (Georgian)",members = {"Gabriel Margiani","Meyer Konrad",},},{subheading = "한국어 (Korean)",members = {"ddfddf2k",},},{subheading = "Lingua latīna (Latin)",members = {"alms21","lopho","Sonnrain","Stephan Lenk","Thorsten",},},{subheading = "Lietuvių (Lithuanian)",members = {"Mantas Kriaučiūnas",},},{subheading = "मराठी (Marathi)",members = {"Amod Ajit Karmarkar",},},{subheading = "بهاس ملايو (Malay)",members = {"abuyop",},},{subheading = "မြန်မာစ (Burmese)",members = {"pyaehtetaung",},},{subheading = "Norsk (Bokmål) (Norwegian Bokmål)",members = {"Fredrik Sudmann","Hans Joachim Desserud","Magnus Meyer Hustveit","Martin Dahl Moe","mr.x","Thorbjørn Bruarøy",},},{subheading = "Plattdütsch (Low German)",members = {"Mister Pi ","Nasenbaer ","Ole ","tando",},},{subheading = "Nederlands (Dutch)",members = {"BenW","Christian Groenendijk","Dirk Schut","fireprog","fk","Foppe Benedictus","Johan Jonkman (Dikjuh)","Maasieboy","Marcel","megabyte","Patrick van der Leer","Pieter Ouwerkerk (Pietertje)","Pietertje","PliniusNeo","REAL NAME","RickvanderZwet","Rob Snelders (Ertai)","Teun Spaans","Victor Pelt","Wim Champagne",},},{subheading = "Nynorsk (Norwegian Nynorsk)",members = {"Alexander Mackinnon Jansen","Hans Joachim Desserud","Odin Hørthe Omdal","Thorbjørn Bruarøy",},},{subheading = "Occitan",members = {"Cédric VALMARY (Tot en òc)",},},{subheading = "Polski (Polish)",members = {"Albert Einstein","Andrzej Krentosz (Endrju)","Asahi Koishi","BartekChom","Bartosz Wiśniewski","Gabriel Fortin","Hubert Pluta","Januzi (januzi)","Jacek Wolszczak (Shutdownrunner)","Jens Beyer","Karol Sobolewski","Łukasz Chełmicki","Mateusz Micał","Michal Maslanko","Michał Rzepiński","orzeh","Patryk Sawicki","Pawel PErz","Stanisław Gackowski (Soeb)","Szymon Fornal","Szymon Gackowski","Szymon Nieznański","tim","Tomasz Sterna","Wesmania","Wojtek","XeonBloomfield",},},{subheading = "Português (Portuguese)",members = {"Almufadado","daniel reis","David Rodrigues","Flávio J. Saraiva","GunChleoc","Marcelo do Pagode","Miguel de Freitas Fonseca","Tiago Silva","trewe",},},{subheading = "Português do Brasil (Brazilian Portuguese)",members = {"Alexandre","alms21","Almufadado","Cleverton","daniel reis","Fabio Garz","Flaviano Angeli","HicHic","Hriostat","Israel","JoãoPedro BrasãoToledo","Juarez S.","Júlio Cezar Santos Pires","Juno","Luiz N","Maraschin","Marcelo do Pagode","Nicolas Abril","Pedro Pisandelli","Proezas","Rafael Neri","Rayback","Rubens Bueno","Samer Ghosnlas.2932","Tomas Abril","Vitor",},},{subheading = "Română (Romanian)",members = {"Ursachi Alexandru",},},{subheading = "Русский (Russian)",members = {"Александр","Александр Бикмеев (Rombal)","Александр Глухов","Алексей Кабанов","Андрей Кулаков ","Андрей Олыкайнен ","Антон Хабаров (lu)","Виктор Биркманис","Владимир Коваленко","Глеб Синковский","Глория Хрусталёва","Денис Дерябин","Егор Панфилов","Константин Щукин","Никита Шехов","Руслан Ковтун","Сергей Фуканчик ","Юрий Соколов (Urra)","CupIvan","Georgiy","gerich","Izon","KroArtem","Lex","Massol","Papazu","SashaQR","TroubleMakerDV","Vampire Hunter D","Vlad",},},{subheading = "Kinyarwanda",members = {"Nasenbaer",},},{subheading = "සිංහල (Sinhala)",members = {"Samith Sandanayake",},},{subheading = "Slovenčina (Slovak)",members = {"Kefir111","Marek Hám","Miroslav Remák","Vladimir","Vladímir Tóth (Ike)",},},{subheading = "Slovenski jezik (Slovenian)",members = {"Andrej Znidarsic","Boštjan Miklavčič","Jure Repinc","kleb","Klemen Košir","Matevž Jekovec","Matic Gradišer","mrt",},},{subheading = "српски (Serbian)",members = {"Никола Павловић",},},{subheading = "Svenska (Swedish)",members = {"Arve Eriksson","Christian Widell","Daniel Nylander (yeager)","Frederik Pettersson (luno)","ivh","Joakim Lundborg","karlrune","Marcus E","Michael Rydén","Patrick H.","Phoenix","Rasmus Olstedt","RasmusBackman","Sigra","Treecko","Tumaini","Ulite",},},{subheading = "Türkçe (Turkish)",members = {"Asiye","Ekrem Kocadere","Ercin Senturk","Recep Hasanbaş","ScriptMonster","Volkan Gezer",},},{subheading = "українська мова (Ukranian)",members = {"Fedik","Shemet Yevhene","Сергій Дубик",},},{subheading = "Tiếng Việt (Vietnamese)",members = {"Nguyen Quang Chien",},},{subheading = "简体中文 (Simplified Chinese)",members = {"luojie-dune","XIA",},},{subheading = "繁體中文 (Traditional Chinese)",members = {"AJ","poormusic","sonny",},},},},{heading = _"Packagers",image = "images/wui/stats/genstats_productivity.png",entries = {{subheading = _"Debian Linux",members = {"Martin Quinson",},},{subheading = _"Fedora Linux",members = {"Karol Trzcionka","Jochen Wiedmann",},},{subheading = _"Mandriva Linux",members = {"Emmanuel Andry (eandry)",},},{subheading = _"FreeBSD",members = {"Bartosz Fabianowski",},},{subheading = _"Mac OS X",members = {"Philipp Engelhard","Pierre Salagnac (Tarou)","Wolf St. Kappesser","David Allwicher (aber)",},},{subheading = _"Windows",members = {"Tino Miegel (TinoM)","Alexander Kahl (Wolfpac)","Geodomus","Jari Hautio",},},{subheading = _"ZetaOS",members = {"BeSman",},},},},{heading = _"Homepage",image = "images/logos/WL-Editor-16.png",entries = {{subheading = _"Homepage Coders",members = {"Holger Rapp (SirVer)","Stanislaw Gackowski (Soeb)","Markus Pfitzner (janus)","Tobi",},},{subheading = _"Documentation, Help and Wiki",members = {"Erik Sigra (sigra)","Florian Falkner (foldrian)","Florian Weber (bedouin)","Nicolai Haehnle","Holger Rapp (SirVer)","Johannes (nuefke)","Alexander Kahl (wolfpac)","Stanislaw Gackowski (Soeb)","Hanna Podewski (kristin)",},},},},{heading = _"Former Elders",image = "images/players/genstats_enable_plr_04.png",entries = {{subheading = _"Graphics",members = {"Salamander","Alexia Death (death)",},},{subheading = _"Homepage",members = {"holymoly","Stuart Eglington (DaaL1973)","Jon Harris (jonsjava)",},},{subheading = _"Sound",members = {"Yannick Warnier",},},{subheading = _"Translation",members = {"Peter Schwanemann (Nasenbaer)","Philipp Niemann (Azagtoth)",},},},},{heading = _"Other",image = "images/wui/stats/genstats_landsize.png",entries = {{members = {"Matt Howe (mdhowe)","Samuel Tilly (eldamar)","and many, many more (thank you for everything you've done)",},},},},} end
\ No newline at end of file
+function developers() return {{heading = _"Chieftain",image = "images/players/genstats_enable_plr_01.png",entries = {{members = {"Holger Rapp (SirVer)",},},},},{heading = _"Elders",image = "images/players/genstats_enable_plr_04.png",entries = {{subheading = _"Graphics",members = {"Chuck Wilder (chuckw)",},},{subheading = _"Homepage",members = {"Markus Pfitzner (janus)",},},{subheading = _"Sound",members = {"Jan Bruns (solatis)",},},{subheading = _"Tongues",members = {"GunChleoc",},},},},{heading = _"Coders",image = "images/wui/stats/genstats_nrwares.png",entries = {{members = {"Holger Rapp (SirVer)","Nicolai Hähnle (ixprefect)","Florian Bluemel","Florian Falkner (foldrian)","Florian Weber (Bedouin)","Philipp Engelhard","Stefan Boettner","Tron","Martin Quinson","Raul Ferriz","Willem Jan Palenstijn","Josef Spillner","Christof Petig","Erik Sigra (sigra)","Nanne Wams","Surgery","Andrius R. (knutux)","Jari Hautio (jarih)","Peter Schwanemann (Nasenbaer)","Victor Pelt (Dwarik)","Axel Gehlert (dunkelbrauer)","András Eisenberger (Kiscsirke)","Andi","Timo","Hannes","TimoW","Jens Beyer (Qcumber-some)","Andreas Breitschopp (ab-tools)","Joachim Breitner (nomeata)","Nizamov Shawkat","Carl-Philip Hänsch (carli)","Martin Prussak (martin)","David Allwicher (aber)","Nathan Peters (nathanpeters)","Leif Sandstede (lcsand)","Matthias Horne (shevonar)","Borim (borim)","Angelo Locritani (alocritani)","Gabriel Margiani (gamag)","Anthony J. Bentley (anthonyjbentley)","Peter Waller (iri)","Johannes Ebke (sirius-in4matiker)","Andreas Eriksson","Mark Scott","Teppo Mäenpää","Steven De Herdt","Charly Ghislain (cghislai)","Tino Miegel (TinoM)","Tibor Bamhor (tiborb95)","GunChleoc","Ferdinand Thiessen (f-thiessen)","Willy Scheibel (willyscheibel)","Martin Schmidt (mars)","Simon Eilting","Alexander Kartzow (daAlx1)","Łukasz Majcher","Paul Mehrer (meitis)","Miroslav Remák (MiroslavR)","Janosch Peters",},},},},{heading = _"Graphicians",image = "images/wui/stats/genstats_nrbuildings.png",entries = {{members = {"Albert Jasiowka","Holger Rapp (SirVer)","Marz","Philipp Engelhard","Yannick Warnier","Delia","Florian Neuerburg","Jerome Rosinski","Mats Olsson","Odin Omdal","Wolfgang Weidner","Andreas Baier","Juri Chomé","Toralf Bethke (bithunter32)","Peter Schwanemann (Nasenbaer)","Alexia Death","Repsa Jih","Geoffroy Schmitlin (Trimard)","Benedikt Freisen (Objpaswriter)","Stefano Guidoni (Ilguido)","Samith Sandanayake (samithdisal)","Chuck Wilder (chuckw)","Astuur","Gerrit Familiegrosskopf (kingcreole)","Florian Angermeier (fraang)",},},},},{heading = _"Musicians",image = "images/wui/overlays/workarea123.png",entries = {{members = {"Kristian","MiddleFinger","Valerio Orlandini (Symbiosis)","Barry van Oudtshoorn (barryvan)","Jan Bruns (Solatis)","Nikola Whallon (Saturn)","Joshua O'Leary (Joshun)",},},},},{heading = _"Sound Effects",image = "images/wui/overlays/workarea123.png",entries = {{members = {"Stefan de Konik","Peter Schwanemann (Nasenbaer)","Stephan","Adam Piggott (_aD)","Stanisław Gackowski (Soeb)",},},},},{heading = _"Maps and Missions",image = "images/wui/stats/genstats_landsize.png",entries = {{members = {"Michal Szopa (Winterwind)","Jan-Henrik Kluth (Isch)","Peter Schwanemann (Nasenbaer)","Sven (deviant)","Tuxlands","Kamil Wilczek (Another Barbarian)","Tarvo Reim (Tarrei)","Manuel Holzmeier (Quappo)","ivh","Hanna Podewski (kristin)","Teppo Mäenpää","fk","Einstein13","Jenia","Robnick","wl-zocker",},},},},{heading = _"Campaign Story",image = "images/ui_basic/ls_wlmap.png",entries = {{subheading = _"Barbarian",members = {"Bastian Rapp","Alexander Kahl (wolfpac)","Peter Schwanemann (Nasenbaer)",},},{subheading = _"Empire",members = {"Peter Schwanemann (Nasenbaer)",},},{subheading = _"Atlantean",members = {"Holger Rapp (SirVer)","Peter Schwanemann (Nasenbaer)",},},},},{heading = _"Translators",image = "images/wui/editor/fsel_editor_set_height.png",entries = {{subheading = "العربية (Arabic)",members = {"abdXelrhman","m-abudrais","someone",},},{subheading = "Asturianu (Asturian)",members = {"Xuacu Saturio",},},{subheading = "Български (Bulgarian)",members = {"А. Ташев","Любомир Василев",},},{subheading = "Català (Catalan)",members = {"Guybrush88","Joan Josep","Oriol",},},{subheading = "Čeština (Czech)",members = {"Adam Matoušek","David Spanel","Jens Beyer","Jezevec","Jiří Locker","Konki","Marek Donar (Markus7cz)","Martin Volf","Martin Vecera (Marvec)","MaSo_CZ","Matej Svrcek (prometheus)","Milan Fašina","prom","Vit Hrachovy","Zbyněk Schwarz",},},{subheading = "Dansk (Danish)",members = {"Ask Hjorth Larsen","beer","Daniel Ejsing-Duun","David Lamhauge","Erik Soe Sorensen","Esben Aaberg","hulagutten","Joe Hansen (joedalton)","larsch","Nikolaj Sejergaard","silentStatic","Simon Stubben","Ville Witt",},},{subheading = "Deutsch (German)",members = {"Andreas Breitschopp","Astuur","Benedikt Tröster","Bob Johns","Borim","Clemens Dinkel","Daniel Kutrowatz","Daniel Winzen","Das MC","David Allwicher","DelphiMarkus","Dirk Stöcker","Fenris Wolf","Ferdinand T.","FetteNase","Flames_in_Paradise","fraang","Frank Kubitschek","Gabriel Margiani","Hagen","Hanna Podewski (kristin)","herbert","hurz","Johannes (nuefke)","Johannes Haupt","Kaste","Klappstuhl","Koneu","kraileth","LAZA","LennStar","Macedon","Marc Wischnowsky","Markus Pfitzner (janus)","Martin","Matthias Krüger","Max","meru","Mirian Margiani","MirkoWodtke","Mister Pi","Mr. Anderson","Ole","Peter Schwanemann (Nasenbaer)","Philipp Niemann (Azagtoth)","Provetin","Ralf-J. Block","raymond","ronny","Shevonar","SirVer","Sonnrain","Thomas","Tim O.","Timowi","Tino Miegel (TinoM)","Tobias Margiani","Venatrix","wl-zocker","Wolfgang Kurz","Wolfs","Wuzzy",},},{subheading = "Ελληνικά (Greek)",members = {"ptr","Γιάννης Ανθυμίδης",},},{subheading = "Canadian English",members = {"Ne-1",},},{subheading = "British English",members = {"_aD","Alex Denvir","Andi Chandler","Anthony Harrington","Biffaboy","Heber","Jackson Doak","James Thorrold","Jon Senior","LiSrt","Luis Miguel D.P.","mrx5682","Terry Jones","Tinker","UndiFineD","Vladimir Oka",},},{subheading = "US American English",members = {"DragonAtma",},},{subheading = "Esperanto",members = {"alms21","Fenris Wolf","Ivan Camilo Quintero Santacruz","Jens Beyer","Kristjan SCHMIDT","LaPingvino","Manuel Berkemeier","Michael Moroni",},},{subheading = "Español (Spanish)",members = {"Adolfo Jayme","Agustín Vela","Alberto D.V.","Alejandro Pérez","Antonio Trueba (Fasser)","David Mitos","David Pérez","DiegoJ","Dishito","Eduardo Alberto Calvo","Gerardb","ironfisher","Ivan","Ivan Camilo Quintero Santacruz","Javi Sol","Jonay","Joseph Molina","JoseRoberto","Juan Eduardo Riva","Kiibakun","Luis Miguel D.P.","MadkaT","Martín V.","Miguel adre","Miguel de Dios","Monkey","Pablo Frigerio","Paco Molinero","Paulomorales","pescamillam","Rafael Augusto Maguiña Yrivarren","Rafael Medina","Raul Ferriz","Roberto López","schimmm","simon","Siz","WalterCool","zer berros",},},{subheading = "Eesti keel (Estonian)",members = {"gert7","rm87",},},{subheading = "Euskara (Basque)",members = {"Mikel Alzibar",},},{subheading = "فارسی (Persian)",members = {"katy Zahedi",},},{subheading = "Suomi (Finnish)",members = {"Jari Hautio","Juhani Numminen","Markus Hällfors","Pekka Järvinen (Raspi)","Sampo Harjula","Sini Ruohomaa (Byakushin)","Teppo Mäenpää","Tommi Nirha","Vazde",},},{subheading = "Français (French)",members = {"AGuechoum","AnubiS","Audiger Jeremy","Aurelien Pavel","Benjamin Subtil","Bertram","bouchard renaud","Bruno Veilleux","clark17","crep4ever","David .","El Pensador","Eliovir","Emmanuel Andry (Eandry)","fk","François Rousselet","Gilles Aubert","Guillaume Brant","Guybrush88","Immunoman","Jean-Pierre Gemble","Hanna Podewski (kristin)","londumas","Michael Colignon","Michael DOUBEZ","Mohamed SEDKI","NonoSan","Pierre Rudloff","Sébastien Duthil","Sevy Ride","Tarou","Thomas Jungers","tomtom","Tubuntu","Ubuntu1988","verdy_p","wl-zocker","YS1","Yves MATHIEU",},},{subheading = "Gàidhlig (Scottish Gaelic)",members = {"GunChleoc",},},{subheading = "Galego (Galician)",members = {"Adrián Chaves Fernández","Antonio Trueba (Fasser)","Xosé",},},{subheading = "עברית (Hebrew)",members = {"Danny Albocher","Liel Fridman","Michael DOUBEZ","Solomon Gruber (Piql7)","Yaron",},},{subheading = "हिन्दी (Hindi)",members = {"girdhari rao",},},{subheading = "Hrvatski (Croatian)",members = {"Mario Dautović",},},{subheading = "Magyar (Hungarian)",members = {"cn4ij","Dániel Varga (EuroF)","Ferenc Nagy","Gyönki Bendegúz","HUNStree","István Kiss","jzombi","Kiscsirke","Major Gabesz","Muszela Balázs","Papp Bence","Richard Somlói","Robert Roth","SanskritFritz","Szűcs Kornél Géza",},},{subheading = "Interlingua",members = {"alms21",},},{subheading = "Bahasa Indonesia (Indonesian)",members = {"dadanhrn",},},{subheading = "Italiano (Italian)",members = {"Angelo Locritani","Colin Gibson","DarkSaivor","Davidus","Dom De Felice","Doukas7","Eulogy","Gabriel Rota","Guybrush88","ido","Loris Turchetti","Oibaf","Pierpaolo Pierozzi","pierusch","Pietro Battiston","Roberto Sciascia","Sergio Spinatelli","sgargel","simone.sandri",},},{subheading = "日本語 (Japanese)",members = {"alms21","Dios","guess880","Midori","SevyRide","tubame",},},{subheading = "Basa jawa (Javanese)",members = {"zaenal arifin",},},{subheading = "ქართული (Georgian)",members = {"Gabriel Margiani","Meyer Konrad",},},{subheading = "한국어 (Korean)",members = {"ddfddf2k",},},{subheading = "Lingua latīna (Latin)",members = {"alms21","lopho","Sonnrain","Stephan Lenk","Thorsten",},},{subheading = "Lietuvių (Lithuanian)",members = {"Mantas Kriaučiūnas",},},{subheading = "मराठी (Marathi)",members = {"Amod Ajit Karmarkar",},},{subheading = "بهاس ملايو (Malay)",members = {"abuyop",},},{subheading = "မြန်မာစ (Burmese)",members = {"pyaehtetaung",},},{subheading = "Norsk (Bokmål) (Norwegian Bokmål)",members = {"Fredrik Sudmann","Hans Joachim Desserud","Magnus Meyer Hustveit","Martin Dahl Moe","mr.x","Thorbjørn Bruarøy",},},{subheading = "Plattdütsch (Low German)",members = {"Mister Pi ","Nasenbaer ","Ole ","tando",},},{subheading = "Nederlands (Dutch)",members = {"BenW","Christian Groenendijk","Dirk Schut","fireprog","fk","Foppe Benedictus","Johan Jonkman (Dikjuh)","Maasieboy","Marcel","megabyte","Patrick van der Leer","Pieter Ouwerkerk (Pietertje)","Pietertje","PliniusNeo","REAL NAME","RickvanderZwet","Rob Snelders (Ertai)","Teun Spaans","Victor Pelt","Wim Champagne",},},{subheading = "Nynorsk (Norwegian Nynorsk)",members = {"Alexander Mackinnon Jansen","Hans Joachim Desserud","Odin Hørthe Omdal","Thorbjørn Bruarøy",},},{subheading = "Occitan",members = {"Cédric VALMARY (Tot en òc)",},},{subheading = "Polski (Polish)",members = {"Albert Einstein","Andrzej Krentosz (Endrju)","Asahi Koishi","BartekChom","Bartosz Wiśniewski","Gabriel Fortin","Hubert Pluta","Januzi (januzi)","Jacek Wolszczak (Shutdownrunner)","Jens Beyer","Karol Sobolewski","Łukasz Chełmicki","Mateusz Micał","Michal Maslanko","Michał Rzepiński","orzeh","Patryk Sawicki","Pawel PErz","Stanisław Gackowski (Soeb)","Szymon Fornal","Szymon Gackowski","Szymon Nieznański","tim","Tomasz Sterna","Wesmania","Wojtek","XeonBloomfield",},},{subheading = "Português (Portuguese)",members = {"Almufadado","daniel reis","David Rodrigues","Flávio J. Saraiva","GunChleoc","Marcelo do Pagode","Miguel de Freitas Fonseca","Tiago Silva","trewe",},},{subheading = "Português do Brasil (Brazilian Portuguese)",members = {"Alexandre","alms21","Almufadado","Cleverton","daniel reis","Fabio Garz","Flaviano Angeli","HicHic","Hriostat","Israel","JoãoPedro BrasãoToledo","Juarez S.","Júlio Cezar Santos Pires","Juno","Luiz N","Maraschin","Marcelo do Pagode","Nicolas Abril","Pedro Pisandelli","Proezas","Rafael Neri","Rayback","Rubens Bueno","Samer Ghosnlas.2932","Tomas Abril","Vitor",},},{subheading = "Română (Romanian)",members = {"Ursachi Alexandru",},},{subheading = "Русский (Russian)",members = {"Александр","Александр Бикмеев (Rombal)","Александр Глухов","Алексей Кабанов","Андрей Кулаков ","Андрей Олыкайнен ","Антон Хабаров (lu)","Виктор Биркманис","Владимир Коваленко","Глеб Синковский","Глория Хрусталёва","Денис Дерябин","Егор Панфилов","Константин Щукин","Никита Шехов","Руслан Ковтун","Сергей Фуканчик ","Юрий Соколов (Urra)","CupIvan","Georgiy","gerich","Izon","KroArtem","Lex","Massol","Papazu","SashaQR","TroubleMakerDV","Vampire Hunter D","Vlad",},},{subheading = "Kinyarwanda",members = {"Nasenbaer",},},{subheading = "සිංහල (Sinhala)",members = {"Samith Sandanayake",},},{subheading = "Slovenčina (Slovak)",members = {"Kefir111","Marek Hám","Miroslav Remák","Vladimir","Vladímir Tóth (Ike)",},},{subheading = "Slovenski jezik (Slovenian)",members = {"Andrej Znidarsic","Boštjan Miklavčič","Jure Repinc","kleb","Klemen Košir","Matevž Jekovec","Matic Gradišer","mrt",},},{subheading = "српски (Serbian)",members = {"Никола Павловић",},},{subheading = "Svenska (Swedish)",members = {"Arve Eriksson","Christian Widell","Daniel Nylander (yeager)","Frederik Pettersson (luno)","ivh","Joakim Lundborg","karlrune","Marcus E","Michael Rydén","Patrick H.","Phoenix","Rasmus Olstedt","RasmusBackman","Sigra","Treecko","Tumaini","Ulite",},},{subheading = "Türkçe (Turkish)",members = {"Asiye","Ekrem Kocadere","Ercin Senturk","Recep Hasanbaş","ScriptMonster","Volkan Gezer",},},{subheading = "українська мова (Ukranian)",members = {"Fedik","Shemet Yevhene","Сергій Дубик",},},{subheading = "Tiếng Việt (Vietnamese)",members = {"Nguyen Quang Chien",},},{subheading = "简体中文 (Simplified Chinese)",members = {"luojie-dune","XIA",},},{subheading = "繁體中文 (Traditional Chinese)",members = {"AJ","poormusic","sonny",},},},},{heading = _"Packagers",image = "images/wui/stats/genstats_productivity.png",entries = {{subheading = _"Debian Linux",members = {"Martin Quinson",},},{subheading = _"Fedora Linux",members = {"Karol Trzcionka","Jochen Wiedmann",},},{subheading = _"Mandriva Linux",members = {"Emmanuel Andry (eandry)",},},{subheading = _"FreeBSD",members = {"Bartosz Fabianowski",},},{subheading = _"Mac OS X",members = {"Philipp Engelhard","Pierre Salagnac (Tarou)","Wolf St. Kappesser","David Allwicher (aber)",},},{subheading = _"Windows",members = {"Tino Miegel (TinoM)","Alexander Kahl (Wolfpac)","Geodomus","Jari Hautio",},},{subheading = _"ZetaOS",members = {"BeSman",},},},},{heading = _"Homepage",image = "images/logos/WL-Editor-16.png",entries = {{subheading = _"Homepage Coders",members = {"Holger Rapp (SirVer)","Stanislaw Gackowski (Soeb)","Markus Pfitzner (janus)","Tobi",},},{subheading = _"Documentation, Help and Wiki",members = {"Erik Sigra (sigra)","Florian Falkner (foldrian)","Florian Weber (bedouin)","Nicolai Haehnle","Holger Rapp (SirVer)","Johannes (nuefke)","Alexander Kahl (wolfpac)","Stanislaw Gackowski (Soeb)","Hanna Podewski (kristin)",},},},},{heading = _"Former Elders",image = "images/players/genstats_enable_plr_04.png",entries = {{subheading = _"Graphics",members = {"Salamander","Alexia Death (death)",},},{subheading = _"Homepage",members = {"holymoly","Stuart Eglington (DaaL1973)","Jon Harris (jonsjava)",},},{subheading = _"Sound",members = {"Yannick Warnier",},},{subheading = _"Translation",members = {"Peter Schwanemann (Nasenbaer)","Philipp Niemann (Azagtoth)",},},},},{heading = _"Other",image = "images/wui/stats/genstats_landsize.png",entries = {{members = {"Matt Howe (mdhowe)","Samuel Tilly (eldamar)","and many, many more (thank you for everything you've done)",},},},},} end

=== modified file 'data/world/immovables/trees/alder/init.lua'
--- data/world/immovables/trees/alder/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/alder/init.lua	2016-02-12 11:47:09 +0000
@@ -87,6 +87,7 @@
 world:new_immovable_type{
    name = "alder_summer_old",
    descname = _ "Alder (Old)",
+   species = _ "Alder",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/aspen/init.lua'
--- data/world/immovables/trees/aspen/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/aspen/init.lua	2016-02-12 11:47:09 +0000
@@ -82,6 +82,7 @@
 world:new_immovable_type{
    name = "aspen_summer_old",
    descname = _ "Aspen (Old)",
+   species = _ "Aspen",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/beech/init.lua'
--- data/world/immovables/trees/beech/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/beech/init.lua	2016-02-12 11:47:09 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "beech_summer_old",
    descname = _ "Beech (Old)",
+   species = _ "Beech",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/birch/init.lua'
--- data/world/immovables/trees/birch/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/birch/init.lua	2016-02-12 11:47:09 +0000
@@ -82,6 +82,7 @@
 world:new_immovable_type{
    name = "birch_summer_old",
    descname = _ "Birch (Old)",
+   species = _ "Birch",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/cirrus/init.lua'
--- data/world/immovables/trees/cirrus/init.lua	2015-11-03 18:18:27 +0000
+++ data/world/immovables/trees/cirrus/init.lua	2016-02-12 11:47:09 +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.
+   species = _ "Cirrus Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/larch/init.lua'
--- data/world/immovables/trees/larch/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/larch/init.lua	2016-02-12 11:47:09 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "larch_summer_old",
    descname = _ "Larch (Old)",
+   species = _ "Larch",
    editor_category = "trees_coniferous",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/liana/init.lua'
--- data/world/immovables/trees/liana/init.lua	2015-11-03 18:18:27 +0000
+++ data/world/immovables/trees/liana/init.lua	2016-02-12 11:47:09 +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.
+   species = _ "Liana Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/maple/init.lua'
--- data/world/immovables/trees/maple/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/maple/init.lua	2016-02-12 11:47:09 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "maple_winter_old",
    descname = _ "Maple (Old)",
+   species = _ "Maple",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/mushroom_dark/init.lua'
--- data/world/immovables/trees/mushroom_dark/init.lua	2015-11-03 18:18:27 +0000
+++ data/world/immovables/trees/mushroom_dark/init.lua	2016-02-12 11:47:09 +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.
+   species = _ "Dark Mushroom Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/mushroom_green/init.lua'
--- data/world/immovables/trees/mushroom_green/init.lua	2015-11-03 18:18:27 +0000
+++ data/world/immovables/trees/mushroom_green/init.lua	2016-02-12 11:47:09 +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.
+   species = _ "Green Mushroom Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/mushroom_red/init.lua'
--- data/world/immovables/trees/mushroom_red/init.lua	2015-11-03 18:18:27 +0000
+++ data/world/immovables/trees/mushroom_red/init.lua	2016-02-12 11:47:09 +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.
+   species = _ "Red Mushroom Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/oak/init.lua'
--- data/world/immovables/trees/oak/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/oak/init.lua	2016-02-12 11:47:09 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "oak_summer_old",
    descname = _ "Oak (Old)",
+   species = _ "Oak",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/palm_borassus/init.lua'
--- data/world/immovables/trees/palm_borassus/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/palm_borassus/init.lua	2016-02-12 11:47:09 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "palm_borassus_desert_old",
    descname = _ "Borassus Palm (Old)",
+   species = _ "Borassus Palm",
    editor_category = "trees_palm",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/palm_coconut/init.lua'
--- data/world/immovables/trees/palm_coconut/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/palm_coconut/init.lua	2016-02-12 11:47:09 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "palm_coconut_desert_old",
    descname = _ "Coconut Palm (Old)",
+   species = _ "Coconut Palm",
    editor_category = "trees_palm",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/palm_date/init.lua'
--- data/world/immovables/trees/palm_date/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/palm_date/init.lua	2016-02-12 11:47:09 +0000
@@ -82,6 +82,7 @@
 world:new_immovable_type{
    name = "palm_date_desert_old",
    descname = _ "Date Palm (Old)",
+   species = _ "Date Palm",
    editor_category = "trees_palm",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/palm_oil/init.lua'
--- data/world/immovables/trees/palm_oil/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/palm_oil/init.lua	2016-02-12 11:47:09 +0000
@@ -83,6 +83,7 @@
 world:new_immovable_type{
    name = "palm_oil_desert_old",
    descname = _ "Oil Palm (Old)",
+   species = _ "Oil Palm",
    editor_category = "trees_palm",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/palm_roystonea/init.lua'
--- data/world/immovables/trees/palm_roystonea/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/palm_roystonea/init.lua	2016-02-12 11:47:09 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "palm_roystonea_desert_old",
    descname = _ "Roystonea regia Palm (Old)",
+   species = _ "Roystonea regia Palm",
    editor_category = "trees_palm",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/rowan/init.lua'
--- data/world/immovables/trees/rowan/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/rowan/init.lua	2016-02-12 11:47:09 +0000
@@ -82,6 +82,7 @@
 world:new_immovable_type{
    name = "rowan_summer_old",
    descname = _ "Rowan (Old)",
+   species = _ "Rowan",
    editor_category = "trees_deciduous",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/spruce/init.lua'
--- data/world/immovables/trees/spruce/init.lua	2016-01-28 05:24:34 +0000
+++ data/world/immovables/trees/spruce/init.lua	2016-02-12 11:47:09 +0000
@@ -79,6 +79,7 @@
 world:new_immovable_type{
    name = "spruce_summer_old",
    descname = _ "Spruce (Old)",
+   species = _ "Spruce",
    editor_category = "trees_coniferous",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/twine/init.lua'
--- data/world/immovables/trees/twine/init.lua	2015-11-03 18:18:27 +0000
+++ data/world/immovables/trees/twine/init.lua	2016-02-12 11:47:09 +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.
+   species = _ "Twine Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/umbrella_green/init.lua'
--- data/world/immovables/trees/umbrella_green/init.lua	2015-11-03 18:18:27 +0000
+++ data/world/immovables/trees/umbrella_green/init.lua	2016-02-12 11:47:09 +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.
+   species = _ "Green Umbrella Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/immovables/trees/umbrella_red/init.lua'
--- data/world/immovables/trees/umbrella_red/init.lua	2015-11-03 18:18:27 +0000
+++ data/world/immovables/trees/umbrella_red/init.lua	2016-02-12 11:47:09 +0000
@@ -1,4 +1,4 @@
-dirname = path.dirname(__file__)
+sedirname = path.dirname(__file__)
 
 terrain_affinity = {
    preferred_temperature = 110,
@@ -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.
+   species = _ "Red Umbrella Tree",
    editor_category = "trees_wasteland",
    size = "small",
    attributes = { "tree" },

=== modified file 'data/world/resources/init.lua'
--- data/world/resources/init.lua	2016-01-11 22:13:36 +0000
+++ data/world/resources/init.lua	2016-02-12 11:47:09 +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",
    }
 }

=== modified file 'src/editor/CMakeLists.txt'
--- src/editor/CMakeLists.txt	2016-01-14 22:17:50 +0000
+++ src/editor/CMakeLists.txt	2016-02-12 11:47:09 +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-02-04 13:53:42 +0000
+++ src/editor/editorinteractive.cc	2016-02-12 11:47:09 +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"
@@ -82,7 +83,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);
 	}
@@ -91,7 +92,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);
 	}
@@ -107,36 +108,39 @@
 	tools_(new Tools()),
 	history_(new EditorHistory(undo_, redo_)),
 
-#define INIT_BUTTON(picture, name, tooltip)                         \
+#define INIT_BUTTON(image, name, tooltip)                                       \
 	TOOLBAR_BUTTON_COMMON_PARAMETERS(name),                                      \
-	g_gr->images().get("images/wui/" picture ".png"),                      \
+	g_gr->images().get(image),                                                   \
 	tooltip                                                                      \
 
 	toggle_main_menu_
 	(INIT_BUTTON
-	 ("menus/menu_toggle_menu", "menu", _("Menu"))),
+	 ("images/wui/menus/menu_toggle_menu.png", "menu", _("Menu"))),
 	toggle_tool_menu_
 	(INIT_BUTTON
-	 ("editor/editor_menu_toggle_tool_menu", "tools", _("Tools"))),
+	 ("images/wui/editor/editor_menu_toggle_tool_menu.png", "tools", _("Tools"))),
 	toggle_toolsize_menu_
 	(INIT_BUTTON
-	 ("editor/editor_menu_set_toolsize_menu", "toolsize",
+	 ("images/wui/editor/editor_menu_set_toolsize_menu.png", "toolsize",
 	  _("Tool Size"))),
 	toggle_minimap_
 	(INIT_BUTTON
-	 ("menus/menu_toggle_minimap", "minimap", _("Minimap"))),
+	 ("images/wui/menus/menu_toggle_minimap.png", "minimap", _("Minimap"))),
 	toggle_buildhelp_
 	(INIT_BUTTON
-	 ("menus/menu_toggle_buildhelp", "buildhelp", _("Show Building Spaces (on/off)"))),
+	 ("images/wui/menus/menu_toggle_buildhelp.png", "buildhelp", _("Show Building Spaces (on/off)"))),
 	toggle_player_menu_
 	(INIT_BUTTON
-	 ("editor/editor_menu_player_menu", "players", _("Players"))),
+	 ("images/wui/editor/editor_menu_player_menu.png", "players", _("Players"))),
 	undo_
 	(INIT_BUTTON
-	 ("editor/editor_undo", "undo", _("Undo"))),
+	 ("images/wui/editor/editor_undo.png", "undo", _("Undo"))),
 	redo_
 	(INIT_BUTTON
-	 ("editor/editor_redo", "redo", _("Redo")))
+	 ("images/wui/editor/editor_redo.png", "redo", _("Redo"))),
+	toggle_help_
+	(INIT_BUTTON
+	 ("images/ui_basic/menu_help.png", "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));
@@ -146,6 +150,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::Align::kLeft);
@@ -156,6 +161,7 @@
 	toolbar_.add(&toggle_player_menu_,     UI::Align::kLeft);
 	toolbar_.add(&undo_,                   UI::Align::kLeft);
 	toolbar_.add(&redo_,                   UI::Align::kLeft);
+	toolbar_.add(&toggle_help_,            UI::Align::kLeft);
 	adjust_toolbar_position();
 
 #ifndef NDEBUG
@@ -195,7 +201,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);
 		}
@@ -360,6 +366,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);
@@ -487,6 +501,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-02-12 11:47:09 +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

=== modified file 'src/editor/tools/editor_info_tool.cc'
--- src/editor/tools/editor_info_tool.cc	2016-01-06 19:11:20 +0000
+++ src/editor/tools/editor_info_tool.cc	2016-02-12 11:47:09 +0000
@@ -115,7 +115,7 @@
 	/** TRANSLATORS: You can also translate this as "Category: %s" or "Property: %s" */
 	buf += "• " + (boost::format(_("Is: %s"))
 						% i18n::localize_list(terrain_is_strings, i18n::ConcatenateWith::AMPERSAND)).str() + "\n";
-	buf += "• " + (boost::format(_("Editor Category: %s")) % ter.editor_category().descname()).str() + "\n";
+	buf += "• " + (boost::format(_("Editor Category: %s")) % ter.editor_category()->descname()).str() + "\n";
 
 	// *** Resources info
 	buf += std::string("\n") + _("Resources:") + "\n";

=== modified file 'src/editor/ui_menus/categorized_item_selection_menu.h'
--- src/editor/ui_menus/categorized_item_selection_menu.h	2016-02-08 20:04:17 +0000
+++ src/editor/ui_menus/categorized_item_selection_menu.h	2016-02-12 11:47:09 +0000
@@ -94,7 +94,7 @@
 
 		std::vector<int> item_indices;
 		for (size_t j = 0; j < descriptions_.size(); ++j) {
-			if (descriptions_.get(j).editor_category().name() != category.name()) {
+			if (descriptions_.get(j).editor_category()->name() != category.name()) {
 				continue;
 			}
 			item_indices.push_back(j);

=== 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-02-12 11:47:09 +0000
@@ -0,0 +1,225 @@
+/*
+ * 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"
+
+namespace {
+
+#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;
+
+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
+
+inline EditorInteractive& EditorHelp::eia() const {
+	return dynamic_cast<EditorInteractive&>(*get_parent());
+}
+
+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",
+	                                        "images/wui/editor/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::kLeft);
+		boxes_.at(tab->key)->add_space(kPadding);
+		boxes_.at(tab->key)->add(contents_.at(tab->key).get(), UI::Align::kLeft);
+
+		wrapper_boxes_.at(tab->key)->add_space(kPadding);
+		wrapper_boxes_.at(tab->key)->add(boxes_.at(tab->key).get(), UI::Align::kLeft);
+
+		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 Widelands::World& world = eia().egbase().world();
+	std::vector<HelpEntry> entries;
+
+	for (Widelands::DescriptionIndex i = 0; i < world.terrains().size(); ++i) {
+		const Widelands::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 Widelands::World& world = eia().egbase().world();
+	std::vector<HelpEntry> entries;
+
+	for (Widelands::DescriptionIndex i = 0; i < world.get_nr_immovables(); ++i) {
+		const Widelands::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->species(), 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 Widelands::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 Widelands::ImmovableDescr* descr =
+			   eia().egbase().world().get_immovable_descr(lists_.at(key)->get_selected());
+			descname = descr->species();
+			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();
+		// NOCOM(#codereview): Why is the lua script not returning the text
+		// including heading? Feels strange to do most formatting in Lua, but
+		// some in c++.
+		// NOCOM(GunChleoc): This is because if we want to open this in a separate window rather than
+		// in the encyclopedia, the heading is shown on the window title instead. cf. the Building help.
+		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-02-12 11:47:09 +0000
@@ -0,0 +1,109 @@
+/*
+ * 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 init_index,
+					 const std::string& init_descname,
+					 const Image* init_icon)
+			: index(init_index), descname(init_descname), icon(init_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-29 08:37:22 +0000
+++ src/editor/ui_menus/editor_tool_change_resources_options_menu.cc	2016-02-12 11:47:09 +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-02-12 11:47:09 +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-02-07 16:54:31 +0000
+++ src/logic/map_objects/immovable.cc	2016-02-12 11:47:09 +0000
@@ -199,7 +199,8 @@
 	MapObjectDescr(
 	MapObjectType::IMMOVABLE, table.get_string("name"), init_descname, table),
 	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());
 	}
@@ -213,8 +214,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 species name 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("species")) {
+				throw wexception("Immovable '%s' with type 'tree' must define a species", name().c_str());
+			}
+			species_ = table.get_string("species");
+		}
 	}
 
 	std::unique_ptr<LuaTable> programs = table.get_table("programs");
@@ -257,8 +273,8 @@
 	}
 }
 
-const EditorCategory& ImmovableDescr::editor_category() const {
-	return *editor_category_;
+const EditorCategory* ImmovableDescr::editor_category() const {
+	return editor_category_;
 }
 
 bool ImmovableDescr::has_terrain_affinity() const {

=== modified file 'src/logic/map_objects/immovable.h'
--- src/logic/map_objects/immovable.h	2016-02-07 09:30:20 +0000
+++ src/logic/map_objects/immovable.h	2016-02-12 11:47:09 +0000
@@ -129,9 +129,12 @@
 
 	const Buildcost & buildcost() const {return buildcost_;}
 
+	// Returns the editor category, or nullptr if the immovable has no editor category
+	// (e.g. Tribe immovables never have one).
+	const EditorCategory* 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& species() const {return species_;}
 
 	// Every immovable that can 'grow' needs to have terrain affinity defined,
 	// all others do not. Returns true if this one has it defined.
@@ -152,6 +155,8 @@
 	/// \see ActConstruction
 	Buildcost buildcost_;
 
+	std::string species_;
+
 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-02-12 11:47:09 +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-02-12 11:47:09 +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-02-07 09:30:20 +0000
+++ src/logic/map_objects/tribes/worker.cc	2016-02-12 11:47:09 +0000
@@ -926,7 +926,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-02-12 11:47:09 +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-02-12 11:47:09 +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-28 05:24:34 +0000
+++ src/logic/map_objects/world/terrain_description.cc	2016-02-12 11:47:09 +0000
@@ -210,8 +210,8 @@
 	return descname_;
 }
 
-const EditorCategory& TerrainDescription::editor_category() const {
-	return *editor_category_;
+const EditorCategory* TerrainDescription::editor_category() const {
+	return editor_category_;
 }
 
 DescriptionIndex TerrainDescription::get_valid_resource(uint8_t index) const {
@@ -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-02-12 11:47:09 +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;
 
@@ -109,7 +112,7 @@
 	int32_t dither_layer() const;
 
 	/// Returns the editor category.
-	const EditorCategory& editor_category() const;
+	const EditorCategory* editor_category() const;
 
 	/// Parameters for terrain affinity of immovables.
 	/// Temperature is in arbitrary units.

=== modified file 'src/logic/map_objects/world/world.cc'
--- src/logic/map_objects/world/world.cc	2016-01-23 14:57:53 +0000
+++ src/logic/map_objects/world/world.cc	2016-02-12 11:47:09 +0000
@@ -119,7 +119,7 @@
 	return *terrains_->get_mutable(i);
 }
 
-TerrainDescription const* World::get_ter(char const* const name) const {
+const TerrainDescription* World::terrain_descr(const std::string& name) const {
 	int32_t const i = terrains_->get_index(name);
 	return i != INVALID_INDEX ? terrains_->get_mutable(i) : nullptr;
 }

=== modified file 'src/logic/map_objects/world/world.h'
--- src/logic/map_objects/world/world.h	2016-01-13 07:27:55 +0000
+++ src/logic/map_objects/world/world.h	2016-02-12 11:47:09 +0000
@@ -50,7 +50,7 @@
 	// becomes a pure container.
 	const DescriptionMaintainer<TerrainDescription>& terrains() const;
 	TerrainDescription& terrain_descr(DescriptionIndex i) const;
-	TerrainDescription const* get_ter(char const* const name) const;
+	const TerrainDescription* terrain_descr(const std::string& name) const;
 
 	DescriptionIndex get_bob(char const* const l) const;
 	BobDescr const* get_bob_descr(DescriptionIndex index) const;

=== modified file 'src/map_io/map_terrain_packet.cc'
--- src/map_io/map_terrain_packet.cc	2015-11-28 22:29:26 +0000
+++ src/map_io/map_terrain_packet.cc	2016-02-12 11:47:09 +0000
@@ -61,7 +61,7 @@
 				}
 				const std::string new_terrain_name =
 				   lookup_table.lookup_terrain(old_terrain_name);
-				if (!world.get_ter(new_terrain_name.c_str())) {
+				if (!world.terrain_descr(new_terrain_name)) {
 					throw GameDataError("Terrain '%s' exists in map, not in world!", new_terrain_name.c_str());
 				}
 				smap[id] = world.terrains().get_index(new_terrain_name.c_str());

=== 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-02-12 11:47:09 +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[] = {
@@ -149,6 +153,40 @@
  ==========================================================
  */
 
+// NOCOM(#codereview): fix comment. nothing is registered here. also belows.
+// NOCOM(GunChleoc): I just copied the code including the documentation off get_building_description.
+// What would be a better comment then?
+/* 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);
+	EditorGameBase& egbase = get_egbase(L);
+	const World& world = egbase.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));
+	}
+	const Tribes& tribes = egbase.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));
+}
+
+
 /* RST
 	.. function:: get_building_description(building_description.name)
 
@@ -244,6 +282,55 @@
 	return LuaMaps::upcasted_map_object_descr_to_lua(L, worker_description);
 }
 
+// NOCOM(#codereview): fix comment
+/* 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();
+	const 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));
+}
+
+// NOCOM(#codereview): fix commment.
+/* 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().terrain_descr(terrain_name);
+	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
@@ -498,7 +585,8 @@
 	if (lua_gettop(L) >= 5)
 		force = luaL_checkboolean(L, 5);
 
-	const Tribes& tribes = get_egbase(L).tribes();
+	EditorGameBase& egbase = get_egbase(L);
+	const Tribes& tribes = egbase.tribes();
 
 	if (!tribes.building_exists(name)) {
 		report_error(L, "Unknown Building: '%s'", name.c_str());
@@ -514,14 +602,14 @@
 	Building * b = nullptr;
 	if (force) {
 		if (constructionsite) {
-			b = &get(L, get_egbase(L)).force_csite
+			b = &get(L, egbase).force_csite
 				(c->coords(), building_index, former_buildings);
 		} else {
-			b = &get(L, get_egbase(L)).force_building
+			b = &get(L, egbase).force_building
 				(c->coords(), former_buildings);
 		}
 	} else {
-		b = get(L, get_egbase(L)).build
+		b = get(L, egbase).build
 			(c->coords(), building_index, constructionsite, former_buildings);
 	}
 	if (!b)

=== 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-02-12 11:47:09 +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_game.cc'
--- src/scripting/lua_game.cc	2016-01-28 05:24:34 +0000
+++ src/scripting/lua_game.cc	2016-02-12 11:47:09 +0000
@@ -886,14 +886,15 @@
 void LuaPlayer::m_parse_building_list
 	(lua_State * L, const TribeDescr & tribe, std::vector<DescriptionIndex> & rv)
 {
-	const Tribes& tribes = get_egbase(L).tribes();
+	EditorGameBase& egbase = get_egbase(L);
+	const Tribes& tribes = egbase.tribes();
 	if (lua_isstring(L, -1)) {
 		std::string opt = luaL_checkstring(L, -1);
 		if (opt != "all") {
 			report_error(L, "'%s' was not understood as argument!", opt.c_str());
 		}
 		// Only act on buildings that the tribe has or could conquer
-		const TribeDescr& tribe_descr = get(L, get_egbase(L)).tribe();
+		const TribeDescr& tribe_descr = get(L, egbase).tribe();
 		for (size_t i = 0; i < tribes.nrbuildings(); ++i) {
 			const DescriptionIndex& building_index = static_cast<DescriptionIndex>(i);
 			const BuildingDescr& descr = *tribe_descr.get_building_descr(building_index);

=== modified file 'src/scripting/lua_map.cc'
--- src/scripting/lua_map.cc	2016-02-11 06:50:56 +0000
+++ src/scripting/lua_map.cc	2016-02-12 11:47:09 +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,212 @@
 	return 1;
 }
 
+
+/* RST
+ImmovableDescription
+--------------------
+
+.. class:: ImmovableDescription
+
+	A static description of a base immovable, so it can be used in help files
+	without having to access an actual immovable on the map.
+	See also class MapObjectDescription for more properties.
+*/
+const char LuaImmovableDescription::className[] = "ImmovableDescription";
+const MethodType<LuaImmovableDescription> LuaImmovableDescription::Methods[] = {
+	METHOD(LuaImmovableDescription, has_attribute),
+	METHOD(LuaImmovableDescription, probability_to_grow),
+	{nullptr, nullptr},
+};
+const PropertyType<LuaImmovableDescription> LuaImmovableDescription::Properties[] = {
+	PROP_RO(LuaImmovableDescription, species),
+	PROP_RO(LuaImmovableDescription, build_cost),
+	PROP_RO(LuaImmovableDescription, editor_category),
+	PROP_RO(LuaImmovableDescription, terrain_affinity),
+	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 species name of a tree for editor lists
+
+			(RO) the localized species name of the immovable, or an empty string if it has none.
+*/
+int LuaImmovableDescription::get_species(lua_State * L) {
+	lua_pushstring(L, get()->species());
+	return 1;
+}
+
+
+/* RST
+	.. attribute:: build_cost
+
+			(RO) a table of ware-to-count pairs, describing the 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) {
+	const EditorCategory* editor_category = get()->editor_category();
+	if (editor_category != nullptr) {
+		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:: returns the terrain affinity values for this immovable
+
+			(RO) a table containing numbers labeled as pickiness, preferred_fertility,
+				  preferred_humidity, and preferred_temperature,
+				  or nil if the immovalbe has no terrain affinity.
+*/
+int LuaImmovableDescription::get_terrain_affinity(lua_State * L) {
+	if (get()->has_terrain_affinity()) {
+		const TerrainAffinity& affinity = get()->terrain_affinity();
+		lua_newtable(L);
+		lua_pushstring(L, "pickiness");
+		lua_pushnumber(L, affinity.pickiness());
+		lua_settable(L, -3);
+		lua_pushstring(L, "preferred_fertility");
+		lua_pushnumber(L, affinity.preferred_fertility());
+		lua_settable(L, -3);
+		lua_pushstring(L, "preferred_humidity");
+		lua_pushnumber(L, affinity.preferred_humidity());
+		lua_settable(L, -3);
+		lua_pushstring(L, "preferred_temperature");
+		lua_pushnumber(L, affinity.preferred_temperature());
+		lua_settable(L, -3);
+	} else {
+		lua_pushnil(L);
+	}
+	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) {
+	switch (get()->owner_type()) {
+	case MapObjectDescr::OwnerType::kWorld:
+		lua_pushstring(L, "world");
+		break;
+	case MapObjectDescr::OwnerType::kTribe:
+		lua_pushstring(L, "tribe");
+		break;
+	default:
+		NEVER_HERE();
+	}
+	return 1;
+}
+
+
+/* RST
+	.. attribute:: size
+
+			(RO) the size of the immovable as an int.
+*/
+int LuaImmovableDescription::get_size(lua_State * L) {
+	// TODO(GunChleoc): see this todo, that is also mentioned below for
+	// buildings. I think we can do that now, every description is wrapped I
+	// think. Essentially that means every instance of something on the map
+	// (like a building) get's a .description that has the static data for the
+	// building/immovable. maybe in a followup branch, but definitvely before b19, since that is backwards
+	// incompatible.
+	// TODO(SirVer): size should be similar to
+	// https://wl.widelands.org/docs/wl/autogen_wl_map/#wl.map.BaseImmovable.size.
+	// In fact, as soon as all descriptions are wrapped (also for other
+	// immovables besides buildings) we should get rid of BaseImmovable.size.
+	lua_pushinteger(L, get()->get_size());
+	return 1;
+}
+
+/*
+ ==========================================================
+ METHODS
+ ==========================================================
+ */
+
+/* RST
+	.. method:: whether the immovable has the given attribute
+
+		:arg attribute_name: The attribute that we are checking for.
+		:type attribute_name: :class:`string`
+
+			(RO) true if the immovable has the attribute, false otherwise.
+*/
+int LuaImmovableDescription::has_attribute(lua_State * L) {
+	if (lua_gettop(L) != 2) {
+		report_error(L, "Takes only one argument.");
+	}
+	const uint32_t attribute_id = get()->get_attribute_id(luaL_checkstring(L, 2));
+	lua_pushboolean(L, get()->has_attribute(attribute_id));
+	return 1;
+}
+
+/* RST
+	.. method:: probability_to_grow
+
+		:arg terrain_name: The terrain that we are checking the probability for.
+		:type terrain_name: :class:`string`
+
+		(RO) A double describing the probability that this tree will grow on the given terrain.
+			  Returns nil if this immovable tree has no terrain affinity (all trees should have one).
+*/
+// NOCOM(#codereview): I think it should pass in the terrain description, not the name.
+// NOCOM(GunChleoc): Which eris function do I need for that?
+int LuaImmovableDescription::probability_to_grow(lua_State * L) {
+	if (lua_gettop(L) != 2) {
+		report_error(L, "Takes only one argument.");
+	}
+	if (get()->has_terrain_affinity()) {
+		const TerrainDescription* terrain = get_egbase(L).world().terrain_descr(luaL_checkstring(L, 2));
+		lua_pushnumber(L, Widelands::probability_to_grow(get()->terrain_affinity(), *terrain));
+	} else {
+		lua_pushnil(L);
+	}
+	return 1;
+}
+
+
+
 /* RST
 BuildingDescription
 -------------------
@@ -1540,8 +1750,9 @@
 int LuaBuildingDescription::get_enhanced_from(lua_State * L) {
 	if (get()->is_enhanced()) {
 		const DescriptionIndex& enhanced_from = get()->enhanced_from();
-		assert(get_egbase(L).tribes().building_exists(enhanced_from));
-		return upcasted_map_object_descr_to_lua(L, get_egbase(L).tribes().get_building_descr(enhanced_from));
+		EditorGameBase& egbase = get_egbase(L);
+		assert(egbase.tribes().building_exists(enhanced_from));
+		return upcasted_map_object_descr_to_lua(L, egbase.tribes().get_building_descr(enhanced_from));
 	}
 	lua_pushnil(L);
 	return 0;
@@ -2271,8 +2482,9 @@
 void LuaWareDescription::__unpersist(lua_State* L) {
 	std::string name;
 	UNPERS_STRING("name", name);
-	DescriptionIndex idx = get_egbase(L).tribes().safe_ware_index(name.c_str());
-	set_description_pointer(get_egbase(L).tribes().get_ware_descr(idx));
+	const Tribes& tribes = get_egbase(L).tribes();
+	DescriptionIndex idx = tribes.safe_ware_index(name.c_str());
+	set_description_pointer(tribes.get_ware_descr(idx));
 }
 
 
@@ -2311,7 +2523,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 +2689,312 @@
  ==========================================================
  */
 
+/* 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:: editor_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[] = {
+	{nullptr, nullptr},
+};
+const PropertyType<LuaTerrainDescription> LuaTerrainDescription::Properties[] = {
+	PROP_RO(LuaTerrainDescription, name),
+	PROP_RO(LuaTerrainDescription, descname),
+	PROP_RO(LuaTerrainDescription, default_resource),
+	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),
+	{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().terrain_descr(name));
+}
+
+/*
+ ==========================================================
+ 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
+
+			(RO) the :class:`wl.map.ResourceDescription` for the default resource provided by this terrain, or
+				  nil if the terrain has no default resource.
+*/
+
+int LuaTerrainDescription::get_default_resource(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()) {
+		to_lua<LuaMaps::LuaResourceDescription>
+				(L, new LuaMaps::LuaResourceDescription(world.get_resource(res_index)));
+	} 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 != nullptr) {
+		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().front());
+	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
+
+			(RO) a list of :class:`wl.map.ResourceDescription` with all valid resources for this terrain.
+*/
+
+int LuaTerrainDescription::get_valid_resources(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++);
+			to_lua<LuaMaps::LuaResourceDescription>
+					(L, new LuaMaps::LuaResourceDescription(world.get_resource(res_index)));
+			lua_settable(L, -3);
+		}
+	}
+	return 1;
+}
+
+
+/*
+ ==========================================================
+ METHODS
+ ==========================================================
+ */
+
 
 
 /* RST
@@ -3338,10 +3856,10 @@
 */
 int LuaWarehouse::get_expedition_in_progress(lua_State * L) {
 
-	Warehouse* wh = get(L, get_egbase(L));
+	EditorGameBase& egbase = get_egbase(L);
 
-	if (is_a(Game, &get_egbase(L))) {
-		PortDock* pd = wh->get_portdock();
+	if (is_a(Game, &egbase)) {
+		PortDock* pd = get(L, egbase)->get_portdock();
 		if (pd) {
 			if (pd->expedition_started()){
 				return 1;
@@ -3425,19 +3943,20 @@
 */
 int LuaWarehouse::start_expedition(lua_State* L) {
 
-	Warehouse* Wh = get(L, get_egbase(L));
+	EditorGameBase& egbase = get_egbase(L);
+	Warehouse* wh = get(L, egbase);
 
-	if (!Wh) {
+	if (!wh) {
 		return 0;
 	}
 
-	if (upcast(Game, game, &get_egbase(L))) {
-		PortDock* pd = Wh->get_portdock();
+	if (upcast(Game, game, &egbase)) {
+		PortDock* pd = wh->get_portdock();
 		if (!pd) {
 			return 0;
 		}
 		if (!pd->expedition_started()){
-			game->send_player_start_or_cancel_expedition(*Wh);
+			game->send_player_start_or_cancel_expedition(*wh);
 			return 1;
 		}
 	}
@@ -3455,19 +3974,20 @@
 */
 int LuaWarehouse::cancel_expedition(lua_State* L) {
 
-	Warehouse* Wh = get(L, get_egbase(L));
+	EditorGameBase& egbase = get_egbase(L);
+	Warehouse* wh = get(L, egbase);
 
-	if (!Wh) {
+	if (!wh) {
 		return 0;
 	}
 
-	if (upcast(Game, game, &get_egbase(L))) {
-		PortDock* pd = Wh->get_portdock();
+	if (upcast(Game, game, &egbase)) {
+		PortDock* pd = wh->get_portdock();
 			if (!pd) {
 				return 0;
 			}
 		if (pd->expedition_started()){
-			game->send_player_start_or_cancel_expedition(*Wh);
+			game->send_player_start_or_cancel_expedition(*wh);
 			return 1;
 		}
 	}
@@ -3901,8 +4421,9 @@
 */
 // UNTESTED sink states
 int LuaShip::get_state(lua_State* L) {
-	if (is_a(Game, &get_egbase(L))) {
-		switch (get(L, get_egbase(L))->get_ship_state()) {
+	EditorGameBase& egbase = get_egbase(L);
+	if (is_a(Game, &egbase)) {
+		switch (get(L, egbase)->get_ship_state()) {
 			case Ship::TRANSPORT:
 				lua_pushstring(L, "transport");
 				break;
@@ -3934,8 +4455,9 @@
 }
 
 int LuaShip::get_scouting_direction(lua_State* L) {
-	if (is_a(Game, &get_egbase(L))) {
-		switch (get(L, get_egbase(L))->get_scouting_direction()) {
+	EditorGameBase& egbase = get_egbase(L);
+	if (is_a(Game, &egbase)) {
+		switch (get(L, egbase)->get_scouting_direction()) {
 			case WalkingDir::WALK_NE:
 				lua_pushstring(L, "ne");
 				break;
@@ -3963,7 +4485,8 @@
 }
 
 int LuaShip::set_scouting_direction(lua_State* L) {
-	if (upcast(Game, game, &get_egbase(L))) {
+	EditorGameBase& egbase = get_egbase(L);
+	if (upcast(Game, game, &egbase)) {
 		std::string dirname = luaL_checkstring(L, 3);
 		WalkingDir dir = WalkingDir::IDLE;
 
@@ -3982,7 +4505,7 @@
 		} else {
 			return 0;
 		}
-		game->send_player_ship_scouting_direction(*get(L, get_egbase(L)), dir);
+		game->send_player_ship_scouting_direction(*get(L, egbase), dir);
 		return 1;
 	}
 	return 0;
@@ -3997,8 +4520,9 @@
 
 */
 int LuaShip::get_island_explore_direction(lua_State* L) {
-	if (is_a(Game, &get_egbase(L))) {
-		switch (get(L, get_egbase(L))->get_island_explore_direction()) {
+	EditorGameBase& egbase = get_egbase(L);
+	if (is_a(Game, &egbase)) {
+		switch (get(L, egbase)->get_island_explore_direction()) {
 			case IslandExploreDirection::kCounterClockwise:
 				lua_pushstring(L, "ccw");
 				break;
@@ -4014,8 +4538,9 @@
 }
 
 int LuaShip::set_island_explore_direction(lua_State* L) {
-	if (upcast(Game, game, &get_egbase(L))) {
-		Ship* ship = get(L, get_egbase(L));
+	EditorGameBase& egbase = get_egbase(L);
+	if (upcast(Game, game, &egbase)) {
+		Ship* ship = get(L, egbase);
 		std::string dir = luaL_checkstring(L, 3);
 		if (dir == "ccw"){
 			 game->send_player_ship_explore_island(*ship,  IslandExploreDirection::kCounterClockwise);
@@ -4110,9 +4635,10 @@
 		:returns: true/false
 */
 int LuaShip::build_colonization_port(lua_State* L) {
-	Ship* ship =  get(L, get_egbase(L));
+	EditorGameBase& egbase = get_egbase(L);
+	Ship* ship =  get(L, egbase);
 	if (ship->get_ship_state() == Widelands::Ship::EXP_FOUNDPORTSPACE) {
-		if (upcast(Game, game, &get_egbase(L))) {
+		if (upcast(Game, game, &egbase)) {
 			game->send_player_ship_construct_port(*ship, ship->exp_port_spaces().front());
 			return 1;
 		}
@@ -4461,7 +4987,7 @@
 }
 int LuaField::set_resource(lua_State * L) {
 	auto& egbase = get_egbase(L);
-	int32_t res = get_egbase(L).world().get_resource
+	int32_t res = egbase.world().get_resource
 		(luaL_checkstring(L, -1));
 
 	if (res == Widelands::INVALID_INDEX)
@@ -4487,16 +5013,16 @@
 	return 1;
 }
 int LuaField::set_resource_amount(lua_State * L) {
+	EditorGameBase& egbase = get_egbase(L);
 	auto c  = fcoords(L);
 	int32_t res = c.field->get_resources();
 	int32_t amount = luaL_checkint32(L, -1);
-	const ResourceDescription * resDesc = get_egbase(L).world().get_resource(res);
+	const ResourceDescription * resDesc = egbase.world().get_resource(res);
 	int32_t max_amount = resDesc ? resDesc->max_amount() : 0;
 
 	if (amount < 0 || amount > max_amount)
 		report_error(L, "Illegal amount: %i, must be >= 0 and <= %i", amount, max_amount);
 
-	EditorGameBase & egbase = get_egbase(L);
 	auto& map = egbase.map();
 	if (is_a(Game, &egbase)) {
 		map.set_resources(c, amount);
@@ -4943,6 +5469,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 +5516,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-02-12 11:47:09 +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,47 @@
 		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_species(lua_State *);
+	int get_build_cost(lua_State *);
+	int get_editor_category(lua_State *);
+	int get_terrain_affinity(lua_State *);
+	int get_owner_type(lua_State *);
+	int get_size(lua_State *);
+
+	/*
+	 * Lua methods
+	 */
+	int has_attribute(lua_State *);
+	int probability_to_grow(lua_State *);
+
+	/*
+	 * C methods
+	 */
+
+private:
+	CASTED_GET_DESCRIPTION(ImmovableDescr)
+};
+
+
 class LuaBuildingDescription : public LuaMapObjectDescription {
 public:
 	LUNA_CLASS_HEAD(LuaBuildingDescription);
@@ -515,6 +559,109 @@
 
 #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(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(lua_State *);
+
+	/*
+	 * Lua methods
+	 */
+
+	/*
+	 * 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	2016-01-28 05:24:34 +0000
+++ src/scripting/lua_root.cc	2016-02-12 11:47:09 +0000
@@ -318,6 +318,8 @@
 	{0, 0},
 };
 const PropertyType<LuaWorld> LuaWorld::Properties[] = {
+	PROP_RO(LuaWorld, immovable_descriptions),
+	PROP_RO(LuaWorld, terrain_descriptions),
 	{0, 0, 0},
 };
 
@@ -345,6 +347,52 @@
  */
 
 /* RST
+	.. attribute:: immovable_descriptions
+
+		Returns a list of names with all the immovables that the world has.
+
+		(RO) a list of immovable names, e.g. {"alder_summer_old", "cirrus_wasteland_old", ...}
+*/
+int LuaWorld::get_immovable_descriptions(lua_State* L) {
+	const World& world = get_egbase(L).world();
+	lua_newtable(L);
+	int index = 1;
+	for (DescriptionIndex i = 0; i < world.get_nr_immovables(); ++i) {
+		lua_pushint32(L, index++);
+		lua_pushstring(L, world.get_immovable_descr(i)->name());
+		lua_settable(L, -3);
+	}
+	return 1;
+}
+
+
+/* RST
+	.. attribute:: terrain_descriptions
+
+		Returns a list of names with the terrains that are available in the world.
+
+		(RO) a list of terrain names, e.g. {"wiese1", "wiese2", ...}
+*/
+// NOCOM(#codereview): why is this not just returning the terrain_description
+// instead of strings? Alternatively it could also return name/description
+// pairs.
+// NOCOM(GunChleoc): LuaTerrainDescription is defined in LuaMap. Do we really want a dependency from
+// here to LuaMap? Same problem for the immovables.
+int LuaWorld::get_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
@@ -559,7 +607,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		get_egbase(L).mutable_tribes()->add_constructionsite_type(table, get_egbase(L));
+		EditorGameBase& egbase = get_egbase(L);
+		egbase.mutable_tribes()->add_constructionsite_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -581,7 +630,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		get_egbase(L).mutable_tribes()->add_dismantlesite_type(table, get_egbase(L));
+		EditorGameBase& egbase = get_egbase(L);
+		egbase.mutable_tribes()->add_dismantlesite_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -604,7 +654,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		get_egbase(L).mutable_tribes()->add_militarysite_type(table, get_egbase(L));
+		EditorGameBase& egbase = get_egbase(L);
+		egbase.mutable_tribes()->add_militarysite_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -626,7 +677,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		get_egbase(L).mutable_tribes()->add_productionsite_type(table, get_egbase(L));
+		EditorGameBase& egbase = get_egbase(L);
+		egbase.mutable_tribes()->add_productionsite_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -648,7 +700,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		get_egbase(L).mutable_tribes()->add_trainingsite_type(table, get_egbase(L));
+		EditorGameBase& egbase = get_egbase(L);
+		egbase.mutable_tribes()->add_trainingsite_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -670,7 +723,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		get_egbase(L).mutable_tribes()->add_warehouse_type(table, get_egbase(L));
+		EditorGameBase& egbase = get_egbase(L);
+		egbase.mutable_tribes()->add_warehouse_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -758,7 +812,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		get_egbase(L).mutable_tribes()->add_carrier_type(table, get_egbase(L));
+		EditorGameBase& egbase = get_egbase(L);
+		egbase.mutable_tribes()->add_carrier_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -780,7 +835,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		get_egbase(L).mutable_tribes()->add_soldier_type(table, get_egbase(L));
+		EditorGameBase& egbase = get_egbase(L);
+		egbase.mutable_tribes()->add_soldier_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -802,7 +858,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		get_egbase(L).mutable_tribes()->add_worker_type(table, get_egbase(L));
+		EditorGameBase& egbase = get_egbase(L);
+		egbase.mutable_tribes()->add_worker_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -825,7 +882,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		get_egbase(L).mutable_tribes()->add_tribe(table, get_egbase(L));
+		EditorGameBase& egbase = get_egbase(L);
+		egbase.mutable_tribes()->add_tribe(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}

=== 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-02-12 11:47:09 +0000
@@ -107,6 +107,8 @@
 	/*
 	 * Properties
 	 */
+	int get_immovable_descriptions(lua_State* L);
+	int get_terrain_descriptions(lua_State* L);
 
 	/*
 	 * Lua methods

=== modified file 'src/wui/encyclopedia_window.cc'
--- src/wui/encyclopedia_window.cc	2016-01-29 08:37:22 +0000
+++ src/wui/encyclopedia_window.cc	2016-02-12 11:47:09 +0000
@@ -42,20 +42,14 @@
 #include "scripting/lua_table.h"
 #include "wui/interactive_player.h"
 
+namespace {
+
 #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 InteractivePlayer& EncyclopediaWindow::iaplayer() const {
-	return dynamic_cast<InteractivePlayer&>(*get_parent());
-}
-
-namespace {
-
 struct EncyclopediaTab {
 	EncyclopediaTab(const std::string& _key,
 	                const std::string& _image_filename,
@@ -83,6 +77,10 @@
 
 }  // namespace
 
+inline InteractivePlayer& EncyclopediaWindow::iaplayer() const {
+	return dynamic_cast<InteractivePlayer&>(*get_parent());
+}
+
 EncyclopediaWindow::EncyclopediaWindow(InteractivePlayer& parent, UI::UniqueWindow::Registry& registry)
 	: UI::UniqueWindow(
         &parent, "encyclopedia", &registry, WINDOW_WIDTH, WINDOW_HEIGHT, _("Tribal Encyclopedia")),
@@ -171,58 +169,58 @@
 	}
 }
 
-void EncyclopediaWindow::fill_entries(const char* key, std::vector<EncyclopediaEntry>& entries) {
-	std::sort(entries.begin(), entries.end());
-	for (uint32_t i = 0; i < entries.size(); i++) {
-		EncyclopediaEntry cur = entries[i];
+void EncyclopediaWindow::fill_entries(const char* key, std::vector<EncyclopediaEntry>* entries) {
+	std::sort(entries->begin(), entries->end());
+	for (uint32_t i = 0; i < entries->size(); i++) {
+		EncyclopediaEntry cur = (*entries)[i];
 		lists_.at(key)->add(cur.descname, cur.index, cur.icon);
 	}
 	lists_.at(key)->select(0);
 }
 
 void EncyclopediaWindow::fill_buildings() {
-	const Tribes& tribes = iaplayer().egbase().tribes();
-	const TribeDescr& tribe = iaplayer().player().tribe();
+	const Widelands::Tribes& tribes = iaplayer().egbase().tribes();
+	const Widelands::TribeDescr& tribe = iaplayer().player().tribe();
 	std::vector<EncyclopediaEntry> entries;
 
 	for (Widelands::DescriptionIndex i = 0; i < tribes.nrbuildings(); ++i) {
-		const BuildingDescr* building = tribes.get_building_descr(i);
-		if (tribe.has_building(i) || building->type() == MapObjectType::MILITARYSITE) {
+		const Widelands::BuildingDescr* building = tribes.get_building_descr(i);
+		if (tribe.has_building(i) || building->type() == Widelands::MapObjectType::MILITARYSITE) {
 			EncyclopediaEntry entry(i, building->descname(), building->icon());
 			entries.push_back(entry);
 		}
 	}
-	fill_entries("buildings", entries);
+	fill_entries("buildings", &entries);
 }
 
 void EncyclopediaWindow::fill_wares() {
-	const TribeDescr& tribe = iaplayer().player().tribe();
+	const Widelands::TribeDescr& tribe = iaplayer().player().tribe();
 	std::vector<EncyclopediaEntry> entries;
 
 	for (const Widelands::DescriptionIndex& i : tribe.wares()) {
-		const WareDescr* ware = tribe.get_ware_descr(i);
+		const Widelands::WareDescr* ware = tribe.get_ware_descr(i);
 		EncyclopediaEntry entry(i, ware->descname(), ware->icon());
 		entries.push_back(entry);
 	}
-	fill_entries("wares", entries);
+	fill_entries("wares", &entries);
 }
 
 void EncyclopediaWindow::fill_workers() {
-	const TribeDescr& tribe = iaplayer().player().tribe();
+	const Widelands::TribeDescr& tribe = iaplayer().player().tribe();
 	std::vector<EncyclopediaEntry> entries;
 
 	for (const Widelands::DescriptionIndex& i : tribe.workers()) {
-		const WorkerDescr* worker = tribe.get_worker_descr(i);
+		const Widelands::WorkerDescr* worker = tribe.get_worker_descr(i);
 		EncyclopediaEntry entry(i, worker->descname(), worker->icon());
 		entries.push_back(entry);
 	}
-	fill_entries("workers", entries);
+	fill_entries("workers", &entries);
 }
 
 void EncyclopediaWindow::entry_selected(const std::string& key,
                                         const std::string& script_path,
                                         const Widelands::MapObjectType& type) {
-	const TribeDescr& tribe = iaplayer().player().tribe();
+	const Widelands::TribeDescr& tribe = iaplayer().player().tribe();
 	try {
 		std::unique_ptr<LuaTable> table(iaplayer().egbase().lua().run_script(script_path));
 		std::unique_ptr<LuaCoroutine> cr(table->get_coroutine("func"));

=== modified file 'src/wui/encyclopedia_window.h'
--- src/wui/encyclopedia_window.h	2016-01-17 09:54:31 +0000
+++ src/wui/encyclopedia_window.h	2016-02-12 11:47:09 +0000
@@ -59,7 +59,7 @@
 	InteractivePlayer& iaplayer() const;
 
 	// Fill table of contents
-	void fill_entries(const char* key, std::vector<EncyclopediaEntry>& entries);
+	void fill_entries(const char* key, std::vector<EncyclopediaEntry>* entries);
 	void fill_buildings();
 	void fill_wares();
 	void fill_workers();

=== modified file 'src/wui/game_main_menu_save_game.cc'
--- src/wui/game_main_menu_save_game.cc	2016-01-31 10:57:58 +0000
+++ src/wui/game_main_menu_save_game.cc	2016-02-12 11:47:09 +0000
@@ -33,9 +33,7 @@
 #include "logic/playersmanager.h"
 #include "wui/interactive_gamebase.h"
 
-InteractiveGameBase & GameMainMenuSaveGame::igbase() {
-	return dynamic_cast<InteractiveGameBase&>(*get_parent());
-}
+namespace {
 
 #define WINDOW_WIDTH                                                        440
 #define WINDOW_HEIGHT                                                       440
@@ -52,6 +50,12 @@
 #define DELETE_Y                          (CANCEL_Y - BUTTON_HEIGHT - VSPACING)
 #define OK_Y                              (DELETE_Y - BUTTON_HEIGHT - VSPACING)
 
+} // namespace
+
+InteractiveGameBase & GameMainMenuSaveGame::igbase() {
+	return dynamic_cast<InteractiveGameBase&>(*get_parent());
+}
+
 GameMainMenuSaveGame::GameMainMenuSaveGame
 	(InteractiveGameBase & parent, UI::UniqueWindow::Registry & registry)
 :

=== modified file 'test/maps/lua_persistence.wmf/scripting/test_persistence.lua'
--- test/maps/lua_persistence.wmf/scripting/test_persistence.lua	2016-01-28 05:24:34 +0000
+++ test/maps/lua_persistence.wmf/scripting/test_persistence.lua	2016-02-12 11:47:09 +0000
@@ -33,6 +33,9 @@
    building_descr = game:get_building_description("barbarians_lumberjacks_hut")
    ware_descr = game:get_ware_description("ax")
    worker_descr = game:get_worker_description("barbarians_lumberjack")
+   immovable_descr = game:get_immovable_description("alder_summer_sapling")
+   resource_descr = game:get_resource_description("coal")
+   terrain_descr = game:get_terrain_description("wiese1")
 
    corout = coroutine.create(function()
       local a = 100
@@ -101,6 +104,9 @@
    assert_equal("barbarians_lumberjacks_hut", building_descr.name)
    assert_equal("ax", ware_descr.name)
    assert_equal("barbarians_lumberjack", worker_descr.name)
+   assert_equal("alder_summer_sapling", immovable_descr.name)
+   assert_equal("coal", resource_descr.name)
+   assert_equal("wiese1", terrain_descr.name)
 
    assert_equal(global_value_1, false)
    assert_thread(corout)

=== modified file 'test/maps/lua_testsuite.wmf/scripting/immovables_descriptions.lua'
--- test/maps/lua_testsuite.wmf/scripting/immovables_descriptions.lua	2016-01-28 05:24:34 +0000
+++ test/maps/lua_testsuite.wmf/scripting/immovables_descriptions.lua	2016-02-12 11:47:09 +0000
@@ -13,12 +13,129 @@
    end)
 end
 
+
+--  =======================================================
+--  ***************** ImmovableDescription *****************
+--  =======================================================
+
+function test_descr:test_immovable_descr()
+   assert_error("Unknown 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_species()
+   assert_equal("", egbase:get_immovable_description("bush1").species)
+   assert_equal("", egbase:get_immovable_description("cornfield_ripe").species)
+   assert_equal("", egbase:get_immovable_description("alder_summer_sapling").species)
+   assert_equal("Alder", egbase:get_immovable_description("alder_summer_old").species)
+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"])
+
+   local total_cost = function(t)
+      local cost = 0
+      for name, count in pairs(t) do
+         cost = cost + count
+      end
+      return cost
+   end
+   assert_equal(16, total_cost(build_cost))
+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_terrain_affinity()
+   assert_equal(nil, egbase:get_immovable_description("bush1").terrain_affinity)
+   assert_equal(nil, egbase:get_immovable_description("cornfield_ripe").terrain_affinity)
+
+   local aff_alder_sapling = egbase:get_immovable_description("alder_summer_sapling").terrain_affinity
+   local aff_alder_old = egbase:get_immovable_description("alder_summer_old").terrain_affinity
+   local aff_mushroom_red_pole = egbase:get_immovable_description("mushroom_red_wasteland_pole").terrain_affinity
+   local aff_umbrella_green_mature = egbase:get_immovable_description("umbrella_green_wasteland_mature").terrain_affinity
+
+   -- Pickiness
+   assert_near(0.6, aff_alder_sapling["pickiness"], 0.01)
+   assert_equal(aff_alder_sapling["pickiness"], aff_alder_old["pickiness"])
+   assert_near(0.6, aff_mushroom_red_pole["pickiness"], 0.01)
+   assert_near(0.8, aff_umbrella_green_mature["pickiness"], 0.01)
+
+   -- preferred_fertility
+   assert_near(0.6, aff_alder_sapling["preferred_fertility"], 0.01)
+   assert_equal(aff_alder_sapling["preferred_fertility"], aff_alder_old["preferred_fertility"])
+   assert_near(0.85, aff_mushroom_red_pole["preferred_fertility"], 0.01)
+   assert_near(0.85, aff_umbrella_green_mature["preferred_fertility"], 0.01)
+
+   -- preferred_humidity
+   assert_near(0.65, aff_alder_sapling["preferred_humidity"], 0.01)
+   assert_equal(aff_alder_sapling["preferred_humidity"], aff_alder_old["preferred_humidity"])
+   assert_near(0.35, aff_mushroom_red_pole["preferred_humidity"], 0.01)
+   assert_near(0.2, aff_umbrella_green_mature["preferred_humidity"], 0.01)
+
+   -- preferred_temperature
+   assert_equal(125, aff_alder_sapling["preferred_temperature"])
+   assert_equal(aff_alder_sapling["preferred_temperature"], aff_alder_old["preferred_temperature"])
+   assert_equal(80, aff_mushroom_red_pole["preferred_temperature"])
+   assert_equal(110, aff_umbrella_green_mature["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
+
+function test_descr:test_immovable_has_attribute()
+   assert_equal(false, egbase:get_immovable_description("bush1"):has_attribute("tree"))
+   assert_equal(false, egbase:get_immovable_description("alder_summer_sapling"):has_attribute("tree"))
+   assert_equal(true, egbase:get_immovable_description("alder_summer_old"):has_attribute("tree"))
+end
+
+function test_descr:test_immovable_probability_to_grow()
+   assert_equal(nil, egbase:get_immovable_description("bush1"):probability_to_grow("wiese1"))
+
+   local alder = egbase:get_immovable_description("alder_summer_sapling")
+   assert_near(0.51, alder:probability_to_grow("wiese1"), 0.01)
+   assert_near(0.0022, alder:probability_to_grow("wasteland_beach"), 0.0001)
+   assert_near(0.66, alder:probability_to_grow("desert_forested_mountain2"), 0.01)
+   assert_near(0.000037, alder:probability_to_grow("winter_water"), 0.000001)
+end
+
+
 --  =======================================================
 --  ***************** BuildingDescription *****************
 --  =======================================================
 
 function test_descr:test_building_descr()
-   assert_error("Wrong building", function() egbase:get_building_description("XXX") end)
+   assert_error("Unknown building", function() egbase:get_building_description("XXX") end)
    assert_error("Wrong number of parameters: 2", function() egbase:get_building_description("XXX", "YYY") end)
    assert_error("Wrong number of parameters: 3", function() egbase:get_building_description("XXX","YYY","ZZZ") end)
 end
@@ -26,10 +143,10 @@
 
 -- This is actually a property of MapOjectDescription
 function test_descr:test_descname()
-   assert_equal(_"Lumberjack’s Hut", egbase:get_building_description("barbarians_lumberjacks_hut").descname)
-   assert_equal(_"Battle Arena", egbase:get_building_description("barbarians_battlearena").descname)
-   assert_equal(_"Fortress", egbase:get_building_description("barbarians_fortress").descname)
-   assert_equal(_"Coal Mine", egbase:get_building_description("barbarians_coalmine").descname)
+   assert_equal("Lumberjack’s Hut", egbase:get_building_description("barbarians_lumberjacks_hut").descname)
+   assert_equal("Battle Arena", egbase:get_building_description("barbarians_battlearena").descname)
+   assert_equal("Fortress", egbase:get_building_description("barbarians_fortress").descname)
+   assert_equal("Coal Mine", egbase:get_building_description("barbarians_coalmine").descname)
 end
 
 -- This is actually a property of MapOjectDescription
@@ -157,7 +274,7 @@
 
 -- This is actually a property of MapOjectDescription
 function test_descr:test_descname()
-   assert_equal(_"Coal Mine", egbase:get_building_description("barbarians_coalmine").descname)
+   assert_equal("Coal Mine", egbase:get_building_description("barbarians_coalmine").descname)
 end
 
 -- This is actually a property of MapOjectDescription
@@ -208,7 +325,7 @@
 
 -- This is actually a property of MapOjectDescription
 function test_descr:test_descname()
-   assert_equal(_"Sentry", egbase:get_building_description("barbarians_sentry").descname)
+   assert_equal("Sentry", egbase:get_building_description("barbarians_sentry").descname)
 end
 
 -- This is actually a property of MapOjectDescription
@@ -237,7 +354,7 @@
 
 -- This is actually a property of MapOjectDescription
 function test_descr:test_descname()
-   assert_equal(_"Battle Arena", egbase:get_building_description("barbarians_battlearena").descname)
+   assert_equal("Battle Arena", egbase:get_building_description("barbarians_battlearena").descname)
 end
 
 -- This is actually a property of MapOjectDescription
@@ -298,7 +415,7 @@
 
 -- This is actually a property of MapOjectDescription
 function test_descr:test_descname()
-   assert_equal(_"Warehouse", egbase:get_building_description("barbarians_warehouse").descname)
+   assert_equal("Warehouse", egbase:get_building_description("barbarians_warehouse").descname)
 end
 
 -- This is actually a property of MapOjectDescription
@@ -321,13 +438,13 @@
 --  =======================================================
 
 function test_descr:test_ware_descr()
-   assert_error("Wrong ware", function() egbase:get_ware_description("XXX") end)
+   assert_error("Unknown ware", function() egbase:get_ware_description("XXX") end)
    assert_error("Wrong number of parameters: 2", function() egbase:get_ware_description("XXX","YYY") end)
 end
 
 -- This is actually a property of MapOjectDescription
 function test_descr:test_descname()
-   assert_equal(_"Thatch Reed", egbase:get_ware_description("thatch_reed").descname)
+   assert_equal("Thatch Reed", egbase:get_ware_description("thatch_reed").descname)
 end
 
 -- This is actually a property of MapOjectDescription
@@ -395,13 +512,13 @@
 --  =======================================================
 
 function test_descr:test_worker_descr()
-   assert_error("Wrong worker", function() egbase:get_worker_description("XXX") end)
+   assert_error("Unknown worker", function() egbase:get_worker_description("XXX") end)
    assert_error("Wrong number of parameters: 2", function() egbase:get_worker_description("XXX","YYY") end)
 end
 
 -- This is actually a property of MapOjectDescription
 function test_descr:test_descname()
-   assert_equal(_"Miner", egbase:get_worker_description("barbarians_miner").descname)
+   assert_equal("Miner", egbase:get_worker_description("barbarians_miner").descname)
 end
 
 -- This is actually a property of MapOjectDescription

=== 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-02-12 11:47:09 +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-02-12 11:47:09 +0000
@@ -0,0 +1,184 @@
+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()
+   -- NOCOM(#codereview): is_detectable -> is_surveiable ?
+   -- https://en.wikipedia.org/wiki/Geological_survey
+   -- NOCOM(GunChleoc): I think "detectable" is fine, the term "detecting" is used on
+   -- https://en.wikipedia.org/wiki/Prospecting. And the word "surveyable" doesn't exist.
+   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()
+   assert_equal("water", egbase:get_terrain_description("wiese1").default_resource.name)
+   assert_equal(nil, egbase:get_terrain_description("wasteland_beach").default_resource)
+   assert_equal(nil,
+      egbase:get_terrain_description("desert_forested_mountain2").default_resource)
+   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_near(0.7, egbase:get_terrain_description("wiese1").fertility, 0.01)
+   assert_near(0.2, egbase:get_terrain_description("wasteland_beach").fertility, 0.01)
+   assert_near(0.5, egbase:get_terrain_description("desert_forested_mountain2").fertility, 0.01)
+   assert_near(0.001, egbase:get_terrain_description("winter_water").fertility, 0.0001)
+end
+
+function test_terrains_resource_descr:test_terrain_humidity()
+   assert_near(0.6, egbase:get_terrain_description("wiese1").humidity, 0.01)
+   assert_near(0.4, egbase:get_terrain_description("wasteland_beach").humidity, 0.01)
+   assert_near(0.5, egbase:get_terrain_description("desert_forested_mountain2").humidity, 0.01)
+   assert_near(0.999, egbase:get_terrain_description("winter_water").humidity, 0.0001)
+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()
+   assert_equal("water", egbase:get_terrain_description("wiese1").valid_resources[1].name)
+   assert_equal(0, #egbase:get_terrain_description("wasteland_beach").valid_resources)
+   assert_equal(4, #egbase:get_terrain_description("desert_forested_mountain2").valid_resources)
+   assert_equal("fish", egbase:get_terrain_description("winter_water").valid_resources[1].name)
+end


Follow ups