← Back to team overview

widelands-dev team mailing list archive

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

 

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

Commit message:
Dynamic loading of the tribes' map objects and of custom scenario tribe objects:

- Simplified tribes/init.lua: The tribe directory is now walked automatically, without the need of manually getting the load order right. Missing prerequisite map objects are parsed during postload.

- If scripting/tribes/init.lua exists in a map, run it to load custom tribe objects when starting a new scenario. So far, only buildings are supported via a new function Tribes::add_custom_building{table} in lua_root.

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1705950 in widelands: "empire mission 4"
  https://bugs.launchpad.net/widelands/+bug/1705950

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

Reworked the tribe loading to make it more moddable. I'd like this branch to have priority, because it affects hessenfarmer's work on the Empire 4 scenario.

An adjusted version of the new scenario for testing is available in https://code.launchpad.net/~widelands-dev/widelands/dynamic_tribe_loading_datadir
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/dynamic_tribe_loading into lp:widelands.
=== modified file 'data/tribes/init.lua'
--- data/tribes/init.lua	2017-02-12 09:10:57 +0000
+++ data/tribes/init.lua	2017-08-17 11:49:32 +0000
@@ -11,12 +11,33 @@
 --
 -- Basic load order (first wares, then immovables etc.) is important,
 -- because checks will be made in C++.
--- Also, enhanced/upgraded units need to come before their basic units.
 --
 
 tribes = wl.Tribes()
 include "scripting/mapobjects.lua"
 
+-- Load all init.lua files in the given table of directory names
+function load_directories(directories)
+   -- Helper function to check for file name endings
+   function string.ends(haystack, needle)
+      return needle == '' or string.sub(haystack, -string.len(needle)) == needle
+   end
+
+   while #directories > 0 do
+      local filepath = directories[1]
+      table.remove(directories, 1)
+      if path.is_directory(filepath) then
+         for idx, listed_path in ipairs(path.list_directory(filepath)) do
+            if path.is_directory(listed_path) then
+               table.insert(directories, listed_path)
+            elseif string.ends(listed_path , "init.lua") then
+               include(listed_path)
+            end
+         end
+      end
+   end
+end
+
 print("┏━ Running Lua for tribes:")
 
 print_loading_message("┗━ took", function()
@@ -25,9 +46,7 @@
    -- ===================================
 
    print_loading_message("┃    Ships", function()
-      include "tribes/ships/atlanteans/init.lua"
-      include "tribes/ships/barbarians/init.lua"
-      include "tribes/ships/empire/init.lua"
+      load_directories({"tribes/ships"})
    end)
 
    -- ===================================
@@ -35,91 +54,7 @@
    -- ===================================
 
    print_loading_message("┃    Wares", function()
-      include "tribes/wares/armor/init.lua"
-      include "tribes/wares/armor_chain/init.lua"
-      include "tribes/wares/armor_gilded/init.lua"
-      include "tribes/wares/armor_helmet/init.lua"
-      include "tribes/wares/ax/init.lua"
-      include "tribes/wares/ax_battle/init.lua"
-      include "tribes/wares/ax_broad/init.lua"
-      include "tribes/wares/ax_bronze/init.lua"
-      include "tribes/wares/ax_sharp/init.lua"
-      include "tribes/wares/ax_warriors/init.lua"
-      include "tribes/wares/basket/init.lua"
-      include "tribes/wares/beer/init.lua"
-      include "tribes/wares/beer_strong/init.lua"
-      include "tribes/wares/blackroot/init.lua"
-      include "tribes/wares/blackroot_flour/init.lua"
-      include "tribes/wares/blackwood/init.lua"
-      include "tribes/wares/bread_atlanteans/init.lua"
-      include "tribes/wares/bread_barbarians/init.lua"
-      include "tribes/wares/bread_empire/init.lua"
-      include "tribes/wares/bread_paddle/init.lua"
-      include "tribes/wares/buckets/init.lua"
-      include "tribes/wares/cloth/init.lua"
-      include "tribes/wares/coal/init.lua"
-      include "tribes/wares/corn/init.lua"
-      include "tribes/wares/cornmeal/init.lua"
-      include "tribes/wares/diamond/init.lua"
-      include "tribes/wares/felling_ax/init.lua"
-      include "tribes/wares/fire_tongs/init.lua"
-      include "tribes/wares/fish/init.lua"
-      include "tribes/wares/fishing_net/init.lua"
-      include "tribes/wares/fishing_rod/init.lua"
-      include "tribes/wares/flour/init.lua"
-      include "tribes/wares/gold/init.lua"
-      include "tribes/wares/gold_ore/init.lua"
-      include "tribes/wares/gold_thread/init.lua"
-      include "tribes/wares/granite/init.lua"
-      include "tribes/wares/grape/init.lua"
-      include "tribes/wares/grout/init.lua"
-      include "tribes/wares/hammer/init.lua"
-      include "tribes/wares/helmet/init.lua"
-      include "tribes/wares/helmet_mask/init.lua"
-      include "tribes/wares/helmet_warhelm/init.lua"
-      include "tribes/wares/hook_pole/init.lua"
-      include "tribes/wares/hunting_bow/init.lua"
-      include "tribes/wares/hunting_spear/init.lua"
-      include "tribes/wares/iron/init.lua"
-      include "tribes/wares/iron_ore/init.lua"
-      include "tribes/wares/kitchen_tools/init.lua"
-      include "tribes/wares/log/init.lua"
-      include "tribes/wares/marble/init.lua"
-      include "tribes/wares/marble_column/init.lua"
-      include "tribes/wares/meal/init.lua"
-      include "tribes/wares/meat/init.lua"
-      include "tribes/wares/milking_tongs/init.lua"
-      include "tribes/wares/pick/init.lua"
-      include "tribes/wares/planks/init.lua"
-      include "tribes/wares/quartz/init.lua"
-      include "tribes/wares/ration/init.lua"
-      include "tribes/wares/saw/init.lua"
-      include "tribes/wares/scythe/init.lua"
-      include "tribes/wares/shield_advanced/init.lua"
-      include "tribes/wares/shield_steel/init.lua"
-      include "tribes/wares/shovel/init.lua"
-      include "tribes/wares/smoked_fish/init.lua"
-      include "tribes/wares/smoked_meat/init.lua"
-      include "tribes/wares/snack/init.lua"
-      include "tribes/wares/spear/init.lua"
-      include "tribes/wares/spear_advanced/init.lua"
-      include "tribes/wares/spear_heavy/init.lua"
-      include "tribes/wares/spear_war/init.lua"
-      include "tribes/wares/spear_wooden/init.lua"
-      include "tribes/wares/spidercloth/init.lua"
-      include "tribes/wares/spider_silk/init.lua"
-      include "tribes/wares/tabard/init.lua"
-      include "tribes/wares/tabard_golden/init.lua"
-      include "tribes/wares/thatch_reed/init.lua"
-      include "tribes/wares/trident_double/init.lua"
-      include "tribes/wares/trident_heavy_double/init.lua"
-      include "tribes/wares/trident_light/init.lua"
-      include "tribes/wares/trident_long/init.lua"
-      include "tribes/wares/trident_steel/init.lua"
-      include "tribes/wares/water/init.lua"
-      include "tribes/wares/wheat/init.lua"
-      include "tribes/wares/wine/init.lua"
-      include "tribes/wares/wool/init.lua"
+      load_directories({"tribes/wares"})
    end)
 
    -- ===================================
@@ -127,44 +62,7 @@
    -- ===================================
 
    print_loading_message("┃    Immovables", function()
-      include "tribes/immovables/ashes/init.lua"
-      include "tribes/immovables/blackrootfield_harvested/init.lua"
-      include "tribes/immovables/blackrootfield_medium/init.lua"
-      include "tribes/immovables/blackrootfield_ripe/init.lua"
-      include "tribes/immovables/blackrootfield_small/init.lua"
-      include "tribes/immovables/blackrootfield_tiny/init.lua"
-      include "tribes/immovables/cornfield_harvested/init.lua"
-      include "tribes/immovables/cornfield_medium/init.lua"
-      include "tribes/immovables/cornfield_ripe/init.lua"
-      include "tribes/immovables/cornfield_small/init.lua"
-      include "tribes/immovables/cornfield_tiny/init.lua"
-      include "tribes/immovables/destroyed_building/init.lua"
-      include "tribes/immovables/field_harvested/init.lua"
-      include "tribes/immovables/field_medium/init.lua"
-      include "tribes/immovables/field_ripe/init.lua"
-      include "tribes/immovables/field_small/init.lua"
-      include "tribes/immovables/field_tiny/init.lua"
-      include "tribes/immovables/grapevine_medium/init.lua"
-      include "tribes/immovables/grapevine_ripe/init.lua"
-      include "tribes/immovables/grapevine_small/init.lua"
-      include "tribes/immovables/grapevine_tiny/init.lua"
-      include "tribes/immovables/reed_medium/init.lua"
-      include "tribes/immovables/reed_ripe/init.lua"
-      include "tribes/immovables/reed_small/init.lua"
-      include "tribes/immovables/reed_tiny/init.lua"
-      include "tribes/immovables/resi_coal1/init.lua"
-      include "tribes/immovables/resi_coal2/init.lua"
-      include "tribes/immovables/resi_gold1/init.lua"
-      include "tribes/immovables/resi_gold2/init.lua"
-      include "tribes/immovables/resi_iron1/init.lua"
-      include "tribes/immovables/resi_iron2/init.lua"
-      include "tribes/immovables/resi_none/init.lua"
-      include "tribes/immovables/resi_stones1/init.lua"
-      include "tribes/immovables/resi_stones2/init.lua"
-      include "tribes/immovables/resi_water1/init.lua"
-      include "tribes/immovables/shipconstruction_atlanteans/init.lua"
-      include "tribes/immovables/shipconstruction_barbarians/init.lua"
-      include "tribes/immovables/shipconstruction_empire/init.lua"
+      load_directories({"tribes/immovables"})
    end)
 
    -- ===================================
@@ -172,103 +70,7 @@
    -- ===================================
 
    print_loading_message("┃    Workers", function()
-      include "tribes/workers/atlanteans/carrier/init.lua"
-      include "tribes/workers/atlanteans/armorsmith/init.lua"
-      include "tribes/workers/atlanteans/baker/init.lua"
-      include "tribes/workers/atlanteans/blackroot_farmer/init.lua"
-      include "tribes/workers/atlanteans/builder/init.lua"
-      include "tribes/workers/atlanteans/charcoal_burner/init.lua"
-      include "tribes/workers/atlanteans/farmer/init.lua"
-      include "tribes/workers/atlanteans/fishbreeder/init.lua"
-      include "tribes/workers/atlanteans/fisher/init.lua"
-      include "tribes/workers/atlanteans/forester/init.lua"
-      include "tribes/workers/atlanteans/geologist/init.lua"
-      include "tribes/workers/atlanteans/horse/init.lua"
-      include "tribes/workers/atlanteans/horsebreeder/init.lua"
-      include "tribes/workers/atlanteans/hunter/init.lua"
-      include "tribes/workers/atlanteans/miller/init.lua"
-      include "tribes/workers/atlanteans/miner/init.lua"
-      include "tribes/workers/atlanteans/recruit/init.lua"
-      include "tribes/workers/atlanteans/sawyer/init.lua"
-      include "tribes/workers/atlanteans/scout/init.lua"
-      include "tribes/workers/atlanteans/shipwright/init.lua"
-      include "tribes/workers/atlanteans/smelter/init.lua"
-      include "tribes/workers/atlanteans/smoker/init.lua"
-      include "tribes/workers/atlanteans/soldier/init.lua"
-      include "tribes/workers/atlanteans/spiderbreeder/init.lua"
-      include "tribes/workers/atlanteans/stonecutter/init.lua"
-      include "tribes/workers/atlanteans/toolsmith/init.lua"
-      include "tribes/workers/atlanteans/trainer/init.lua"
-      include "tribes/workers/atlanteans/weaponsmith/init.lua"
-      include "tribes/workers/atlanteans/weaver/init.lua"
-      include "tribes/workers/atlanteans/woodcutter/init.lua"
-
-      include "tribes/workers/barbarians/carrier/init.lua"
-      include "tribes/workers/barbarians/baker/init.lua"
-      include "tribes/workers/barbarians/blacksmith_master/init.lua"
-      include "tribes/workers/barbarians/blacksmith/init.lua"
-      include "tribes/workers/barbarians/brewer_master/init.lua"
-      include "tribes/workers/barbarians/brewer/init.lua"
-      include "tribes/workers/barbarians/builder/init.lua"
-      include "tribes/workers/barbarians/cattlebreeder/init.lua"
-      include "tribes/workers/barbarians/charcoal_burner/init.lua"
-      include "tribes/workers/barbarians/farmer/init.lua"
-      include "tribes/workers/barbarians/fisher/init.lua"
-      include "tribes/workers/barbarians/gamekeeper/init.lua"
-      include "tribes/workers/barbarians/gardener/init.lua"
-      include "tribes/workers/barbarians/geologist/init.lua"
-      include "tribes/workers/barbarians/helmsmith/init.lua"
-      include "tribes/workers/barbarians/hunter/init.lua"
-      include "tribes/workers/barbarians/innkeeper/init.lua"
-      include "tribes/workers/barbarians/lime_burner/init.lua"
-      include "tribes/workers/barbarians/lumberjack/init.lua"
-      include "tribes/workers/barbarians/miner_master/init.lua"
-      include "tribes/workers/barbarians/miner_chief/init.lua"
-      include "tribes/workers/barbarians/miner/init.lua"
-      include "tribes/workers/barbarians/ox/init.lua"
-      include "tribes/workers/barbarians/ranger/init.lua"
-      include "tribes/workers/barbarians/recruit/init.lua"
-      include "tribes/workers/barbarians/scout/init.lua"
-      include "tribes/workers/barbarians/shipwright/init.lua"
-      include "tribes/workers/barbarians/smelter/init.lua"
-      include "tribes/workers/barbarians/soldier/init.lua"
-      include "tribes/workers/barbarians/stonemason/init.lua"
-      include "tribes/workers/barbarians/trainer/init.lua"
-      include "tribes/workers/barbarians/weaver/init.lua"
-
-      include "tribes/workers/empire/carrier/init.lua"
-      include "tribes/workers/empire/armorsmith/init.lua"
-      include "tribes/workers/empire/baker/init.lua"
-      include "tribes/workers/empire/brewer/init.lua"
-      include "tribes/workers/empire/builder/init.lua"
-      include "tribes/workers/empire/carpenter/init.lua"
-      include "tribes/workers/empire/charcoal_burner/init.lua"
-      include "tribes/workers/empire/donkey/init.lua"
-      include "tribes/workers/empire/donkeybreeder/init.lua"
-      include "tribes/workers/empire/farmer/init.lua"
-      include "tribes/workers/empire/fisher/init.lua"
-      include "tribes/workers/empire/forester/init.lua"
-      include "tribes/workers/empire/geologist/init.lua"
-      include "tribes/workers/empire/hunter/init.lua"
-      include "tribes/workers/empire/innkeeper/init.lua"
-      include "tribes/workers/empire/lumberjack/init.lua"
-      include "tribes/workers/empire/miller/init.lua"
-      include "tribes/workers/empire/miner_master/init.lua"
-      include "tribes/workers/empire/miner/init.lua"
-      include "tribes/workers/empire/pigbreeder/init.lua"
-      include "tribes/workers/empire/recruit/init.lua"
-      include "tribes/workers/empire/scout/init.lua"
-      include "tribes/workers/empire/shepherd/init.lua"
-      include "tribes/workers/empire/shipwright/init.lua"
-      include "tribes/workers/empire/smelter/init.lua"
-      include "tribes/workers/empire/soldier/init.lua"
-      include "tribes/workers/empire/stonemason/init.lua"
-      include "tribes/workers/empire/toolsmith/init.lua"
-      include "tribes/workers/empire/trainer/init.lua"
-      include "tribes/workers/empire/vinefarmer/init.lua"
-      include "tribes/workers/empire/vintner/init.lua"
-      include "tribes/workers/empire/weaponsmith/init.lua"
-      include "tribes/workers/empire/weaver/init.lua"
+      load_directories({"tribes/workers"})
    end)
 
    -- ===================================
@@ -276,17 +78,7 @@
    -- ===================================
 
    print_loading_message("┃    Warehouses", function()
-      include "tribes/buildings/warehouses/atlanteans/headquarters/init.lua"
-      include "tribes/buildings/warehouses/atlanteans/port/init.lua"
-      include "tribes/buildings/warehouses/atlanteans/warehouse/init.lua"
-      include "tribes/buildings/warehouses/barbarians/headquarters/init.lua"
-      include "tribes/buildings/warehouses/barbarians/headquarters_interim/init.lua"
-      include "tribes/buildings/warehouses/barbarians/port/init.lua"
-      include "tribes/buildings/warehouses/barbarians/warehouse/init.lua"
-      include "tribes/buildings/warehouses/empire/headquarters/init.lua"
-      include "tribes/buildings/warehouses/empire/headquarters_shipwreck/init.lua"
-      include "tribes/buildings/warehouses/empire/port/init.lua"
-      include "tribes/buildings/warehouses/empire/warehouse/init.lua"
+      load_directories({"tribes/buildings/warehouses"})
    end)
 
    -- ===================================
@@ -294,125 +86,7 @@
    -- ===================================
 
    print_loading_message("┃    Productionsites", function()
-      -- Atlanteans small
-      include "tribes/buildings/productionsites/atlanteans/quarry/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/woodcutters_house/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/foresters_house/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/fishers_house/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/fishbreeders_house/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/hunters_house/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/well/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/gold_spinning_mill/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/scouts_house/init.lua"
-      -- Atlanteans medium
-      include "tribes/buildings/productionsites/atlanteans/sawmill/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/smokery/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/mill/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/bakery/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/charcoal_kiln/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/smelting_works/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/shipyard/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/toolsmithy/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/weaponsmithy/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/armorsmithy/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/barracks/init.lua"
-
-      -- Atlanteans big
-      include "tribes/buildings/productionsites/atlanteans/horsefarm/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/farm/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/blackroot_farm/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/spiderfarm/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/weaving_mill/init.lua"
-
-      -- Atlanteans mines
-      include "tribes/buildings/productionsites/atlanteans/crystalmine/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/coalmine/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/ironmine/init.lua"
-      include "tribes/buildings/productionsites/atlanteans/goldmine/init.lua"
-      -- Barbarians small
-      include "tribes/buildings/productionsites/barbarians/quarry/init.lua"
-      include "tribes/buildings/productionsites/barbarians/lumberjacks_hut/init.lua"
-      include "tribes/buildings/productionsites/barbarians/rangers_hut/init.lua"
-      include "tribes/buildings/productionsites/barbarians/fishers_hut/init.lua"
-      include "tribes/buildings/productionsites/barbarians/hunters_hut/init.lua"
-      include "tribes/buildings/productionsites/barbarians/gamekeepers_hut/init.lua"
-      include "tribes/buildings/productionsites/barbarians/well/init.lua"
-      include "tribes/buildings/productionsites/barbarians/scouts_hut/init.lua"
-      -- Barbarians medium
-      include "tribes/buildings/productionsites/barbarians/wood_hardener/init.lua"
-      include "tribes/buildings/productionsites/barbarians/lime_kiln/init.lua"
-      include "tribes/buildings/productionsites/barbarians/reed_yard/init.lua"
-      include "tribes/buildings/productionsites/barbarians/bakery/init.lua"
-      include "tribes/buildings/productionsites/barbarians/brewery/init.lua"
-      include "tribes/buildings/productionsites/barbarians/micro_brewery/init.lua"
-      include "tribes/buildings/productionsites/barbarians/big_inn/init.lua"
-      include "tribes/buildings/productionsites/barbarians/inn/init.lua"
-      include "tribes/buildings/productionsites/barbarians/tavern/init.lua"
-      include "tribes/buildings/productionsites/barbarians/charcoal_kiln/init.lua"
-      include "tribes/buildings/productionsites/barbarians/smelting_works/init.lua"
-      include "tribes/buildings/productionsites/barbarians/shipyard/init.lua"
-      include "tribes/buildings/productionsites/barbarians/warmill/init.lua"
-      include "tribes/buildings/productionsites/barbarians/ax_workshop/init.lua"
-      include "tribes/buildings/productionsites/barbarians/metal_workshop/init.lua"
-      include "tribes/buildings/productionsites/barbarians/barracks/init.lua"
-
-      -- Barbarians big
-      include "tribes/buildings/productionsites/barbarians/cattlefarm/init.lua"
-      include "tribes/buildings/productionsites/barbarians/farm/init.lua"
-      include "tribes/buildings/productionsites/barbarians/weaving_mill/init.lua"
-      include "tribes/buildings/productionsites/barbarians/helmsmithy/init.lua"
-      -- Barbarians mines
-      include "tribes/buildings/productionsites/barbarians/granitemine/init.lua"
-      include "tribes/buildings/productionsites/barbarians/coalmine_deeper/init.lua"
-      include "tribes/buildings/productionsites/barbarians/coalmine_deep/init.lua"
-      include "tribes/buildings/productionsites/barbarians/coalmine/init.lua"
-      include "tribes/buildings/productionsites/barbarians/ironmine_deeper/init.lua"
-      include "tribes/buildings/productionsites/barbarians/ironmine_deep/init.lua"
-      include "tribes/buildings/productionsites/barbarians/ironmine/init.lua"
-      include "tribes/buildings/productionsites/barbarians/goldmine_deeper/init.lua"
-      include "tribes/buildings/productionsites/barbarians/goldmine_deep/init.lua"
-      include "tribes/buildings/productionsites/barbarians/goldmine/init.lua"
-      -- Empire small
-      include "tribes/buildings/productionsites/empire/quarry/init.lua"
-      include "tribes/buildings/productionsites/empire/lumberjacks_house/init.lua"
-      include "tribes/buildings/productionsites/empire/foresters_house/init.lua"
-      include "tribes/buildings/productionsites/empire/fishers_house/init.lua"
-      include "tribes/buildings/productionsites/empire/hunters_house/init.lua"
-      include "tribes/buildings/productionsites/empire/well/init.lua"
-      include "tribes/buildings/productionsites/empire/scouts_house/init.lua"
-      -- Empire medium
-      include "tribes/buildings/productionsites/empire/stonemasons_house/init.lua"
-      include "tribes/buildings/productionsites/empire/sawmill/init.lua"
-      include "tribes/buildings/productionsites/empire/mill/init.lua"
-      include "tribes/buildings/productionsites/empire/bakery/init.lua"
-      include "tribes/buildings/productionsites/empire/brewery/init.lua"
-      include "tribes/buildings/productionsites/empire/vineyard/init.lua"
-      include "tribes/buildings/productionsites/empire/winery/init.lua"
-      include "tribes/buildings/productionsites/empire/inn/init.lua"
-      include "tribes/buildings/productionsites/empire/tavern/init.lua"
-      include "tribes/buildings/productionsites/empire/charcoal_kiln/init.lua"
-      include "tribes/buildings/productionsites/empire/smelting_works/init.lua"
-      include "tribes/buildings/productionsites/empire/shipyard/init.lua"
-      include "tribes/buildings/productionsites/empire/toolsmithy/init.lua"
-      include "tribes/buildings/productionsites/empire/armorsmithy/init.lua"
-      -- Empire big
-      include "tribes/buildings/productionsites/empire/donkeyfarm/init.lua"
-      include "tribes/buildings/productionsites/empire/farm/init.lua"
-      include "tribes/buildings/productionsites/empire/piggery/init.lua"
-      include "tribes/buildings/productionsites/empire/sheepfarm/init.lua"
-      include "tribes/buildings/productionsites/empire/weaving_mill/init.lua"
-      include "tribes/buildings/productionsites/empire/weaponsmithy/init.lua"
-      include "tribes/buildings/productionsites/empire/barracks/init.lua"
-
-      -- Empire mines
-      include "tribes/buildings/productionsites/empire/coalmine_deep/init.lua"
-      include "tribes/buildings/productionsites/empire/coalmine/init.lua"
-      include "tribes/buildings/productionsites/empire/ironmine_deep/init.lua"
-      include "tribes/buildings/productionsites/empire/ironmine/init.lua"
-      include "tribes/buildings/productionsites/empire/marblemine_deep/init.lua"
-      include "tribes/buildings/productionsites/empire/marblemine/init.lua"
-      include "tribes/buildings/productionsites/empire/goldmine_deep/init.lua"
-      include "tribes/buildings/productionsites/empire/goldmine/init.lua"
+      load_directories({"tribes/buildings/productionsites"})
    end)
 
    -- ===================================
@@ -420,13 +94,7 @@
    -- ===================================
 
    print_loading_message("┃    Trainingsites", function()
-      include "tribes/buildings/trainingsites/atlanteans/dungeon/init.lua"
-      include "tribes/buildings/trainingsites/atlanteans/labyrinth/init.lua"
-      include "tribes/buildings/trainingsites/barbarians/battlearena/init.lua"
-      include "tribes/buildings/trainingsites/barbarians/trainingcamp/init.lua"
-      include "tribes/buildings/trainingsites/empire/colosseum/init.lua"
-      include "tribes/buildings/trainingsites/empire/arena/init.lua"
-      include "tribes/buildings/trainingsites/empire/trainingcamp/init.lua"
+      load_directories({"tribes/buildings/trainingsites"})
    end)
 
    -- ===================================
@@ -434,26 +102,7 @@
    -- ===================================
 
    print_loading_message("┃    Militarysites", function()
-      include "tribes/buildings/militarysites/atlanteans/guardhouse/init.lua"
-      include "tribes/buildings/militarysites/atlanteans/guardhall/init.lua"
-      include "tribes/buildings/militarysites/atlanteans/tower_small/init.lua"
-      include "tribes/buildings/militarysites/atlanteans/tower_high/init.lua"
-      include "tribes/buildings/militarysites/atlanteans/tower/init.lua"
-      include "tribes/buildings/militarysites/atlanteans/castle/init.lua"
-
-      include "tribes/buildings/militarysites/barbarians/sentry/init.lua"
-      include "tribes/buildings/militarysites/barbarians/barrier/init.lua"
-      include "tribes/buildings/militarysites/barbarians/tower/init.lua"
-      include "tribes/buildings/militarysites/barbarians/citadel/init.lua"
-      include "tribes/buildings/militarysites/barbarians/fortress/init.lua"
-
-      include "tribes/buildings/militarysites/empire/sentry/init.lua"
-      include "tribes/buildings/militarysites/empire/blockhouse/init.lua"
-      include "tribes/buildings/militarysites/empire/barrier/init.lua"
-      include "tribes/buildings/militarysites/empire/outpost/init.lua"
-      include "tribes/buildings/militarysites/empire/tower/init.lua"
-      include "tribes/buildings/militarysites/empire/castle/init.lua"
-      include "tribes/buildings/militarysites/empire/fortress/init.lua"
+      load_directories({"tribes/buildings/militarysites"})
    end)
 
    -- ===================================
@@ -461,8 +110,7 @@
    -- ===================================
 
    print_loading_message("┃    Partially finished buildings", function()
-      include "tribes/buildings/partially_finished/constructionsite/init.lua"
-      include "tribes/buildings/partially_finished/dismantlesite/init.lua"
+      load_directories({"tribes/buildings/partially_finished"})
    end)
 
    -- ===================================

=== modified file 'src/game_io/game_loader.cc'
--- src/game_io/game_loader.cc	2017-07-02 21:00:58 +0000
+++ src/game_io/game_loader.cc	2017-08-17 11:49:32 +0000
@@ -84,6 +84,15 @@
 	M.read(fs_, game_);
 	log("Game: Reading Map Data took %ums\n", timer.ms_since_last_query());
 
+	// This has to be loaded after the map packet so that the map's filesystem will exist.
+	// The custom tribe scripts are saved when the map scripting packet is saved, but we need
+	// to load them as early as possible here.
+	if (fs_.file_exists("map/scripting/tribes/init.lua")) {
+		log("Game: Reading Scenario Tribes ... ");
+		game_.lua().run_script("map:scripting/tribes/init.lua");
+		log("Game: Reading Scenario Tribes took %ums\n", timer.ms_since_last_query());
+	}
+
 	log("Game: Reading Player Info ... ");
 	{
 		GamePlayerInfoPacket p;

=== modified file 'src/io/filewrite.cc'
--- src/io/filewrite.cc	2017-01-25 18:55:59 +0000
+++ src/io/filewrite.cc	2017-08-17 11:49:32 +0000
@@ -36,12 +36,12 @@
 	filepos_ = 0;
 }
 
-void FileWrite::write(FileSystem& fs, char const* const filename) {
+void FileWrite::write(FileSystem& fs, const std::string& filename) {
 	fs.write(filename, data_, length_);
 	clear();
 }
 
-void FileWrite::write_append(RealFSImpl& fs, char const* const filename) {
+void FileWrite::write_append(RealFSImpl& fs, const std::string& filename) {
 	fs.write(filename, data_, length_, true);
 	clear();
 }

=== modified file 'src/io/filewrite.h'
--- src/io/filewrite.h	2017-01-25 18:55:59 +0000
+++ src/io/filewrite.h	2017-08-17 11:49:32 +0000
@@ -76,11 +76,11 @@
 	/// Write the file out to disk. If successful, this clears the buffers.
 	/// Otherwise, an exception is thrown but the buffer remains intact (don't
 	/// worry, it will be cleared by the destructor).
-	void write(FileSystem& fs, char const* const filename);
+	void write(FileSystem& fs, const std::string& filename);
 
 	/// Same as above, just that the data is appended to the file
 	/// NOTE RealFSImpl is used by purpose - zip filesystems do not support appending
-	void write_append(RealFSImpl& fs, char const* const filename);
+	void write_append(RealFSImpl& fs, const std::string& filename);
 
 	/// Get the position that will be written to in the next write operation that
 	/// does not specify a position.

=== modified file 'src/logic/game.cc'
--- src/logic/game.cc	2017-08-16 13:23:15 +0000
+++ src/logic/game.cc	2017-08-17 11:49:32 +0000
@@ -205,6 +205,12 @@
 	loader_ui.step(_("Loading tribes"));
 	tribes();
 
+	// If the scenario has custrom tribe entites, load them.
+	const std::string custom_tribe_script = mapname + "/scripting/tribes/init.lua";
+	if (g_fs->file_exists(custom_tribe_script)) {
+		lua().run_script(custom_tribe_script);
+	}
+
 	// We have to create the players here.
 	loader_ui.step(_("Creating players"));
 	PlayerNumber const nr_players = map().get_nrplayers();

=== modified file 'src/logic/map_objects/tribes/building.cc'
--- src/logic/map_objects/tribes/building.cc	2017-08-16 04:31:56 +0000
+++ src/logic/map_objects/tribes/building.cc	2017-08-17 11:49:32 +0000
@@ -57,9 +57,9 @@
 BuildingDescr::BuildingDescr(const std::string& init_descname,
                              const MapObjectType init_type,
                              const LuaTable& table,
-                             const EditorGameBase& egbase)
+                             EditorGameBase* egbase)
    : MapObjectDescr(init_type, table.get_string("name"), init_descname, table),
-     egbase_(egbase),
+     egbase_(*egbase),
      buildable_(false),
      size_(BaseImmovable::SMALL),
      mine_(false),
@@ -112,22 +112,11 @@
 		if (enh == name()) {
 			throw wexception("enhancement to same type");
 		}
-		DescriptionIndex const en_i = egbase_.tribes().building_index(enh);
-		if (egbase_.tribes().building_exists(en_i)) {
-			enhancement_ = en_i;
-
-			//  Merge the enhancements workarea info into this building's
-			//  workarea info.
-			const BuildingDescr* tmp_enhancement = egbase_.tribes().get_building_descr(en_i);
-			for (auto area : tmp_enhancement->workarea_info_) {
-				std::set<std::string>& strs = workarea_info_[area.first];
-				for (std::string str : area.second)
-					strs.insert(str);
-			}
+		if (egbase_.tribes().building_exists(enh)) {
+			set_enhances_to(enh);
 		} else {
-			throw wexception(
-			   "\"%s\" has not been defined as a building type (wrong declaration order?)",
-			   enh.c_str());
+			// The advanced building wasn't loaded yet, so we'll try this again in postload.
+			egbase->mutable_tribes()->add_mapobject_enhancement({MapObjectType::BUILDING, name(), enh});
 		}
 	}
 
@@ -164,6 +153,18 @@
 	}
 }
 
+void BuildingDescr::set_enhances_to(const std::string& name) {
+	enhancement_ = egbase_.tribes().safe_building_index(name);
+
+	//  Merge the enhancements workarea info into this building's
+	//  workarea info.
+	for (auto area : egbase_.tribes().get_building_descr(enhancement_)->workarea_info_) {
+		std::set<std::string>& strs = workarea_info_[area.first];
+		for (std::string str : area.second)
+			strs.insert(str);
+	}
+}
+
 Building& BuildingDescr::create(EditorGameBase& egbase,
                                 Player& owner,
                                 Coords const pos,

=== modified file 'src/logic/map_objects/tribes/building.h'
--- src/logic/map_objects/tribes/building.h	2017-08-09 17:55:34 +0000
+++ src/logic/map_objects/tribes/building.h	2017-08-17 11:49:32 +0000
@@ -66,7 +66,7 @@
 	BuildingDescr(const std::string& init_descname,
 	              MapObjectType type,
 	              const LuaTable& t,
-	              const EditorGameBase& egbase);
+	              EditorGameBase* egbase);
 	~BuildingDescr() override {
 	}
 
@@ -80,6 +80,9 @@
 		return enhanced_building_;
 	}
 
+	/// Define a building that this building can be enhanced to. 'name' is the descr().name() of the other building.
+	void set_enhances_to(const std::string& name);
+
 	/**
 	 * The build cost for direct construction
 	 */

=== modified file 'src/logic/map_objects/tribes/carrier.cc'
--- src/logic/map_objects/tribes/carrier.cc	2017-04-22 06:45:52 +0000
+++ src/logic/map_objects/tribes/carrier.cc	2017-08-17 11:49:32 +0000
@@ -568,7 +568,7 @@
 
 CarrierDescr::CarrierDescr(const std::string& init_descname,
                            const LuaTable& table,
-                           const EditorGameBase& egbase)
+                           EditorGameBase* egbase)
    : WorkerDescr(init_descname, MapObjectType::CARRIER, table, egbase),
      ware_hotspot_(Vector2i(0, 15)) {
 	if (table.has_key("ware_hotspot")) {

=== modified file 'src/logic/map_objects/tribes/carrier.h'
--- src/logic/map_objects/tribes/carrier.h	2017-06-25 19:12:30 +0000
+++ src/logic/map_objects/tribes/carrier.h	2017-08-17 11:49:32 +0000
@@ -29,7 +29,7 @@
 public:
 	CarrierDescr(const std::string& init_descname,
 	             const LuaTable& table,
-	             const EditorGameBase& egbase);
+	             EditorGameBase* egbase);
 	~CarrierDescr() override {
 	}
 

=== modified file 'src/logic/map_objects/tribes/constructionsite.cc'
--- src/logic/map_objects/tribes/constructionsite.cc	2017-06-23 16:16:28 +0000
+++ src/logic/map_objects/tribes/constructionsite.cc	2017-08-17 11:49:32 +0000
@@ -45,7 +45,7 @@
   */
 ConstructionSiteDescr::ConstructionSiteDescr(const std::string& init_descname,
                                              const LuaTable& table,
-                                             const EditorGameBase& egbase)
+                                             EditorGameBase* egbase)
    : BuildingDescr(init_descname, MapObjectType::CONSTRUCTIONSITE, table, egbase) {
 	add_attribute(MapObject::CONSTRUCTIONSITE);
 }

=== modified file 'src/logic/map_objects/tribes/constructionsite.h'
--- src/logic/map_objects/tribes/constructionsite.h	2017-06-24 08:47:46 +0000
+++ src/logic/map_objects/tribes/constructionsite.h	2017-08-17 11:49:32 +0000
@@ -65,7 +65,7 @@
 public:
 	ConstructionSiteDescr(const std::string& init_descname,
 	                      const LuaTable& t,
-	                      const EditorGameBase& egbase);
+	                      EditorGameBase* egbase);
 	~ConstructionSiteDescr() override {
 	}
 

=== modified file 'src/logic/map_objects/tribes/dismantlesite.cc'
--- src/logic/map_objects/tribes/dismantlesite.cc	2017-04-23 12:11:19 +0000
+++ src/logic/map_objects/tribes/dismantlesite.cc	2017-08-17 11:49:32 +0000
@@ -45,7 +45,7 @@
 
 DismantleSiteDescr::DismantleSiteDescr(const std::string& init_descname,
                                        const LuaTable& table,
-                                       const EditorGameBase& egbase)
+                                       EditorGameBase* egbase)
    : BuildingDescr(init_descname, MapObjectType::DISMANTLESITE, table, egbase) {
 	add_attribute(MapObject::Attribute::CONSTRUCTIONSITE);  // Yep, this is correct.
 }

=== modified file 'src/logic/map_objects/tribes/dismantlesite.h'
--- src/logic/map_objects/tribes/dismantlesite.h	2017-06-25 19:12:30 +0000
+++ src/logic/map_objects/tribes/dismantlesite.h	2017-08-17 11:49:32 +0000
@@ -47,7 +47,7 @@
 public:
 	DismantleSiteDescr(const std::string& init_descname,
 	                   const LuaTable& t,
-	                   const EditorGameBase& egbase);
+	                   EditorGameBase* egbase);
 	~DismantleSiteDescr() override {
 	}
 

=== modified file 'src/logic/map_objects/tribes/militarysite.cc'
--- src/logic/map_objects/tribes/militarysite.cc	2017-08-16 13:23:15 +0000
+++ src/logic/map_objects/tribes/militarysite.cc	2017-08-17 11:49:32 +0000
@@ -296,7 +296,7 @@
   */
 MilitarySiteDescr::MilitarySiteDescr(const std::string& init_descname,
                                      const LuaTable& table,
-                                     const EditorGameBase& egbase)
+                                     EditorGameBase* egbase)
    : BuildingDescr(init_descname, MapObjectType::MILITARYSITE, table, egbase),
      conquer_radius_(0),
      num_soldiers_(0),

=== modified file 'src/logic/map_objects/tribes/militarysite.h'
--- src/logic/map_objects/tribes/militarysite.h	2017-08-16 13:23:15 +0000
+++ src/logic/map_objects/tribes/militarysite.h	2017-08-17 11:49:32 +0000
@@ -45,7 +45,7 @@
 public:
 	MilitarySiteDescr(const std::string& init_descname,
 	                  const LuaTable& t,
-	                  const EditorGameBase& egbase);
+	                  EditorGameBase* egbase);
 	~MilitarySiteDescr() override {
 	}
 

=== modified file 'src/logic/map_objects/tribes/productionsite.cc'
--- src/logic/map_objects/tribes/productionsite.cc	2017-08-16 04:31:56 +0000
+++ src/logic/map_objects/tribes/productionsite.cc	2017-08-17 11:49:32 +0000
@@ -63,7 +63,7 @@
                                          const std::string& msgctxt,
                                          MapObjectType init_type,
                                          const LuaTable& table,
-                                         const EditorGameBase& egbase)
+                                         EditorGameBase* egbase)
    : BuildingDescr(init_descname, init_type, table, egbase),
      out_of_resource_productivity_threshold_(100) {
 	i18n::Textdomain td("tribes");
@@ -86,15 +86,15 @@
 	if (table.has_key("outputs")) {
 		for (const std::string& output : table.get_table("outputs")->array_entries<std::string>()) {
 			try {
-				DescriptionIndex idx = egbase.tribes().ware_index(output);
-				if (egbase.tribes().ware_exists(idx)) {
+				DescriptionIndex idx = egbase->tribes().ware_index(output);
+				if (egbase->tribes().ware_exists(idx)) {
 					if (output_ware_types_.count(idx)) {
 						throw wexception("this ware type has already been declared as an output");
 					}
 					output_ware_types_.insert(idx);
 				} else {
-					idx = egbase.tribes().worker_index(output);
-					if (egbase.tribes().worker_exists(idx)) {
+					idx = egbase->tribes().worker_index(output);
+					if (egbase->tribes().worker_exists(idx)) {
 						if (output_worker_types_.count(idx)) {
 							throw wexception("this worker type has already been declared as an output");
 						}
@@ -119,8 +119,8 @@
 				if (amount < 1 || 255 < amount) {
 					throw wexception("amount is out of range 1 .. 255");
 				}
-				DescriptionIndex idx = egbase.tribes().ware_index(ware_name);
-				if (egbase.tribes().ware_exists(idx)) {
+				DescriptionIndex idx = egbase->tribes().ware_index(ware_name);
+				if (egbase->tribes().ware_exists(idx)) {
 					for (const auto& temp_inputs : input_wares()) {
 						if (temp_inputs.first == idx) {
 							throw wexception("duplicated");
@@ -128,8 +128,8 @@
 					}
 					input_wares_.push_back(WareAmount(idx, amount));
 				} else {
-					idx = egbase.tribes().worker_index(ware_name);
-					if (egbase.tribes().worker_exists(idx)) {
+					idx = egbase->tribes().worker_index(ware_name);
+					if (egbase->tribes().worker_exists(idx)) {
 						for (const auto& temp_inputs : input_workers()) {
 							if (temp_inputs.first == idx) {
 								throw wexception("duplicated");
@@ -153,8 +153,8 @@
 			if (amount < 1 || 255 < amount) {
 				throw wexception("count is out of range 1 .. 255");
 			}
-			DescriptionIndex const woi = egbase.tribes().worker_index(worker_name);
-			if (egbase.tribes().worker_exists(woi)) {
+			DescriptionIndex const woi = egbase->tribes().worker_index(worker_name);
+			if (egbase->tribes().worker_exists(woi)) {
 				for (const auto& wp : working_positions()) {
 					if (wp.first == woi) {
 						throw wexception("duplicated");
@@ -187,7 +187,7 @@
 				program_descname = pgettext_expr(msgctxt.c_str(), program_descname_unlocalized.c_str());
 			}
 			programs_[program_name] = std::unique_ptr<ProductionProgram>(new ProductionProgram(
-			   program_name, program_descname, program_table->get_table("actions"), egbase, this));
+			   program_name, program_descname, program_table->get_table("actions"), *egbase, this));
 		} catch (const std::exception& e) {
 			throw wexception("program %s: %s", program_name.c_str(), e.what());
 		}
@@ -197,7 +197,7 @@
 ProductionSiteDescr::ProductionSiteDescr(const std::string& init_descname,
                                          const std::string& msgctxt,
                                          const LuaTable& table,
-                                         const EditorGameBase& egbase)
+                                         EditorGameBase* egbase)
    : ProductionSiteDescr(init_descname, msgctxt, MapObjectType::PRODUCTIONSITE, table, egbase) {
 }
 

=== modified file 'src/logic/map_objects/tribes/productionsite.h'
--- src/logic/map_objects/tribes/productionsite.h	2017-06-24 08:47:46 +0000
+++ src/logic/map_objects/tribes/productionsite.h	2017-08-17 11:49:32 +0000
@@ -63,11 +63,11 @@
 	                    const std::string& msgctxt,
 	                    MapObjectType type,
 	                    const LuaTable& t,
-	                    const EditorGameBase& egbase);
+	                    EditorGameBase* egbase);
 	ProductionSiteDescr(const std::string& init_descname,
 	                    const std::string& msgctxt,
 	                    const LuaTable& t,
-	                    const EditorGameBase& egbase);
+	                    EditorGameBase* egbase);
 
 	Building& create_object() const override;
 

=== modified file 'src/logic/map_objects/tribes/soldier.cc'
--- src/logic/map_objects/tribes/soldier.cc	2017-07-05 19:21:57 +0000
+++ src/logic/map_objects/tribes/soldier.cc	2017-08-17 11:49:32 +0000
@@ -63,7 +63,7 @@
 
 SoldierDescr::SoldierDescr(const std::string& init_descname,
                            const LuaTable& table,
-                           const EditorGameBase& egbase)
+                           EditorGameBase* egbase)
    : WorkerDescr(init_descname, MapObjectType::SOLDIER, table, egbase),
      health_(table.get_table("health")),
      attack_(table.get_table("attack")),

=== modified file 'src/logic/map_objects/tribes/soldier.h'
--- src/logic/map_objects/tribes/soldier.h	2017-06-24 08:47:46 +0000
+++ src/logic/map_objects/tribes/soldier.h	2017-08-17 11:49:32 +0000
@@ -41,7 +41,7 @@
 public:
 	friend class Economy;
 
-	SoldierDescr(const std::string& init_descname, const LuaTable& t, const EditorGameBase& egbase);
+	SoldierDescr(const std::string& init_descname, const LuaTable& t, EditorGameBase* egbase);
 	~SoldierDescr() override {
 	}
 

=== modified file 'src/logic/map_objects/tribes/trainingsite.cc'
--- src/logic/map_objects/tribes/trainingsite.cc	2017-06-24 20:22:19 +0000
+++ src/logic/map_objects/tribes/trainingsite.cc	2017-08-17 11:49:32 +0000
@@ -47,7 +47,7 @@
   */
 TrainingSiteDescr::TrainingSiteDescr(const std::string& init_descname,
                                      const LuaTable& table,
-                                     const EditorGameBase& egbase)
+                                     EditorGameBase* egbase)
    : ProductionSiteDescr(init_descname, "", MapObjectType::TRAININGSITE, table, egbase),
      num_soldiers_(table.get_int("soldier_capacity")),
      max_stall_(table.get_int("trainer_patience")),

=== modified file 'src/logic/map_objects/tribes/trainingsite.h'
--- src/logic/map_objects/tribes/trainingsite.h	2017-06-25 19:12:30 +0000
+++ src/logic/map_objects/tribes/trainingsite.h	2017-08-17 11:49:32 +0000
@@ -35,7 +35,7 @@
 public:
 	TrainingSiteDescr(const std::string& init_descname,
 	                  const LuaTable& table,
-	                  const EditorGameBase& egbase);
+	                  EditorGameBase* egbase);
 	~TrainingSiteDescr() override {
 	}
 

=== modified file 'src/logic/map_objects/tribes/tribe_descr.cc'
--- src/logic/map_objects/tribes/tribe_descr.cc	2017-07-19 20:40:32 +0000
+++ src/logic/map_objects/tribes/tribe_descr.cc	2017-08-17 11:49:32 +0000
@@ -165,67 +165,10 @@
 
 		for (const std::string& buildingname :
 		     table.get_table("buildings")->array_entries<std::string>()) {
-			try {
-				DescriptionIndex index = tribes_.safe_building_index(buildingname);
-				if (has_building(index)) {
-					throw GameDataError("Duplicate definition of building '%s'", buildingname.c_str());
-				}
-				buildings_.push_back(index);
-
-				// Register trainigsites
-				if (get_building_descr(index)->type() == MapObjectType::TRAININGSITE) {
-					trainingsites_.push_back(index);
-				}
-
-				// Register construction materials
-				for (const auto& build_cost : get_building_descr(index)->buildcost()) {
-					if (!is_construction_material(build_cost.first)) {
-						construction_materials_.insert(build_cost.first);
-					}
-				}
-				for (const auto& enhancement_cost : get_building_descr(index)->enhancement_cost()) {
-					if (!is_construction_material(enhancement_cost.first)) {
-						construction_materials_.insert(enhancement_cost.first);
-					}
-				}
-			} catch (const WException& e) {
-				throw GameDataError("Failed adding building '%s': %s", buildingname.c_str(), e.what());
-			}
-		}
-
-		// Set default trainingsites proportions for AI. Make sure that we get a sum of ca. 100
-		float trainingsites_without_percent = 0.f;
-		int used_percent = 0;
-		for (const DescriptionIndex& index : trainingsites_) {
-			const BuildingDescr& descr = *tribes_.get_building_descr(index);
-			if (descr.hints().trainingsites_max_percent() == 0) {
-				++trainingsites_without_percent;
-			} else {
-				used_percent += descr.hints().trainingsites_max_percent();
-			}
-		}
-		if (trainingsites_without_percent > 0.f && used_percent > 100) {
-			throw GameDataError(
-			   "Predefined training sites proportions add up to > 100%%: %d", used_percent);
-		} else if (trainingsites_without_percent > 0) {
-			const int percent_to_use = std::ceil((100 - used_percent) / trainingsites_without_percent);
-			if (percent_to_use < 1) {
-				throw GameDataError("Training sites without predefined proportions add up to < 1%% and "
-				                    "will never be built: %d",
-				                    used_percent);
-			}
-			for (const DescriptionIndex& index : trainingsites_) {
-				BuildingDescr* descr = tribes_.get_mutable_building_descr(index);
-				if (descr->hints().trainingsites_max_percent() == 0) {
-					descr->set_hints_trainingsites_max_percent(percent_to_use);
-					used_percent += percent_to_use;
-				}
-			}
-		}
-		if (used_percent < 100) {
-			throw GameDataError(
-			   "Final training sites proportions add up to < 100%%: %d", used_percent);
-		}
+			add_building(buildingname);
+		}
+
+		update_trainingsites_proportions();
 
 		// Special types
 		builder_ = add_special_worker(table.get_string("builder"));
@@ -503,6 +446,99 @@
 	}
 }
 
+void TribeDescr::add_building(const std::string& buildingname) {
+	try {
+		DescriptionIndex index = tribes_.safe_building_index(buildingname);
+		if (has_building(index)) {
+			throw GameDataError("Duplicate definition of building '%s'", buildingname.c_str());
+		}
+		buildings_.push_back(index);
+
+		// Register trainigsites
+		if (get_building_descr(index)->type() == MapObjectType::TRAININGSITE) {
+			trainingsites_.push_back(index);
+		}
+
+		// Register construction materials
+		for (const auto& build_cost : get_building_descr(index)->buildcost()) {
+			if (!is_construction_material(build_cost.first)) {
+				construction_materials_.insert(build_cost.first);
+			}
+		}
+		for (const auto& enhancement_cost : get_building_descr(index)->enhancement_cost()) {
+			if (!is_construction_material(enhancement_cost.first)) {
+				construction_materials_.insert(enhancement_cost.first);
+			}
+		}
+	} catch (const WException& e) {
+		throw GameDataError("Failed adding building '%s': %s", buildingname.c_str(), e.what());
+	}
+}
+
+void TribeDescr::update_trainingsites_proportions(const std::string& new_site) {
+	// Set default trainingsites proportions for AI. Make sure that we get a sum of ca. 100
+	float trainingsites_without_percent = 0.f;
+	int used_percent = 0;
+	if (new_site.empty()) {
+		for (const DescriptionIndex& index : trainingsites_) {
+			const BuildingDescr& descr = *tribes_.get_building_descr(index);
+			if (descr.hints().trainingsites_max_percent() == 0) {
+				++trainingsites_without_percent;
+			} else {
+				used_percent += descr.hints().trainingsites_max_percent();
+			}
+		}
+		if (trainingsites_without_percent > 0.f && used_percent > 100) {
+			throw GameDataError(
+				"Predefined training sites proportions add up to > 100%%: %d", used_percent);
+		} else if (trainingsites_without_percent > 0) {
+			const int percent_to_use = std::ceil((100 - used_percent) / trainingsites_without_percent);
+			if (percent_to_use < 1) {
+				throw GameDataError("Training sites without predefined proportions add up to < 1%% and "
+										  "will never be built: %d",
+										  used_percent);
+			}
+			for (const DescriptionIndex& index : trainingsites_) {
+				BuildingDescr* descr = tribes_.get_mutable_building_descr(index);
+				if (descr->hints().trainingsites_max_percent() == 0) {
+					descr->set_hints_trainingsites_max_percent(percent_to_use);
+					used_percent += percent_to_use;
+				}
+			}
+		}
+	} else {
+		// Adjust percentages to include a custom scenario training site.
+		assert(!trainingsites().empty());
+
+		// Make sure that the new site gets a fair share.
+		BuildingDescr* newsite_descr = tribes_.get_mutable_building_descr(tribes_.safe_building_index(new_site));
+		if (newsite_descr->hints().trainingsites_max_percent() == 0) {
+			newsite_descr->set_hints_trainingsites_max_percent(100 / trainingsites().size());
+		}
+
+		// See what we have.
+		int total_percent = 0;
+		for (const DescriptionIndex& index : trainingsites_) {
+			const BuildingDescr* descr = tribes_.get_building_descr(index);
+			total_percent += descr->hints().trainingsites_max_percent();
+		}
+
+		// Subtract the surplus.
+		const int percent_to_subtract = std::ceil((total_percent - 100)  / trainingsites().size());
+		for (const DescriptionIndex& index : trainingsites_) {
+			BuildingDescr* descr = tribes_.get_mutable_building_descr(index);
+			if (descr->name() != newsite_descr->name()) {
+				descr->set_hints_trainingsites_max_percent(descr->hints().trainingsites_max_percent() - percent_to_subtract);
+			}
+			used_percent += descr->hints().trainingsites_max_percent();
+		}
+	}
+	if (used_percent < 100) {
+		throw GameDataError(
+		   "Final training sites proportions add up to < 100%%: %d", used_percent);
+	}
+}
+
 /**
   * Helper functions
   */

=== modified file 'src/logic/map_objects/tribes/tribe_descr.h'
--- src/logic/map_objects/tribes/tribe_descr.h	2017-07-19 20:40:32 +0000
+++ src/logic/map_objects/tribes/tribe_descr.h	2017-08-17 11:49:32 +0000
@@ -157,6 +157,9 @@
 		return ship_names_;
 	}
 
+	void add_building(const std::string& buildingname);
+	void update_trainingsites_proportions(const std::string& new_site = "");
+
 private:
 	// Helper function for adding a special worker type (carriers etc.)
 	DescriptionIndex add_special_worker(const std::string& workername);

=== modified file 'src/logic/map_objects/tribes/tribes.cc'
--- src/logic/map_objects/tribes/tribes.cc	2017-03-23 07:36:36 +0000
+++ src/logic/map_objects/tribes/tribes.cc	2017-08-17 11:49:32 +0000
@@ -78,28 +78,28 @@
      tribes_(new DescriptionMaintainer<TribeDescr>()) {
 }
 
-void Tribes::add_constructionsite_type(const LuaTable& table, const EditorGameBase& egbase) {
+void Tribes::add_constructionsite_type(const LuaTable& table, EditorGameBase* egbase) {
 	i18n::Textdomain td("tribes");
 	buildings_->add(new ConstructionSiteDescr(
 	   pgettext_expr(table.get_string("msgctxt").c_str(), table.get_string("descname").c_str()),
 	   table, egbase));
 }
 
-void Tribes::add_dismantlesite_type(const LuaTable& table, const EditorGameBase& egbase) {
+void Tribes::add_dismantlesite_type(const LuaTable& table, EditorGameBase* egbase) {
 	i18n::Textdomain td("tribes");
 	buildings_->add(new DismantleSiteDescr(
 	   pgettext_expr(table.get_string("msgctxt").c_str(), table.get_string("descname").c_str()),
 	   table, egbase));
 }
 
-void Tribes::add_militarysite_type(const LuaTable& table, const EditorGameBase& egbase) {
+void Tribes::add_militarysite_type(const LuaTable& table, EditorGameBase* egbase) {
 	i18n::Textdomain td("tribes");
 	buildings_->add(new MilitarySiteDescr(
 	   pgettext_expr(table.get_string("msgctxt").c_str(), table.get_string("descname").c_str()),
 	   table, egbase));
 }
 
-void Tribes::add_productionsite_type(const LuaTable& table, const EditorGameBase& egbase) {
+void Tribes::add_productionsite_type(const LuaTable& table, EditorGameBase* egbase) {
 	i18n::Textdomain td("tribes");
 	const std::string msgctxt = table.get_string("msgctxt");
 	buildings_->add(
@@ -107,14 +107,14 @@
 	                           msgctxt, table, egbase));
 }
 
-void Tribes::add_trainingsite_type(const LuaTable& table, const EditorGameBase& egbase) {
+void Tribes::add_trainingsite_type(const LuaTable& table, EditorGameBase* egbase) {
 	i18n::Textdomain td("tribes");
 	buildings_->add(new TrainingSiteDescr(
 	   pgettext_expr(table.get_string("msgctxt").c_str(), table.get_string("descname").c_str()),
 	   table, egbase));
 }
 
-void Tribes::add_warehouse_type(const LuaTable& table, const EditorGameBase& egbase) {
+void Tribes::add_warehouse_type(const LuaTable& table, EditorGameBase* egbase) {
 	i18n::Textdomain td("tribes");
 	buildings_->add(new WarehouseDescr(
 	   pgettext_expr(table.get_string("msgctxt").c_str(), table.get_string("descname").c_str()),
@@ -140,21 +140,21 @@
 	   table));
 }
 
-void Tribes::add_carrier_type(const LuaTable& table, const EditorGameBase& egbase) {
+void Tribes::add_carrier_type(const LuaTable& table, EditorGameBase* egbase) {
 	i18n::Textdomain td("tribes");
 	workers_->add(new CarrierDescr(
 	   pgettext_expr(table.get_string("msgctxt").c_str(), table.get_string("descname").c_str()),
 	   table, egbase));
 }
 
-void Tribes::add_soldier_type(const LuaTable& table, const EditorGameBase& egbase) {
+void Tribes::add_soldier_type(const LuaTable& table, EditorGameBase* egbase) {
 	i18n::Textdomain td("tribes");
 	workers_->add(new SoldierDescr(
 	   pgettext_expr(table.get_string("msgctxt").c_str(), table.get_string("descname").c_str()),
 	   table, egbase));
 }
 
-void Tribes::add_worker_type(const LuaTable& table, const EditorGameBase& egbase) {
+void Tribes::add_worker_type(const LuaTable& table, EditorGameBase* egbase) {
 	i18n::Textdomain td("tribes");
 	workers_->add(new WorkerDescr(
 	   pgettext_expr(table.get_string("msgctxt").c_str(), table.get_string("descname").c_str()),
@@ -170,6 +170,20 @@
 	}
 }
 
+void Tribes::add_custom_building(const LuaTable& table) {
+	const std::string tribename = table.get_string("tribename");
+	if (Widelands::tribe_exists(tribename)) {
+		TribeDescr* descr = tribes_->get_mutable(tribe_index(tribename));
+		const std::string buildingname = table.get_string("buildingname");
+		descr->add_building(buildingname);
+		if (descr->get_building_descr(descr->building_index(buildingname))->type() == MapObjectType::TRAININGSITE) {
+			descr->update_trainingsites_proportions(buildingname);
+		}
+	} else {
+		throw GameDataError("The tribe '%s'' has no preload file.", tribename.c_str());
+	}
+}
+
 size_t Tribes::nrbuildings() const {
 	return buildings_->size();
 }
@@ -189,6 +203,9 @@
 bool Tribes::ware_exists(const DescriptionIndex& index) const {
 	return wares_->get_mutable(index) != nullptr;
 }
+bool Tribes::worker_exists(const std::string& workername) const {
+	return workers_->exists(workername) != nullptr;
+}
 bool Tribes::worker_exists(const DescriptionIndex& index) const {
 	return workers_->get_mutable(index) != nullptr;
 }
@@ -329,7 +346,61 @@
 	}
 }
 
+void Tribes::add_worker_buildcost(const WorkerBuildcost& buildcost) {
+	postload_workers_buildcost_.push_back(buildcost);
+}
+
+void Tribes::add_mapobject_enhancement(const MapObjectEnhancement& becomes) {
+	postload_mapobject_enhancements_.push_back(becomes);
+}
+
 void Tribes::postload() {
+	// Some workers have other workers as build cost, so we postload those in order to perform the necessary checks.
+	for (const WorkerBuildcost& buildcost : postload_workers_buildcost_) {
+		if (!worker_exists(buildcost.worker)) {
+			throw GameDataError(
+			   "Trying to add a worker buildcost to non-existing worker '%s'", buildcost.worker.c_str());
+		}
+		if (!worker_exists(buildcost.needed_worker)) {
+			throw GameDataError(
+			   "The worker '%s' to be added as builcost to the worker '%s' does not exist", buildcost.needed_worker.c_str(), buildcost.worker.c_str());
+		}
+		workers_->get_mutable(safe_worker_index(buildcost.worker))->add_worker_to_buildcost(buildcost.needed_worker, buildcost.quantity);
+	}
+	postload_workers_buildcost_.clear();
+
+	// Likewise, more experienced workers and advanced buildings might not have been available yet when a lower-level worker or building was loaded.
+	for (const MapObjectEnhancement& enhancement : postload_mapobject_enhancements_) {
+		switch (enhancement.type) {
+		case MapObjectType::BUILDING:
+			if (!building_exists(enhancement.name)) {
+				throw GameDataError(
+				   "Trying to add a building enhancement to non-existing building '%s'", enhancement.name.c_str());
+			}
+			if (!building_exists(enhancement.enhanced_name)) {
+				throw GameDataError(
+				   "The building '%s' to be added as enhancement to the building '%s' does not exist", enhancement.enhanced_name.c_str(), enhancement.name.c_str());
+			}
+			buildings_->get_mutable(safe_building_index(enhancement.name))->set_enhances_to(enhancement.enhanced_name);
+			break;
+		case MapObjectType::WORKER:
+			if (!worker_exists(enhancement.name)) {
+				throw GameDataError(
+				   "Trying to add a worker enhancement to non-existing worker '%s'", enhancement.name.c_str());
+			}
+			if (!worker_exists(enhancement.enhanced_name)) {
+				throw GameDataError(
+				   "The worker '%s' to be added as enhancement to the worker '%s' does not exist", enhancement.enhanced_name.c_str(), enhancement.name.c_str());
+			}
+			workers_->get_mutable(safe_worker_index(enhancement.name))->set_becomes(enhancement.enhanced_name);
+			break;
+		default:
+			NEVER_HERE();
+		}
+	}
+	postload_mapobject_enhancements_.clear();
+
+	// Add building info to wares here, since wares were loaded first.
 	for (DescriptionIndex i = 0; i < buildings_->size(); ++i) {
 		BuildingDescr& building_descr = *buildings_->get_mutable(i);
 

=== modified file 'src/logic/map_objects/tribes/tribes.h'
--- src/logic/map_objects/tribes/tribes.h	2017-03-23 07:36:36 +0000
+++ src/logic/map_objects/tribes/tribes.h	2017-08-17 11:49:32 +0000
@@ -65,22 +65,22 @@
 	}
 
 	/// Adds this building type to the tribe description.
-	void add_constructionsite_type(const LuaTable& table, const EditorGameBase& egbase);
-
-	/// Adds this building type to the tribe description.
-	void add_dismantlesite_type(const LuaTable& table, const EditorGameBase& egbase);
-
-	/// Adds this building type to the tribe description.
-	void add_militarysite_type(const LuaTable& table, const EditorGameBase& egbase);
-
-	/// Adds this building type to the tribe description.
-	void add_productionsite_type(const LuaTable& table, const EditorGameBase& egbase);
-
-	/// Adds this building type to the tribe description.
-	void add_trainingsite_type(const LuaTable& table, const EditorGameBase& egbase);
-
-	/// Adds this building type to the tribe description.
-	void add_warehouse_type(const LuaTable& table, const EditorGameBase& egbase);
+	void add_constructionsite_type(const LuaTable& table, EditorGameBase* egbase);
+
+	/// Adds this building type to the tribe description.
+	void add_dismantlesite_type(const LuaTable& table, EditorGameBase* egbase);
+
+	/// Adds this building type to the tribe description.
+	void add_militarysite_type(const LuaTable& table, EditorGameBase* egbase);
+
+	/// Adds this building type to the tribe description.
+	void add_productionsite_type(const LuaTable& table, EditorGameBase* egbase);
+
+	/// Adds this building type to the tribe description.
+	void add_trainingsite_type(const LuaTable& table, EditorGameBase* egbase);
+
+	/// Adds this building type to the tribe description.
+	void add_warehouse_type(const LuaTable& table, EditorGameBase* egbase);
 
 	/// Adds this immovable type to the tribe description.
 	void add_immovable_type(const LuaTable& table);
@@ -92,23 +92,26 @@
 	void add_ware_type(const LuaTable& table);
 
 	/// Adds this worker type to the tribe description.
-	void add_carrier_type(const LuaTable& table, const EditorGameBase& egbase);
-
-	/// Adds this worker type to the tribe description.
-	void add_soldier_type(const LuaTable& table, const EditorGameBase& egbase);
-
-	/// Adds this worker type to the tribe description.
-	void add_worker_type(const LuaTable& table, const EditorGameBase& egbase);
+	void add_carrier_type(const LuaTable& table, EditorGameBase* egbase);
+
+	/// Adds this worker type to the tribe description.
+	void add_soldier_type(const LuaTable& table, EditorGameBase* egbase);
+
+	/// Adds this worker type to the tribe description.
+	void add_worker_type(const LuaTable& table, EditorGameBase* egbase);
 
 	/// Adds a specific tribe's configuration.
 	void add_tribe(const LuaTable& table, const EditorGameBase& egbase);
 
+	void add_custom_building(const LuaTable& table);
+
 	size_t nrbuildings() const;
 	size_t nrtribes() const;
 	size_t nrwares() const;
 	size_t nrworkers() const;
 
 	bool ware_exists(const DescriptionIndex& index) const;
+	bool worker_exists(const std::string& workername) const;
 	bool worker_exists(const DescriptionIndex& index) const;
 	bool building_exists(const std::string& buildingname) const;
 	bool building_exists(const DescriptionIndex& index) const;
@@ -148,6 +151,22 @@
 	/// Complete the Description objects' information with data from other Description objects.
 	void postload();
 
+	/// Some workers have other workers as part of their buildcost. If the other worker hasn't been loaded yet, it will need to be added during postload.
+	struct WorkerBuildcost {
+		const std::string worker;
+		const std::string needed_worker;
+		const Quantity quantity;
+	};
+	void add_worker_buildcost(const WorkerBuildcost& buildcost);
+
+	/// Enhanced buildings/workers might not have been loaded yet when a more basic type is being loaded, so we will need to add some of them during postload.
+	struct MapObjectEnhancement {
+		const MapObjectType type; // Worker or building
+		const std::string name;
+		const std::string enhanced_name;
+	};
+	void add_mapobject_enhancement(const MapObjectEnhancement& becomes);
+
 private:
 	std::unique_ptr<DescriptionMaintainer<BuildingDescr>> buildings_;
 	std::unique_ptr<DescriptionMaintainer<ImmovableDescr>> immovables_;
@@ -156,6 +175,11 @@
 	std::unique_ptr<DescriptionMaintainer<WorkerDescr>> workers_;
 	std::unique_ptr<DescriptionMaintainer<TribeDescr>> tribes_;
 
+	/// Worker buildcost records to be added in postload. This container will be empty after postload.
+	std::vector<WorkerBuildcost> postload_workers_buildcost_;
+	/// Worker/Building enhancement records to be added in postload. This container will be empty after postload.
+	std::vector<MapObjectEnhancement> postload_mapobject_enhancements_;
+
 	DISALLOW_COPY_AND_ASSIGN(Tribes);
 };
 

=== modified file 'src/logic/map_objects/tribes/warehouse.cc'
--- src/logic/map_objects/tribes/warehouse.cc	2017-07-01 15:36:36 +0000
+++ src/logic/map_objects/tribes/warehouse.cc	2017-08-17 11:49:32 +0000
@@ -293,7 +293,7 @@
   */
 WarehouseDescr::WarehouseDescr(const std::string& init_descname,
                                const LuaTable& table,
-                               const EditorGameBase& egbase)
+                               EditorGameBase* egbase)
    : BuildingDescr(init_descname, MapObjectType::WAREHOUSE, table, egbase),
      conquers_(0),
      heal_per_second_(0) {

=== modified file 'src/logic/map_objects/tribes/warehouse.h'
--- src/logic/map_objects/tribes/warehouse.h	2017-06-25 19:12:30 +0000
+++ src/logic/map_objects/tribes/warehouse.h	2017-08-17 11:49:32 +0000
@@ -50,7 +50,7 @@
 public:
 	WarehouseDescr(const std::string& init_descname,
 	               const LuaTable& t,
-	               const EditorGameBase& egbase);
+	               EditorGameBase* egbase);
 	~WarehouseDescr() override {
 	}
 

=== modified file 'src/logic/map_objects/tribes/worker_descr.cc'
--- src/logic/map_objects/tribes/worker_descr.cc	2017-04-21 17:03:26 +0000
+++ src/logic/map_objects/tribes/worker_descr.cc	2017-08-17 11:49:32 +0000
@@ -38,12 +38,12 @@
 WorkerDescr::WorkerDescr(const std::string& init_descname,
                          MapObjectType init_type,
                          const LuaTable& table,
-                         const EditorGameBase& egbase)
+                         EditorGameBase* egbase)
    : BobDescr(init_descname, init_type, MapObjectDescr::OwnerType::kTribe, table),
      buildable_(false),
      needed_experience_(INVALID_INDEX),
      becomes_(INVALID_INDEX),
-     egbase_(egbase) {
+     egbase_(*egbase) {
 	if (icon_filename().empty()) {
 		throw GameDataError("Worker %s has no menu icon", table.get_string("name").c_str());
 	}
@@ -60,17 +60,18 @@
 					throw GameDataError(
 					   "a buildcost item of this ware type has already been defined: %s", key.c_str());
 				}
-				if (!tribes.ware_exists(tribes.ware_index(key)) &&
-				    !tribes.worker_exists(tribes.worker_index(key))) {
-					throw GameDataError("\"%s\" has not been defined as a ware/worker type (wrong "
-					                    "declaration order?)",
-					                    key.c_str());
-				}
-				int32_t value = items_table->get_int(key);
-				uint8_t const count = value;
-				if (count != value)
+				int32_t temp_quantity = items_table->get_int(key);
+				const uint8_t quantity = temp_quantity;
+				if (quantity != temp_quantity) {
 					throw GameDataError("count is out of range 1 .. 255");
-				buildcost_.insert(std::pair<std::string, uint8_t>(key, count));
+				}
+				if (tribes.ware_exists(tribes.ware_index(key)) ||
+					 tribes.worker_exists(tribes.worker_index(key))) {
+					buildcost_.insert(std::make_pair(key, quantity));
+				} else {
+					// The buildcost's worker wasn't loaded yet, so we'll try this again in postload.
+					egbase->mutable_tribes()->add_worker_buildcost({name(), key, quantity});
+				}
 			} catch (const WException& e) {
 				throw GameDataError("[buildcost] \"%s\": %s", key.c_str(), e.what());
 			}
@@ -91,8 +92,14 @@
 
 	// Read what the worker can become and the needed experience
 	if (table.has_key("becomes")) {
-		becomes_ = egbase_.tribes().safe_worker_index(table.get_string("becomes"));
 		needed_experience_ = table.get_int("experience");
+		const std::string becomes = table.get_string("becomes");
+		if (egbase_.tribes().worker_exists(becomes)) {
+			set_becomes(becomes);
+		} else {
+			// The expert worker wasn't loaded yet, so we'll try this again in postload.
+			egbase->mutable_tribes()->add_mapobject_enhancement({MapObjectType::WORKER, name(), becomes});
+		}
 	}
 
 	// Read programs
@@ -122,13 +129,22 @@
 
 WorkerDescr::WorkerDescr(const std::string& init_descname,
                          const LuaTable& table,
-                         const EditorGameBase& egbase)
+                         EditorGameBase* egbase)
    : WorkerDescr(init_descname, MapObjectType::WORKER, table, egbase) {
 }
 
 WorkerDescr::~WorkerDescr() {
 }
 
+void WorkerDescr::add_worker_to_buildcost(const std::string& name, uint8_t quantity) {
+	buildcost_.insert(std::make_pair(name, quantity));
+}
+
+void WorkerDescr::set_becomes(const std::string& name) {
+	assert(needed_experience_ > 0 && needed_experience_ != INVALID_INDEX);
+	becomes_ = egbase_.tribes().safe_worker_index(name);
+}
+
 /**
  * Get a program from the workers description.
  */

=== modified file 'src/logic/map_objects/tribes/worker_descr.h'
--- src/logic/map_objects/tribes/worker_descr.h	2017-02-20 14:15:07 +0000
+++ src/logic/map_objects/tribes/worker_descr.h	2017-08-17 11:49:32 +0000
@@ -43,13 +43,13 @@
 	friend struct WorkerProgram;
 
 public:
-	using Buildcost = std::map<std::string, Quantity>;
+	using Buildcost = std::map<std::string, uint8_t>;
 
 	WorkerDescr(const std::string& init_descname,
 	            MapObjectType type,
 	            const LuaTable& table,
-	            const EditorGameBase& egbase);
-	WorkerDescr(const std::string& init_descname, const LuaTable& t, const EditorGameBase& egbase);
+	            EditorGameBase* egbase);
+	WorkerDescr(const std::string& init_descname, const LuaTable& t, EditorGameBase* egbase);
 	~WorkerDescr() override;
 
 	Bob& create_object() const override;
@@ -62,6 +62,14 @@
 		return buildcost_;
 	}
 
+	/// Some workers need to be added to the buildcost in postload, because the other worker might not have been loaded into the engine yet.
+	/// 'name' is the descr().name() of the other worker, 'quantity' is the amount of that woker type needed.
+	void add_worker_to_buildcost(const std::string& name, uint8_t quantity);
+
+	/// Sets the descr().name() of the worker that this worker will enhance to, given enough experience.
+	/// 'needed_experience_' needs to be set first.
+	void set_becomes(const std::string& name);
+
 	/// How much of the worker type that an economy should store in warehouses.
 	/// The special value std::numeric_limits<uint32_t>::max() means that the
 	/// the target quantity of this ware type will never be checked and should

=== modified file 'src/map_io/map_scripting_packet.cc'
--- src/map_io/map_scripting_packet.cc	2017-01-25 18:55:59 +0000
+++ src/map_io/map_scripting_packet.cc	2017-08-17 11:49:32 +0000
@@ -67,18 +67,29 @@
 }
 
 void MapScriptingPacket::write(FileSystem& fs, EditorGameBase& egbase, MapObjectSaver& mos) {
-	fs.ensure_directory_exists("scripting");
-
+
+	// Write all .lua files that exist in the given 'path' in 'map_fs' to the 'target_fs'.
+	auto write_dir = [] (FileSystem& target_fs, FileSystem* map_fs, const std::string& path) {
+		if (map_fs) {
+			target_fs.ensure_directory_exists(path);
+			for (const std::string& script :
+			     filter(map_fs->list_directory(path),
+			            [](const std::string& fn) { return boost::ends_with(fn, ".lua"); })) {
+				size_t length;
+				void* input_data = map_fs->load(script, length);
+				target_fs.write(script, input_data, length);
+				free(input_data);
+			}
+		}
+	};
+
+	// Write any scenario scripting files in the map's basic scripting dir
 	FileSystem* map_fs = egbase.map().filesystem();
-	if (map_fs) {
-		for (const std::string& script :
-		     filter(map_fs->list_directory("scripting"),
-		            [](const std::string& fn) { return boost::ends_with(fn, ".lua"); })) {
-			size_t length;
-			void* input_data = map_fs->load(script, length);
-			fs.write(script, input_data, length);
-			free(input_data);
-		}
+	write_dir(fs, map_fs, "scripting");
+
+	// Write any custom scenario tribe entities
+	if (map_fs->file_exists("scripting/tribes/init.lua")) {
+		write_dir(fs, map_fs, "scripting/tribes");
 	}
 
 	// Dump the global environment if this is a game and not in the editor

=== modified file 'src/scripting/lua_path.cc'
--- src/scripting/lua_path.cc	2017-06-24 08:47:46 +0000
+++ src/scripting/lua_path.cc	2017-08-17 11:49:32 +0000
@@ -143,7 +143,7 @@
    :returns: An :class:`array` of file paths in lexicographical order.
 */
 static int L_list_files(lua_State* L) {
-	std::string filename_template = luaL_checkstring(L, 1);
+	const std::string filename_template = luaL_checkstring(L, 1);
 
 	NumberGlob glob(filename_template);
 	std::string filename;
@@ -161,9 +161,48 @@
 	return 1;
 }
 
+/* RST
+.. function:: list_directory(filename)
+
+   Returns all file names contained in the given directory.
+
+   :type filename: class:`string`
+   :arg filename: The directory to read.
+
+   :returns: An :class:`array` of file names.
+*/
+static int L_list_directory(lua_State* L) {
+	lua_newtable(L);
+	int idx = 1;
+	for (const std::string& filename : g_fs->list_directory(luaL_checkstring(L, 1))) {
+		lua_pushint32(L, idx++);
+		lua_pushstring(L, filename);
+		lua_settable(L, -3);
+	}
+	return 1;
+}
+
+
+/* RST
+.. function:: is_directory(filename)
+
+   Checks whether the given filename points to a directory.
+
+   :type filename: class:`string`
+   :arg filename: The filename to check.
+
+   :returns: ``true`` if the given path is a directory.
+*/
+static int L_is_directory(lua_State* L) {
+	lua_pushboolean(L, g_fs->is_directory(luaL_checkstring(L, -1)));
+	return 1;
+}
+
 const static struct luaL_Reg path[] = {{"basename", &L_basename},
                                        {"dirname", &L_dirname},
                                        {"list_files", &L_list_files},
+													{"list_directory", &L_list_directory},
+													{"is_directory", &L_is_directory},
                                        {nullptr, nullptr}};
 
 void luaopen_path(lua_State* L) {

=== modified file 'src/scripting/lua_root.cc'
--- src/scripting/lua_root.cc	2017-01-25 18:55:59 +0000
+++ src/scripting/lua_root.cc	2017-08-17 11:49:32 +0000
@@ -560,6 +560,7 @@
    METHOD(LuaTribes, new_soldier_type),
    METHOD(LuaTribes, new_worker_type),
    METHOD(LuaTribes, new_tribe),
+	METHOD(LuaTribes, add_custom_building),
    {0, 0},
 };
 const PropertyType<LuaTribes> LuaTribes::Properties[] = {
@@ -606,8 +607,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		EditorGameBase& egbase = get_egbase(L);
-		egbase.mutable_tribes()->add_constructionsite_type(table, egbase);
+		EditorGameBase* egbase = &get_egbase(L);
+		egbase->mutable_tribes()->add_constructionsite_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -630,8 +631,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		EditorGameBase& egbase = get_egbase(L);
-		egbase.mutable_tribes()->add_dismantlesite_type(table, egbase);
+		EditorGameBase* egbase = &get_egbase(L);
+		egbase->mutable_tribes()->add_dismantlesite_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -654,8 +655,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		EditorGameBase& egbase = get_egbase(L);
-		egbase.mutable_tribes()->add_militarysite_type(table, egbase);
+		EditorGameBase* egbase = &get_egbase(L);
+		egbase->mutable_tribes()->add_militarysite_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -678,8 +679,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		EditorGameBase& egbase = get_egbase(L);
-		egbase.mutable_tribes()->add_productionsite_type(table, egbase);
+		EditorGameBase* egbase = &get_egbase(L);
+		egbase->mutable_tribes()->add_productionsite_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -702,8 +703,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		EditorGameBase& egbase = get_egbase(L);
-		egbase.mutable_tribes()->add_trainingsite_type(table, egbase);
+		EditorGameBase* egbase = &get_egbase(L);
+		egbase->mutable_tribes()->add_trainingsite_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -726,8 +727,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		EditorGameBase& egbase = get_egbase(L);
-		egbase.mutable_tribes()->add_warehouse_type(table, egbase);
+		EditorGameBase* egbase = &get_egbase(L);
+		egbase->mutable_tribes()->add_warehouse_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -818,8 +819,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		EditorGameBase& egbase = get_egbase(L);
-		egbase.mutable_tribes()->add_carrier_type(table, egbase);
+		EditorGameBase* egbase = &get_egbase(L);
+		egbase->mutable_tribes()->add_carrier_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -841,8 +842,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		EditorGameBase& egbase = get_egbase(L);
-		egbase.mutable_tribes()->add_soldier_type(table, egbase);
+		EditorGameBase* egbase = &get_egbase(L);
+		egbase->mutable_tribes()->add_soldier_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -864,8 +865,8 @@
 
 	try {
 		LuaTable table(L);  // Will pop the table eventually.
-		EditorGameBase& egbase = get_egbase(L);
-		egbase.mutable_tribes()->add_worker_type(table, egbase);
+		EditorGameBase* egbase = &get_egbase(L);
+		egbase->mutable_tribes()->add_worker_type(table, egbase);
 	} catch (std::exception& e) {
 		report_error(L, "%s", e.what());
 	}
@@ -896,6 +897,38 @@
 	return 0;
 }
 
+
+/* RST
+	.. method:: add_custom_building{table}
+
+		Adds a custom building to a tribe, e.g. for use in a scenario.
+		The building must already be known to the tribes.
+		The table has the following entries:
+
+		**tribename**
+			*Mandatory*. The name of the tribe that this building will be added to.
+
+		**buildingname**
+			*Mandatory*. The name of the building to be added to the tribe.
+
+		:returns: :const:`0`
+*/
+int LuaTribes::add_custom_building(lua_State* L) {
+	if (lua_gettop(L) != 2) {
+		report_error(L, "Takes only one argument.");
+	}
+
+	try {
+		LuaTable table(L);  // Will pop the table eventually.
+		EditorGameBase& egbase = get_egbase(L);
+		egbase.mutable_tribes()->add_custom_building(table);
+	} catch (std::exception& e) {
+		report_error(L, "%s", e.what());
+	}
+	return 0;
+}
+
+
 /*
  ==========================================================
  C METHODS

=== modified file 'src/scripting/lua_root.h'
--- src/scripting/lua_root.h	2017-06-24 08:47:46 +0000
+++ src/scripting/lua_root.h	2017-08-17 11:49:32 +0000
@@ -172,6 +172,7 @@
 	int new_soldier_type(lua_State* L);
 	int new_worker_type(lua_State* L);
 	int new_tribe(lua_State* L);
+	int add_custom_building(lua_State* L);
 
 	/*
 	 * C methods


References