← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~notabilis27/widelands/casern into lp:widelands

 

Notabilis has proposed merging lp:~notabilis27/widelands/casern into lp:widelands.

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1075562 in widelands: "Implement a casern for soldier recruting"
  https://bugs.launchpad.net/widelands/+bug/1075562

For more details, see:
https://code.launchpad.net/~notabilis27/widelands/casern/+merge/309763

Implements a worker queue which allows production buildings to consume
workers as input.

Adds:
- Adds worker queue for production buildings.
- The "inputs" in the lua files for the buildings can now contains worker
  names and amounts.
- Production programs can consume workers from the queues.

Changes:
- The barracks-building (new in trunk) now requests and stores carriers
  required for recruiting.
- Soldiers can no longer be created in warehouses.
- Renamed the c-for-lua function {set,get,valid}_wares() of production
  buildings to {set,get,valid}_inputs() since they now be used to set the
  to-be-consumed workers.
- Modified the lua function prefilled_buildings() to required the argument
  "inputs" instead of "wares" for production sites.
- Affected scripts should be updated (but only some are tested).
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~notabilis27/widelands/casern into lp:widelands.
=== modified file 'data/campaigns/bar01.wmf/scripting/secret_village.lua'
--- data/campaigns/bar01.wmf/scripting/secret_village.lua	2015-10-31 12:11:44 +0000
+++ data/campaigns/bar01.wmf/scripting/secret_village.lua	2016-11-01 14:41:18 +0000
@@ -101,13 +101,13 @@
       {"barbarians_gamekeepers_hut", 56, 12},
       {"barbarians_farm", 56, 16},
       {"barbarians_well", 54, 18},
-      {"barbarians_bakery", 55, 20, wares = {wheat = 6, water = 6}},
+      {"barbarians_bakery", 55, 20, inputs = {wheat = 6, water = 6}},
       {"barbarians_lumberjacks_hut", 56, 21},
       {"barbarians_lumberjacks_hut", 55, 22},
       {"barbarians_lumberjacks_hut", 54, 24},
       {"barbarians_rangers_hut", 57, 24},
       {"barbarians_rangers_hut", 55, 25},
-      {"barbarians_wood_hardener", 54, 26, wares = {log = 8}},
+      {"barbarians_wood_hardener", 54, 26, inputs = {log = 8}},
       -- to make it more realistic
       {"barbarians_warehouse", 53, 28,
          wares = {
@@ -116,11 +116,11 @@
             meat = 30
          }
       },
-      {"barbarians_inn", 55, 28, wares = {barbarians_bread = 4, meat = 4}},
-      {"barbarians_tavern", 57, 28, wares = {barbarians_bread = 4, meat = 4}},
+      {"barbarians_inn", 55, 28, inputs = {barbarians_bread = 4, meat = 4}},
+      {"barbarians_tavern", 57, 28, inputs = {barbarians_bread = 4, meat = 4}},
       {"barbarians_well", 52, 30},
       {"barbarians_farm", 54, 33},
-      {"barbarians_bakery", 51, 35, wares = {wheat = 6, water = 6}},
+      {"barbarians_bakery", 51, 35, inputs = {wheat = 6, water = 6}},
       {"barbarians_well", 52, 37}
    )
 

=== modified file 'data/campaigns/tutorial03_seafaring.wmf/scripting/helper_functions.lua'
--- data/campaigns/tutorial03_seafaring.wmf/scripting/helper_functions.lua	2016-01-28 05:24:34 +0000
+++ data/campaigns/tutorial03_seafaring.wmf/scripting/helper_functions.lua	2016-11-01 14:41:18 +0000
@@ -23,5 +23,7 @@
       -- Fill with wares
       if bdescr.wares then b:set_wares(bdescr.wares)
       elseif b.valid_wares then b:set_wares(b.valid_wares) end
+      if bdescr.inputs then b:set_inputs(bdescr.inputs)
+      elseif b.valid_inputs then b:set_inputs(b.valid_inputs) end
    end
 end

=== modified file 'data/campaigns/tutorial03_seafaring.wmf/scripting/starting_conditions.lua'
--- data/campaigns/tutorial03_seafaring.wmf/scripting/starting_conditions.lua	2015-10-31 12:11:44 +0000
+++ data/campaigns/tutorial03_seafaring.wmf/scripting/starting_conditions.lua	2016-11-01 14:41:18 +0000
@@ -102,8 +102,8 @@
    {"atlanteans_horsefarm", 40, 55},
    {"atlanteans_spiderfarm", 37, 45},
    {"atlanteans_weaving_mill", 45, 45},
-   {"atlanteans_smelting_works", 35, 56, wares = {coal = 8, iron_ore = 8}}, -- no gold
-   {"atlanteans_smelting_works", 35, 59, wares = {coal = 8, iron_ore = 8}},
+   {"atlanteans_smelting_works", 35, 56, inputs = {coal = 8, iron_ore = 8}}, -- no gold
+   {"atlanteans_smelting_works", 35, 59, inputs = {coal = 8, iron_ore = 8}},
    {"atlanteans_toolsmithy", 41, 52},
    {"atlanteans_weaponsmithy", 37, 54},
    {"atlanteans_tower_small", 34, 63},

=== modified file 'data/campaigns/tutorial04_economy.wmf/scripting/helper_functions.lua'
--- data/campaigns/tutorial04_economy.wmf/scripting/helper_functions.lua	2016-01-28 05:24:34 +0000
+++ data/campaigns/tutorial04_economy.wmf/scripting/helper_functions.lua	2016-11-01 14:41:18 +0000
@@ -24,5 +24,7 @@
       -- Fill with wares
       if bdescr.wares then b:set_wares(bdescr.wares)
       elseif b.valid_wares then b:set_wares(b.valid_wares) end
+      if bdescr.inputs then b:set_inputs(bdescr.inputs)
+      elseif b.valid_inputs then b:set_inputs(b.valid_inputs) end
    end
 end

=== modified file 'data/campaigns/tutorial04_economy.wmf/scripting/starting_conditions.lua'
--- data/campaigns/tutorial04_economy.wmf/scripting/starting_conditions.lua	2015-10-31 12:11:44 +0000
+++ data/campaigns/tutorial04_economy.wmf/scripting/starting_conditions.lua	2016-11-01 14:41:18 +0000
@@ -94,16 +94,16 @@
       {"empire_bakery",116,28},
       {"empire_bakery",115,32},
       {"empire_tavern",tavern_field.x,tavern_field.y}, -- (105,44), will be destroyed
-      {"empire_coalmine",118,45, wares = {beer = 6}},
-      {"empire_coalmine",119,39, wares = {beer = 6}},
-      {"empire_ironmine",107,59, wares = {beer = 6}},
-      {"empire_marblemine",98,38, wares = {wine = 6}},
-      {"empire_marblemine",102,38, wares = {wine = 6}},
-      {"empire_smelting_works",110,38, wares = {}},
-      {"empire_smelting_works",111,43, wares = {}},
-      {"empire_toolsmithy",104,64, wares = {log = 8}},
-      {"empire_weaponsmithy",113,40, wares = {planks = 8}},
-      {"empire_armorsmithy",112,37, wares = {cloth = 8}},
+      {"empire_coalmine",118,45, inputs = {beer = 6}},
+      {"empire_coalmine",119,39, inputs = {beer = 6}},
+      {"empire_ironmine",107,59, inputs = {beer = 6}},
+      {"empire_marblemine",98,38, inputs = {wine = 6}},
+      {"empire_marblemine",102,38, inputs = {wine = 6}},
+      {"empire_smelting_works",110,38, inputs = {}},
+      {"empire_smelting_works",111,43, inputs = {}},
+      {"empire_toolsmithy",104,64, inputs = {log = 8}},
+      {"empire_weaponsmithy",113,40, inputs = {planks = 8}},
+      {"empire_armorsmithy",112,37, inputs = {cloth = 8}},
       {"empire_farm",105,70},
       {"empire_farm",101,71},
       {"empire_farm",99,77},

=== modified file 'data/maps/Trident_of_Fire.wmf/scripting/initial_conditions.lua'
--- data/maps/Trident_of_Fire.wmf/scripting/initial_conditions.lua	2016-03-21 19:29:24 +0000
+++ data/maps/Trident_of_Fire.wmf/scripting/initial_conditions.lua	2016-11-01 14:41:18 +0000
@@ -149,7 +149,7 @@
             soldiers = { [{0,0,0,0}] = 45 },
          },
          { "barbarians_port", f_port.x, f_port.y},
-         { "barbarians_shipyard", f_shipyard.x, f_shipyard.y, wares = {
+         { "barbarians_shipyard", f_shipyard.x, f_shipyard.y, inputs = {
               blackwood = 10,
               cloth = 4,
               log = 2,
@@ -219,7 +219,7 @@
          },
          { "empire_port", f_port.x, f_port.y},
          { "empire_shipyard", f_shipyard.x, f_shipyard.y,
-            wares = {
+            inputs = {
              cloth = 4,
              log = 2,
              planks = 10,
@@ -289,7 +289,7 @@
          soldiers = { [{0,0,0,0}] = 45 },
          },
          { "atlanteans_port", f_port.x, f_port.y},
-         { "atlanteans_shipyard", f_shipyard.x, f_shipyard.y, wares = {
+         { "atlanteans_shipyard", f_shipyard.x, f_shipyard.y, inputs = {
            planks = 10,
            spidercloth = 4,
            log = 2,

=== modified file 'data/scripting/infrastructure.lua'
--- data/scripting/infrastructure.lua	2016-03-01 09:31:36 +0000
+++ data/scripting/infrastructure.lua	2016-11-01 14:41:18 +0000
@@ -65,7 +65,7 @@
 --       prefilled_buildings(wl.Game().players[1],
 --          {"sentry", 57, 9}, -- Sentry completely full with soldiers
 --          {"sentry", 57, 9, soldier={[{0,0,0,0}]=1}}, -- Sentry with one soldier
---          {"bakery", 55, 20, wares = {wheat=6, water=6}}, -- bakery with wares and workers
+--          {"bakery", 55, 20, inputs = {wheat=6, water=6}}, -- bakery with wares and workers
 --          {"well", 52, 30}, -- a well with workers
 --       )
 --
@@ -77,9 +77,13 @@
 --
 --       wares
 --          A table of (name,count) as expected by
---          :meth:`wl.map.ProductionSite.set_wares`. This is valid for
---          :class:`wl.map.ProductionSite` and :class:`wl.map.Warehouse` and
---          ignored otherwise.
+--          :meth:`wl.map.Warehouse.set_wares`. This is valid for
+--          :class:`wl.map.Warehouse` and ignored otherwise.
+--       inputs
+--          A table of (name,count) as expected by
+--          :meth:`wl.map.ProductionSite.set_inputs`. Inputs are wares or workers
+--          which are consumed by the building. This is valid for
+--          :class:`wl.map.ProductionSite` and ignored otherwise.
 --       soldiers
 --          A table of (soldier_descr,count) as expected by
 --          :meth:`wl.map.HasSoldiers.set_soldiers`.  If this is nil, the site
@@ -107,6 +111,7 @@
       end
       -- Fill with wares if this is requested
       if bdescr.wares then b:set_wares(bdescr.wares) end
+      if bdescr.inputs then b:set_inputs(bdescr.inputs) end
    end
 end
 

=== modified file 'data/tribes/buildings/productionsites/atlanteans/barracks/init.lua'
--- data/tribes/buildings/productionsites/atlanteans/barracks/init.lua	2016-10-05 04:13:48 +0000
+++ data/tribes/buildings/productionsites/atlanteans/barracks/init.lua	2016-11-01 14:41:18 +0000
@@ -34,6 +34,9 @@
 	},
 
 	aihints = {
+		forced_after = 1000,
+		very_weak_ai_limit = 1,
+		weak_ai_limit = 3
 	},
 
 	working_positions = {
@@ -42,7 +45,8 @@
 
    inputs = {
       { name = "tabard", amount = 8 },
-      { name = "trident_light", amount = 8 }
+      { name = "trident_light", amount = 8 },
+      { name = "atlanteans_carrier", amount = 8 }
    },
 	outputs = {
 		"atlanteans_soldier",
@@ -51,12 +55,11 @@
 	programs = {
 		work = {
 			-- TRANSLATORS: Completed/Skipped/Did not start recruiting soldier because ...
-			-- TODO(GunChleoc): this should cost us a carrier as well, or maybe a recruit.
 			descname = _"recruiting soldier",
 			actions = {
 				"sleep=15000",
 				"return=skipped unless economy needs atlanteans_soldier",
-				"consume=tabard trident_light",
+				"consume=tabard trident_light atlanteans_carrier",
 				"animate=working 15000",
 				"recruit=atlanteans_soldier"
 			}

=== modified file 'data/tribes/buildings/productionsites/barbarians/barracks/init.lua'
--- data/tribes/buildings/productionsites/barbarians/barracks/init.lua	2016-10-04 18:07:56 +0000
+++ data/tribes/buildings/productionsites/barbarians/barracks/init.lua	2016-11-01 14:41:18 +0000
@@ -33,6 +33,9 @@
 	},
 
 	aihints = {
+		forced_after = 1000,
+		very_weak_ai_limit = 1,
+		weak_ai_limit = 3
 	},
 
 	working_positions = {
@@ -40,7 +43,9 @@
 	},
 
    inputs = {
-      { name = "ax", amount = 8 }
+      { name = "ax", amount = 8 },
+      { name = "beer", amount = 8 },
+      { name = "barbarians_carrier", amount = 8 }
    },
 	outputs = {
 		"barbarians_soldier",
@@ -50,11 +55,10 @@
 		work = {
 			-- TRANSLATORS: Completed/Skipped/Did not start recruiting soldier because ...
 			descname = _"recruiting soldier",
-			-- TODO(GunChleoc): this should cost us a carrier as well, or maybe a recruit.
 			actions = {
 				"sleep=15000",
 				"return=skipped unless economy needs barbarians_soldier",
-				"consume=ax",
+				"consume=ax beer barbarians_carrier",
 				"animate=working 15000",
 				"recruit=barbarians_soldier"
 			}

=== modified file 'data/tribes/buildings/productionsites/empire/barracks/init.lua'
--- data/tribes/buildings/productionsites/empire/barracks/init.lua	2016-10-04 18:07:56 +0000
+++ data/tribes/buildings/productionsites/empire/barracks/init.lua	2016-11-01 14:41:18 +0000
@@ -35,6 +35,9 @@
 	},
 
 	aihints = {
+		forced_after = 1000,
+		very_weak_ai_limit = 1,
+		weak_ai_limit = 3
 	},
 
 	working_positions = {
@@ -43,7 +46,8 @@
 
    inputs = {
       { name = "armor_helmet", amount = 8 },
-      { name = "spear_wooden", amount = 8 }
+      { name = "spear_wooden", amount = 8 },
+      { name = "empire_carrier", amount = 8 }
    },
 	outputs = {
 		"empire_soldier",
@@ -52,12 +56,11 @@
 	programs = {
 		work = {
 			-- TRANSLATORS: Completed/Skipped/Did not start recruiting soldier because ...
-			-- TODO(GunChleoc): this should cost us a carrier as well, or maybe a recruit.
 			descname = _"recruiting soldier",
 			actions = {
 				"sleep=15000",
 				"return=skipped unless economy needs empire_soldier",
-				"consume=armor_helmet spear_wooden",
+				"consume=armor_helmet spear_wooden empire_carrier",
 				"animate=working 15000",
 				"recruit=empire_soldier"
 			}

=== modified file 'data/tribes/scripting/starting_conditions/atlanteans/fortified_village.lua'
--- data/tribes/scripting/starting_conditions/atlanteans/fortified_village.lua	2016-03-30 07:23:59 +0000
+++ data/tribes/scripting/starting_conditions/atlanteans/fortified_village.lua	2016-11-01 14:41:18 +0000
@@ -75,7 +75,7 @@
       })
 
       place_building_in_region(plr, "atlanteans_labyrinth", sf:region(11), {
-         wares = {
+         inputs = {
             atlanteans_bread = 4,
             smoked_fish = 3,
             smoked_meat = 3,
@@ -83,21 +83,21 @@
       })
 
       place_building_in_region(plr, "atlanteans_dungeon", sf:region(11), {
-         wares = {atlanteans_bread = 4, smoked_fish = 3, smoked_meat = 3}
+         inputs = {atlanteans_bread = 4, smoked_fish = 3, smoked_meat = 3}
       })
 
       place_building_in_region(plr, "atlanteans_armorsmithy", sf:region(11), {
-         wares = { coal=4, gold =4 }
+         inputs = { coal=4, gold =4 }
       })
       place_building_in_region(plr, "atlanteans_toolsmithy", sf:region(11), {
-         wares = { log = 6 }
+         inputs = { log = 6 }
       })
       place_building_in_region(plr, "atlanteans_weaponsmithy", sf:region(11), {
-         wares = { coal = 8, iron = 8 }
+         inputs = { coal = 8, iron = 8 }
       })
 
       place_building_in_region(plr, "atlanteans_sawmill", sf:region(11), {
-         wares = { log = 1 }
+         inputs = { log = 1 }
       })
    end
 }

=== modified file 'data/tribes/scripting/starting_conditions/atlanteans/trading_outpost.lua'
--- data/tribes/scripting/starting_conditions/atlanteans/trading_outpost.lua	2016-10-23 09:51:50 +0000
+++ data/tribes/scripting/starting_conditions/atlanteans/trading_outpost.lua	2016-11-01 14:41:18 +0000
@@ -84,7 +84,7 @@
       })
 
       place_building_in_region(player, "atlanteans_toolsmithy", sf:region(11), {
-         wares = {
+         inputs = {
             iron = 6,
             log = 6,
             spidercloth = 4
@@ -92,13 +92,13 @@
       })
 
       place_building_in_region(player, "atlanteans_sawmill", sf:region(11), {
-         wares = {
+         inputs = {
             log = 8
          }
       })
 
       place_building_in_region(player, "atlanteans_hunters_house", sf:region(11), {
-         wares = {}
+         inputs = {}
       })
 
       place_building_in_region(player, "atlanteans_tower", sf:region(13), {

=== modified file 'data/tribes/scripting/starting_conditions/barbarians/fortified_village.lua'
--- data/tribes/scripting/starting_conditions/barbarians/fortified_village.lua	2016-03-30 07:23:59 +0000
+++ data/tribes/scripting/starting_conditions/barbarians/fortified_village.lua	2016-11-01 14:41:18 +0000
@@ -68,7 +68,7 @@
       })
 
       place_building_in_region(plr, "barbarians_battlearena", sf:region(12), {
-         wares = {
+         inputs = {
             barbarians_bread = 8,
             fish = 6,
             meat = 6,
@@ -78,19 +78,19 @@
       place_building_in_region(plr, "barbarians_trainingcamp", sf:region(12))
 
       place_building_in_region(plr, "barbarians_helmsmithy", sf:region(12), {
-         wares = { iron = 4, gold = 4 }
+         inputs = { iron = 4, gold = 4 }
       })
       place_building_in_region(plr, "barbarians_metal_workshop", sf:region(12), {
-         wares = { iron = 8 },
+         inputs = { iron = 8 },
       })
       place_building_in_region(plr, "barbarians_ax_workshop", sf:region(12), {
-         wares = { coal = 8 },
+         inputs = { coal = 8 },
       })
       place_building_in_region(plr, "barbarians_wood_hardener", sf:region(12), {
-         wares = { log = 1 },
+         inputs = { log = 1 },
       })
       place_building_in_region(plr, "barbarians_lime_kiln", sf:region(12), {
-         wares = { granite = 6, coal = 3 },
+         inputs = { granite = 6, coal = 3 },
       })
    end
 }

=== modified file 'data/tribes/scripting/starting_conditions/barbarians/trading_outpost.lua'
--- data/tribes/scripting/starting_conditions/barbarians/trading_outpost.lua	2016-10-23 09:51:50 +0000
+++ data/tribes/scripting/starting_conditions/barbarians/trading_outpost.lua	2016-11-01 14:41:18 +0000
@@ -76,20 +76,20 @@
       })
 
       place_building_in_region(player, "barbarians_metal_workshop", sf:region(11), {
-         wares = {
+         inputs = {
             iron = 8,
             log = 8
          }
       })
 
       place_building_in_region(player, "barbarians_wood_hardener", sf:region(11), {
-         wares = {
+         inputs = {
             log = 8
          }
       })
 
       place_building_in_region(player, "barbarians_hunters_hut", sf:region(11), {
-         wares = {}
+         inputs = {}
       })
 
       place_building_in_region(player, "barbarians_tower", sf:region(13), {

=== modified file 'data/tribes/scripting/starting_conditions/empire/fortified_village.lua'
--- data/tribes/scripting/starting_conditions/empire/fortified_village.lua	2016-03-30 07:23:59 +0000
+++ data/tribes/scripting/starting_conditions/empire/fortified_village.lua	2016-11-01 14:41:18 +0000
@@ -74,7 +74,7 @@
       })
 
       place_building_in_region(plr, "empire_colosseum", sf:region(11), {
-         wares = {
+         inputs = {
             empire_bread = 8,
             fish = 4,
             meat = 4,
@@ -82,7 +82,7 @@
       })
 
       place_building_in_region(plr, "empire_trainingcamp", sf:region(11), {
-         wares = {
+         inputs = {
             fish = 2,
             meat = 2,
             armor_helmet = 2,
@@ -90,7 +90,7 @@
       })
 
       place_building_in_region(plr, "empire_armorsmithy", sf:region(11), {
-         wares = {
+         inputs = {
                gold = 4,
                coal = 8,
                cloth = 5,
@@ -98,20 +98,20 @@
       })
 
       place_building_in_region(plr, "empire_toolsmithy", sf:region(11), {
-         wares = {
+         inputs = {
             iron = 8,
          }
       })
 
       place_building_in_region(plr, "empire_weaponsmithy", sf:region(11), {
-         wares = {
+         inputs = {
             coal = 4,
             planks = 8,
          }
       })
 
       place_building_in_region(plr, "empire_sawmill", sf:region(11), {
-         wares = {
+         inputs = {
             log = 1,
          }
       })

=== modified file 'data/tribes/scripting/starting_conditions/empire/trading_outpost.lua'
--- data/tribes/scripting/starting_conditions/empire/trading_outpost.lua	2016-10-23 09:51:50 +0000
+++ data/tribes/scripting/starting_conditions/empire/trading_outpost.lua	2016-11-01 14:41:18 +0000
@@ -82,20 +82,20 @@
       })
 
       place_building_in_region(player, "empire_toolsmithy", sf:region(11), {
-         wares = {
+         inputs = {
             iron = 8,
             log = 8
          }
       })
 
       place_building_in_region(player, "empire_sawmill", sf:region(11), {
-         wares = {
+         inputs = {
             log = 8
          }
       })
 
       place_building_in_region(player, "empire_hunters_house", sf:region(11), {
-         wares = {}
+         inputs = {}
       })
 
       place_building_in_region(player, "empire_tower", sf:region(13), {

=== modified file 'data/tribes/workers/atlanteans/soldier/init.lua'
--- data/tribes/workers/atlanteans/soldier/init.lua	2016-09-27 06:30:47 +0000
+++ data/tribes/workers/atlanteans/soldier/init.lua	2016-11-01 14:41:18 +0000
@@ -69,12 +69,6 @@
    icon = dirname .. "menu.png",
    vision_range = 2,
 
-   buildcost = {
-      atlanteans_carrier = 1,
-      tabard = 1,
-      trident_light = 1
-   },
-
    animations = animations,
 
    -- Battle attributes - initial values and per level increase

=== modified file 'data/tribes/workers/barbarians/soldier/init.lua'
--- data/tribes/workers/barbarians/soldier/init.lua	2016-09-27 06:30:47 +0000
+++ data/tribes/workers/barbarians/soldier/init.lua	2016-11-01 14:41:18 +0000
@@ -69,11 +69,6 @@
    icon = dirname .. "menu.png",
    vision_range = 2,
 
-   buildcost = {
-      barbarians_carrier = 1,
-      ax = 1
-   },
-
    animations = animations,
 
    -- Battle attributes - initial values and per level increase

=== modified file 'data/tribes/workers/empire/soldier/init.lua'
--- data/tribes/workers/empire/soldier/init.lua	2016-09-27 06:30:47 +0000
+++ data/tribes/workers/empire/soldier/init.lua	2016-11-01 14:41:18 +0000
@@ -69,12 +69,6 @@
    icon = dirname .. "menu.png",
    vision_range = 2,
 
-   buildcost = {
-      empire_carrier = 1,
-      armor_helmet = 1,
-      spear_wooden = 1
-   },
-
    animations = animations,
 
    -- Battle attributes - initial values and per level increase

=== modified file 'src/economy/CMakeLists.txt'
--- src/economy/CMakeLists.txt	2015-11-28 22:29:26 +0000
+++ src/economy/CMakeLists.txt	2016-11-01 14:41:18 +0000
@@ -42,6 +42,8 @@
     warehousesupply.h
     wares_queue.cc
     wares_queue.h
+    workers_queue.cc
+    workers_queue.h
   DEPENDS
     base_exceptions
     base_log

=== added file 'src/economy/workers_queue.cc'
--- src/economy/workers_queue.cc	1970-01-01 00:00:00 +0000
+++ src/economy/workers_queue.cc	2016-11-01 14:41:18 +0000
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2004, 2006-2011 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 "economy/workers_queue.h"
+
+#include "economy/economy.h"
+#include "economy/request.h"
+#include "io/fileread.h"
+#include "io/filewrite.h"
+#include "logic/editor_game_base.h"
+#include "logic/game.h"
+#include "logic/map_objects/tribes/tribe_descr.h"
+#include "logic/player.h"
+#include "map_io/map_object_loader.h"
+#include "map_io/map_object_saver.h"
+
+namespace Widelands {
+
+WorkersQueue::WorkersQueue
+    (PlayerImmovable &       init_owner,
+	 DescriptionIndex        const init_worker,
+	 uint8_t           const init_max_size)
+    : owner_(init_owner), worker_type_(init_worker), max_capacity_(init_max_size),
+	  capacity_(init_max_size), workers_(), request_(nullptr) {
+	// Can happen when loading a game
+	if (worker_type_ != INVALID_INDEX)
+		update_request();
+
+	// TODO(Notabilis): When set_filled() is called here, a later script call to set the worker of the
+	// building will fail. Not sure if this is a bug and/or a bug of this class. I don't really think so.
+}
+
+void WorkersQueue::set_capacity(Quantity capacity) {
+	assert(capacity <= max_capacity_);
+	if (capacity_ != capacity) {
+        capacity_ = capacity;
+        update_request();
+	}
+}
+
+void WorkersQueue::drop(Worker & worker) {
+	Game & game = dynamic_cast<Game&>(owner().egbase());
+
+	std::vector<Worker *>::iterator it =
+		std::find(workers_.begin(), workers_.end(), &worker);
+	if (it == workers_.end()) {
+		return;
+	}
+
+	workers_.erase(it);
+
+	worker.reset_tasks(game);
+	worker.start_task_leavebuilding(game, true);
+
+	update_request();
+}
+
+void WorkersQueue::remove_workers(Quantity amount) {
+// TODO(Notabilis): Check if there are any resources lost when removing workers
+// Especially if there are any worker-memory-objects left or too much is removed
+// I am not sure about how it should be done, so please check it
+
+    assert(get_filled() >= amount);
+
+	Game & game = dynamic_cast<Game&>(owner().egbase());
+
+	// Note: This might be slow (removing from start) but we want to consume
+	// the first worker in the queue first
+    for (Quantity i = 0; i < amount; i++) {
+        // Maybe: Remove from economy (I don't think this is required)
+        // owner_.economy().remove_workers(worker_type_, amount);
+        // Remove worker
+		(*(workers_.begin()))->schedule_destroy(game);
+        // Remove reference from list
+        workers_.erase(workers_.begin());
+    }
+
+	update_request();
+}
+
+Quantity WorkersQueue::get_filled() const {
+	return workers_.size();
+}
+
+void WorkersQueue::set_filled(Quantity amount) {
+
+	if (amount > max_capacity())
+		amount = max_capacity_;
+	const size_t currentAmount = get_filled();
+	if (amount == currentAmount)
+		return;
+
+	// Now adjust them
+	while (get_filled() < amount) {
+		// Create new worker
+		const TribeDescr& tribe = owner().tribe();
+		const WorkerDescr* worker_descr = tribe.get_worker_descr(worker_type_);
+		EditorGameBase& egbase = owner().egbase();
+		// Worker& create(EditorGameBase&, Player&, PlayerImmovable*, Coords) const;
+		Worker& w = worker_descr->create(egbase, owner(), nullptr, owner_.get_positions(egbase).front());
+		if (incorporate_worker(egbase, w) == -1) {
+			NEVER_HERE();
+		}
+	}
+	if (currentAmount > amount) {
+		// Drop workers
+		remove_workers(currentAmount - amount);
+	}
+}
+
+int WorkersQueue::incorporate_worker(EditorGameBase & egbase, Worker & w) {
+	if (w.get_location(egbase) != &(owner_)) {
+		if (get_filled() + 1 > max_capacity_) {
+			return -1;
+        }
+		w.set_location(&(owner_));
+	}
+
+	// Bind the worker into this house, hide him on the map
+	if (upcast(Game, game, &egbase)) {
+		w.start_task_idle(*game, 0, -1);
+	}
+
+    // Not quite sure about next line, the training sites are doing it inside add_worker().
+    // But that method is not available for ware/worker-queues.
+    // But anyway: Add worker to queue
+    workers_.push_back(&w);
+
+	// Make sure the request count is reduced or the request is deleted.
+	update_request();
+	return 0;
+}
+
+void WorkersQueue::remove_from_economy(Economy &) {
+	if (worker_type_ != INVALID_INDEX) {
+        // Setting request_->economy to nullptr will crash the game on load,
+        // but dropping the request and creating a new one in add_to_economy
+        // works fine
+		if (request_) {
+			delete request_;
+			request_ = nullptr;
+		}
+        // Removal of workers from the economy is not required, this is done by the building (or so)
+	}
+}
+
+void WorkersQueue::add_to_economy(Economy &) {
+	if (worker_type_ != INVALID_INDEX) {
+			update_request();
+	}
+}
+
+constexpr uint16_t kCurrentPacketVersion = 2;
+
+void WorkersQueue::write(FileWrite & fw, Game & game, MapObjectSaver & mos) {
+    // Adapted copy from WaresQueue
+	fw.unsigned_16(kCurrentPacketVersion);
+
+	//  Owner and callback is not saved, but this should be obvious on load.
+	fw.c_string
+		(owner().tribe().get_worker_descr(worker_type_)->name().c_str());
+	fw.signed_32(max_capacity_);
+	fw.signed_32(capacity_);
+	if (request_) {
+		fw.unsigned_8(1);
+		request_->write(fw, game, mos);
+	} else {
+		fw.unsigned_8(0);
+	}
+    // Store references to the workers
+    fw.unsigned_32(workers_.size());
+    for (Worker * w : workers_) {
+        assert(mos.is_object_known(*w));
+        fw.unsigned_32(mos.get_object_file_index(*w));
+    }
+}
+
+
+void WorkersQueue::read(FileRead & fr, Game & game, MapObjectLoader & mol) {
+    // Adapted copy from WaresQueue
+	uint16_t const packet_version = fr.unsigned_16();
+	try {
+		if (packet_version == kCurrentPacketVersion) {
+			delete request_;
+			worker_type_ = owner().tribe().worker_index(fr.c_string());
+			max_capacity_ = fr.unsigned_32();
+			capacity_ = fr.signed_32();
+			assert(capacity_ <= max_capacity_);
+			if (fr.unsigned_8 ()) {
+				request_ =
+					new Request
+						(owner_,
+						 0,
+						 WorkersQueue::request_callback,
+						 wwWORKER);
+				request_->read(fr, game, mol);
+			} else {
+				request_ = nullptr;
+			}
+            size_t nr_workers = fr.unsigned_32();
+            assert(nr_workers <= capacity_);
+            assert(workers_.empty());
+            for (size_t i = 0; i < nr_workers; ++i) {
+                workers_.push_back(&mol.get<Worker>(fr.unsigned_32()));
+            }
+            assert(workers_.size() == nr_workers);
+		} else {
+			throw UnhandledVersionError("WorkersQueue", packet_version, kCurrentPacketVersion);
+		}
+	} catch (const GameDataError & e) {
+		throw GameDataError("workersqueue: %s", e.what());
+	}
+}
+
+void WorkersQueue::request_callback
+	(Game            &       game,
+	 Request         &,
+	 DescriptionIndex        const,
+	 Worker          * const worker,
+	 PlayerImmovable & target)
+{
+	WorkersQueue & wq =
+		dynamic_cast<Building&>(target).workersqueue(worker->descr().worker_index());
+
+	assert(worker != nullptr);
+	assert(wq.workers_.size() < wq.max_capacity_);
+	assert(worker->descr().can_act_as(wq.worker_type_));
+
+	assert(worker->get_location(game) == &target);
+
+	// Update
+	wq.incorporate_worker(game, *worker);
+}
+
+void WorkersQueue::update_request() {
+	assert(worker_type_ != INVALID_INDEX);
+
+	if (workers_.size() < capacity_) {
+		if (!request_) {
+			request_ =
+				new Request
+					(owner_,
+					 worker_type_,
+					 WorkersQueue::request_callback,
+					 wwWORKER);
+             // TODO(Notabilis): If it is possible to restrict the request to exactly the worker-type
+             // of this queue, do so. Currently there are sometimes improved workers (e.g. Chief Miner)
+             // coming to enter the building (where Miners are requested).
+             // This happened after I kicked the Chief Miner from its mine.
+             // The Master Miners in the headquarters however show no intentions of entering the building
+		}
+
+		request_->set_count(capacity_ - workers_.size());
+	} else if (workers_.size() >= capacity_) {
+		delete request_;
+		request_ = nullptr;
+
+		while (workers_.size() > capacity_) {
+			drop(**workers_.rbegin());
+		}
+	}
+}
+
+}

=== added file 'src/economy/workers_queue.h'
--- src/economy/workers_queue.h	1970-01-01 00:00:00 +0000
+++ src/economy/workers_queue.h	2016-11-01 14:41:18 +0000
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2004, 2006-2011 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_ECONOMY_WORKERS_QUEUE_H
+#define WL_ECONOMY_WORKERS_QUEUE_H
+
+#include <vector>
+#include "logic/map_objects/immovable.h"
+#include "logic/widelands.h"
+
+namespace Widelands {
+
+class EditorGameBase;
+class Game;
+class MapObjectLoader;
+struct MapObjectSaver;
+class Player;
+class Request;
+class Worker;
+
+/**
+ * Similar to WaresQueue but for workers.
+ */
+class WorkersQueue {
+public:
+
+    /**
+     * Default constructor
+     */
+    WorkersQueue(PlayerImmovable &, DescriptionIndex, uint8_t size);
+
+    /**
+     * The type of workers required here.
+     */
+	DescriptionIndex get_worker() const {return worker_type_;}
+
+	/**
+	 * \return a list of workers that are currently in the building.
+	 */
+	std::vector<Worker *> workers() const {return workers_;};
+
+	/**
+	 * \return the maximum number of workers that this building can be
+	 * configured to hold.
+	 */
+	Quantity max_capacity() const {return max_capacity_;};
+
+	/**
+	 * Is in [0, max_capacity()].
+	 * \return the number of workers this building is configured to hold
+	 * right now.
+	 */
+	Quantity capacity() const {return capacity_;};
+
+	/**
+	 * Sets the capacity for workers of this building.
+	 * Has to be in [0, max_capacity()].
+	 *
+	 * New workers will be requested and old workers will be evicted
+	 * as necessary.
+	 */
+	void set_capacity(Quantity capacity);
+
+	void change_capacity(int32_t const difference) {
+		Quantity const old_capacity = capacity();
+		Quantity const new_capacity =
+			std::min
+				(static_cast<Quantity>
+				 	(std::max
+				 	 	(static_cast<int32_t>(old_capacity) + difference,
+				 	 	 0)),
+				 max_capacity_);
+		if (old_capacity != new_capacity)
+			set_capacity(new_capacity);
+	}
+
+	/**
+	 * Evict the given worker from the building immediately,
+	 * without changing the building's capacity.
+	 */
+	void drop(Worker &);
+
+    /**
+     * Removes the given amount of workers from the game.
+     * There have to be at least the given amount of workers in the queue.
+     */
+    void remove_workers(Quantity amount);
+
+    /**
+     * Returns the amount of workers currently in this building.
+     * @return The number of workers.
+     */
+    Quantity get_filled() const;
+
+	/**
+	 * Adds new workers to the queue.
+	 * The current capacity is not modified, the maximal capacity is respected.
+	 * If the given amount is smaller than the current one, the workers will
+	 * be removed silently.
+	 * @param amount The number of workers which should be inside.
+	*/
+	void set_filled(Quantity amount);
+
+	/**
+	 * Add a new worker into this site.
+	 * \return -1 if there is no space for him, 0 on success.
+	 */
+	int incorporate_worker(EditorGameBase &, Worker &);
+
+	void remove_from_economy(Economy &);
+	void add_to_economy(Economy &);
+
+	Player & owner() const {return owner_.owner();}
+
+	void read (FileRead  &, Game &, MapObjectLoader &);
+	void write(FileWrite &, Game &, MapObjectSaver  &);
+
+private:
+
+    /**
+     * Callback when a request is fulfilled and a worker enters the queue.
+     */
+	static void request_callback
+		(Game &, Request &, DescriptionIndex, Worker *, PlayerImmovable &);
+
+    /**
+     * Updates the request for further workers.
+     * Should be called when a worker is added or removed.
+     */
+	void update_request();
+
+	PlayerImmovable & owner_;
+	/// Type of the stored worker
+	DescriptionIndex worker_type_;
+	/// Number of workers that fit into the queue maximum.
+	Quantity max_capacity_;
+	/// Number of workers that fit currently into the queue.
+	Quantity capacity_;
+
+    /// The workers currently in the queue
+    std::vector<Worker *> workers_;
+
+    /// Currently pending request
+	Request * request_;
+};
+
+}
+
+#endif // WL_ECONOMY_WORKERS_QUEUE_H

=== modified file 'src/logic/game.cc'
--- src/logic/game.cc	2016-10-15 16:36:12 +0000
+++ src/logic/game.cc	2016-11-01 14:41:18 +0000
@@ -717,6 +717,17 @@
 	   *new CmdChangeTrainingOptions(get_gametime(), ts.owner().player_number(), ts, attr, val));
 }
 
+void Game::send_player_drop_worker(Building& b, int32_t const ser) {
+	assert(ser != -1);
+	send_player_command(
+		*new CmdDropWorker(get_gametime(), b.owner().player_number(), b, ser));
+}
+
+void Game::send_player_change_worker_capacity(Building& b, DescriptionIndex worker_type, int16_t const val) {
+	send_player_command(
+		*new CmdChangeWorkerCapacity(get_gametime(), b.owner().player_number(), b, worker_type, val));
+}
+
 void Game::send_player_drop_soldier(Building& b, int32_t const ser) {
 	assert(ser != -1);
 	send_player_command(*new CmdDropSoldier(get_gametime(), b.owner().player_number(), b, ser));

=== modified file 'src/logic/game.h'
--- src/logic/game.h	2016-08-04 15:49:05 +0000
+++ src/logic/game.h	2016-11-01 14:41:18 +0000
@@ -200,6 +200,8 @@
 	                                   int32_t prio);
 	void send_player_set_ware_max_fill(PlayerImmovable&, DescriptionIndex index, uint32_t);
 	void send_player_change_training_options(TrainingSite&, TrainingAttribute, int32_t);
+	void send_player_drop_worker(Building&, int32_t serial);
+	void send_player_change_worker_capacity(Building&, DescriptionIndex worker_type, int16_t delta);
 	void send_player_drop_soldier(Building&, int32_t);
 	void send_player_change_soldier_capacity(Building&, int32_t);
 	void send_player_enemyflagaction(const Flag&, PlayerNumber, uint32_t count);

=== modified file 'src/logic/map_objects/tribes/building.cc'
--- src/logic/map_objects/tribes/building.cc	2016-10-26 19:43:40 +0000
+++ src/logic/map_objects/tribes/building.cc	2016-11-01 14:41:18 +0000
@@ -470,6 +470,10 @@
 	throw wexception("%s (%u) has no WaresQueue for %u", descr().name().c_str(), serial(), wi);
 }
 
+WorkersQueue & Building::workersqueue(DescriptionIndex const wi) {
+	throw wexception("%s (%u) has no WorkersQueue for %u", descr().name().c_str(), serial(), wi);
+}
+
 /*
 ===============
 This function is called by workers in the buildingwork task.

=== modified file 'src/logic/map_objects/tribes/building.h'
--- src/logic/map_objects/tribes/building.h	2016-10-26 19:43:40 +0000
+++ src/logic/map_objects/tribes/building.h	2016-11-01 14:41:18 +0000
@@ -50,6 +50,7 @@
 struct Message;
 class TribeDescr;
 class WaresQueue;
+class WorkersQueue;
 
 class Building;
 
@@ -238,6 +239,9 @@
 	/// \returns the queue for a ware type or \throws WException.
 	virtual WaresQueue& waresqueue(DescriptionIndex);
 
+	/// \returns the queue for a worker type or \throws WException.
+	virtual WorkersQueue & workersqueue(DescriptionIndex);
+
 	virtual bool burn_on_destroy();
 	void destroy(EditorGameBase&) override;
 

=== modified file 'src/logic/map_objects/tribes/production_program.cc'
--- src/logic/map_objects/tribes/production_program.cc	2016-09-27 06:30:47 +0000
+++ src/logic/map_objects/tribes/production_program.cc	2016-11-01 14:41:18 +0000
@@ -32,6 +32,7 @@
 #include "economy/economy.h"
 #include "economy/flag.h"
 #include "economy/wares_queue.h"
+#include "economy/workers_queue.h"
 #include "graphic/graphic.h"
 #include "helper.h"
 #include "io/filesystem/layered_filesystem.h"
@@ -200,8 +201,9 @@
 void ProductionProgram::parse_ware_type_group(char*& parameters,
                                               WareTypeGroup& group,
                                               const Tribes& tribes,
-                                              const BillOfMaterials& inputs) {
-	std::set<DescriptionIndex>::iterator last_insert_pos = group.first.end();
+                                              const BillOfMaterials& inputs,
+                                              const BillOfMaterials& input_workers) {
+	std::set<std::pair<DescriptionIndex, WareWorker>>::iterator last_insert_pos = group.first.end();
 	uint8_t count = 1;
 	uint8_t count_max = 0;
 	for (;;) {
@@ -211,25 +213,51 @@
 		char const terminator = *parameters;
 		*parameters = '\0';
 
-		DescriptionIndex const ware_index = tribes.safe_ware_index(ware);
-
-		for (BillOfMaterials::const_iterator input_it = inputs.begin(); input_it != inputs.end();
-		     ++input_it) {
-			if (input_it == inputs.end()) {
-				throw GameDataError("%s is not declared as an input (\"%s=<count>\" was not "
-				                    "found in the [inputs] section)",
-				                    ware, ware);
-			} else if (input_it->first == ware_index) {
-				count_max += input_it->second;
-				break;
-			}
-		}
-
-		if (group.first.size() && ware_index <= *group.first.begin())
-			throw GameDataError("wrong order of ware types within group: ware type %s appears "
-			                    "after ware type %s (fix order!)",
-			                    ware, tribes.get_ware_descr(*group.first.begin())->name().c_str());
-		last_insert_pos = group.first.insert(last_insert_pos, ware_index);
+        WareWorker type = wwWARE;
+
+        // Try as ware
+		DescriptionIndex ware_index = tribes.ware_index(ware);
+		if (tribes.ware_exists(ware_index)) {
+            for (BillOfMaterials::const_iterator input_it = inputs.begin();
+                input_it != inputs.end(); ++input_it) {
+                if (input_it == inputs.end()) {
+                    throw GameDataError
+                        ("%s is not declared as an input (\"%s=<count>\" was not "
+                         "found in the [inputs] section)",
+                         ware, ware);
+                } else if (input_it->first == ware_index) {
+                    count_max += input_it->second;
+                    break;
+                }
+            }
+        } else {
+            ware_index = tribes.worker_index(ware);
+            if (tribes.worker_exists(ware_index)) {
+                // It is a worker
+                type = wwWORKER;
+                for (BillOfMaterials::const_iterator input_it = input_workers.begin();
+                    input_it != input_workers.end(); ++input_it) {
+                    if (input_it == input_workers.end()) {
+                        throw GameDataError
+                            ("%s is not declared as an input (\"%s=<count>\" was not "
+                             "found in the [inputs] section)",
+                             ware, ware);
+                    } else if (input_it->first == ware_index) {
+                        count_max += input_it->second;
+                        break;
+                    }
+                }
+            } else {
+                throw GameDataError("Unknown ware or worker type \"%s\"", ware);
+            }
+        }
+
+		if (group.first.size() && ware_index <= group.first.begin()->first)
+			throw GameDataError
+				("wrong order of ware types within group: ware type %s appears "
+				 "after ware type %s (fix order!)",
+				 ware, tribes.get_ware_descr(group.first.begin()->first)->name().c_str());
+		last_insert_pos = group.first.insert(last_insert_pos, std::make_pair(ware_index, type));
 		*parameters = terminator;
 		switch (terminator) {
 		case ':': {
@@ -331,7 +359,7 @@
                                                const ProductionSiteDescr& descr,
                                                const Tribes& tribes) {
 	try {
-		parse_ware_type_group(parameters, group, tribes, descr.inputs());
+		parse_ware_type_group(parameters, group, tribes, descr.inputs(), descr.input_workers());
 	} catch (const WException& e) {
 		throw GameDataError("has ware_type1[,ware_type2[,...]][:N]: %s", e.what());
 	}
@@ -339,11 +367,14 @@
 bool ProductionProgram::ActReturn::SiteHas::evaluate(const ProductionSite& ps) const {
 	uint8_t count = group.second;
 	for (WaresQueue* ip_queue : ps.warequeues()) {
-		if (group.first.count(ip_queue->get_ware())) {
-			uint8_t const filled = ip_queue->get_filled();
-			if (count <= filled)
-				return true;
-			count -= filled;
+        for (auto it = group.first.begin(); it != group.first.end(); it++) {
+            if (it->first == ip_queue->get_ware() && it->second == wwWARE) {
+                uint8_t const filled = ip_queue->get_filled();
+                if (count <= filled)
+                    return true;
+                count -= filled;
+                break;
+            }
 		}
 	}
 	return false;
@@ -351,8 +382,11 @@
 
 std::string ProductionProgram::ActReturn::SiteHas::description(const Tribes& tribes) const {
 	std::vector<std::string> condition_list;
-	for (const DescriptionIndex& temp_ware : group.first) {
-		condition_list.push_back(tribes.get_ware_descr(temp_ware)->descname());
+	for (const auto& entry : group.first) {
+		if (entry.second == wwWARE)
+			condition_list.push_back(tribes.get_ware_descr(entry.first)->descname());
+		else
+			condition_list.push_back(tribes.get_worker_descr(entry.first)->descname());
 	}
 	std::string condition = i18n::localize_list(condition_list, i18n::ConcatenateWith::AND);
 	if (1 < group.second) {
@@ -373,8 +407,11 @@
 std::string
 ProductionProgram::ActReturn::SiteHas::description_negation(const Tribes& tribes) const {
 	std::vector<std::string> condition_list;
-	for (const DescriptionIndex& temp_ware : group.first) {
-		condition_list.push_back(tribes.get_ware_descr(temp_ware)->descname());
+	for (const auto& entry : group.first) {
+		if (entry.second == wwWARE)
+			condition_list.push_back(tribes.get_ware_descr(entry.first)->descname());
+		else
+			condition_list.push_back(tribes.get_worker_descr(entry.first)->descname());
 	}
 	std::string condition = i18n::localize_list(condition_list, i18n::ConcatenateWith::AND);
 	if (1 < group.second) {
@@ -760,7 +797,8 @@
 	try {
 		for (;;) {
 			consumed_wares_.resize(consumed_wares_.size() + 1);
-			parse_ware_type_group(parameters, *consumed_wares_.rbegin(), tribes, descr.inputs());
+			parse_ware_type_group
+				(parameters, *consumed_wares_.rbegin(), tribes, descr.inputs(), descr.input_workers());
 			if (!*parameters)
 				break;
 			force_skip(parameters);
@@ -775,38 +813,76 @@
 
 void ProductionProgram::ActConsume::execute(Game& game, ProductionSite& ps) const {
 	std::vector<WaresQueue*> const warequeues = ps.warequeues();
-	size_t const nr_warequeues = warequeues.size();
-	std::vector<uint8_t> consumption_quantities(nr_warequeues, 0);
+	std::vector<WorkersQueue*> const workerqueues = ps.workerqueues();
+	std::vector<uint8_t> consumption_quantities_wares(warequeues.size(), 0);
+	std::vector<uint8_t> consumption_quantities_workers(workerqueues.size(), 0);
 
 	Groups l_groups = consumed_wares_;  //  make a copy for local modification
 
 	//  Iterate over all input queues and see how much we should consume from
 	//  each of them.
-	for (size_t i = 0; i < nr_warequeues; ++i) {
+	bool found;
+	for (size_t i = 0; i < warequeues.size(); ++i) {
 		DescriptionIndex const ware_type = warequeues[i]->get_ware();
 		uint8_t nr_available = warequeues[i]->get_filled();
-		consumption_quantities[i] = 0;
+		consumption_quantities_wares[i] = 0;
 
 		//  Iterate over all consume groups and see if they want us to consume
 		//  any thing from the currently considered input queue.
-		for (Groups::iterator it = l_groups.begin(); it != l_groups.end();)
-			if (it->first.count(ware_type)) {
-				if (it->second <= nr_available) {
-					//  There are enough wares of the currently considered type
-					//  to fulfill the requirements of the current group. We can
-					//  therefore erase the group.
-					consumption_quantities[i] += it->second;
-					nr_available -= it->second;
-					it = l_groups.erase(it);
-					//  No increment here, erase moved next element to the position
-					//  pointed to by it.
-				} else {
-					consumption_quantities[i] += nr_available;
-					it->second -= nr_available;
-					++it;  //  Now check if the next group includes this ware type.
-				}
-			} else
-				++it;
+		for (Groups::iterator it = l_groups.begin(); it != l_groups.end();) {
+			found = false;
+			for (auto it2 = it->first.begin(); it2 != it->first.end(); it2++) {
+				if (it2->first == ware_type && it2->second == wwWARE) {
+					found = true;
+					if (it->second <= nr_available) {
+						//  There are enough wares of the currently considered type
+						//  to fulfill the requirements of the current group. We can
+						//  therefore erase the group.
+						consumption_quantities_wares[i] += it->second;
+						nr_available -= it->second;
+						it = l_groups.erase(it);
+						//  No increment here, erase moved next element to the position
+						//  pointed to by it.
+					} else {
+						consumption_quantities_wares[i] += nr_available;
+						it->second -= nr_available;
+						++it; //  Now check if the next group includes this ware type.
+					}
+					break;
+				}
+			}
+			// group does not request ware
+			if (!found)
+				++it;
+		}
+	}
+
+    // Same for workers
+	for (size_t i = 0; i < workerqueues.size(); ++i) {
+		DescriptionIndex const worker_type = workerqueues[i]->get_worker();
+		uint8_t nr_available = workerqueues[i]->workers().size();
+		consumption_quantities_workers[i] = 0;
+
+		for (Groups::iterator it = l_groups.begin(); it != l_groups.end();) {
+			found = false;
+			for (auto it2 = it->first.begin(); it2 != it->first.end(); it2++) {
+				if (it2->first == worker_type && it2->second == wwWORKER) {
+					found = true;
+					if (it->second <= nr_available) {
+						consumption_quantities_workers[i] += it->second;
+						nr_available -= it->second;
+						it = l_groups.erase(it);
+					} else {
+						consumption_quantities_workers[i] += nr_available;
+						it->second -= nr_available;
+						++it;
+					}
+					break;
+				}
+			}
+			if (!found)
+				++it;
+		}
 	}
 
 	// "Did not start working because .... is/are missing"
@@ -818,8 +894,11 @@
 			assert(group.first.size());
 
 			std::vector<std::string> ware_list;
-			for (const DescriptionIndex& ware : group.first) {
-				ware_list.push_back(tribe.get_ware_descr(ware)->descname());
+			for (const auto& entry : group.first) {
+				if (entry.second == wwWARE)
+					ware_list.push_back(tribe.get_ware_descr(entry.first)->descname());
+				else
+					ware_list.push_back(tribe.get_worker_descr(entry.first)->descname());
 			}
 			std::string ware_string = i18n::localize_list(ware_list, i18n::ConcatenateWith::OR);
 
@@ -861,15 +940,20 @@
 
 		ps.set_production_result(result_string);
 		return ps.program_end(game, Failed);
-	} else {  //  we fulfilled all consumption requirements
-		for (size_t i = 0; i < nr_warequeues; ++i)
-			if (uint8_t const q = consumption_quantities[i]) {
+	} else { //  we fulfilled all consumption requirements
+		for (size_t i = 0; i < warequeues.size(); ++i)
+			if (uint8_t const q = consumption_quantities_wares[i]) {
 				assert(q <= warequeues[i]->get_filled());
 				warequeues[i]->set_filled(warequeues[i]->get_filled() - q);
 
 				// Update consumption statistic
 				ps.owner().ware_consumed(warequeues[i]->get_ware(), q);
 			}
+		for (size_t i = 0; i < workerqueues.size(); ++i)
+			if (uint8_t const q = consumption_quantities_workers[i]) {
+				assert(q <= workerqueues[i]->workers().size());
+				workerqueues[i]->remove_workers(q);
+			}
 		return ps.program_step(game);
 	}
 }

=== modified file 'src/logic/map_objects/tribes/production_program.h'
--- src/logic/map_objects/tribes/production_program.h	2016-08-04 15:49:05 +0000
+++ src/logic/map_objects/tribes/production_program.h	2016-11-01 14:41:18 +0000
@@ -52,7 +52,7 @@
 struct ProductionProgram {
 
 	/// A group of ware types with a count.
-	using WareTypeGroup = std::pair<std::set<DescriptionIndex>, uint8_t>;
+	using WareTypeGroup = std::pair<std::set<std::pair<DescriptionIndex, WareWorker>>, uint8_t>;
 	using Groups = std::vector<WareTypeGroup>;
 
 	/// Can be executed on a ProductionSite.
@@ -100,7 +100,8 @@
 	static void parse_ware_type_group(char*& parameters,
 	                                  WareTypeGroup& group,
 	                                  const Tribes& tribes,
-	                                  const BillOfMaterials& inputs);
+	                                  const BillOfMaterials& inputs,
+	                                  const BillOfMaterials& input_workers);
 
 	/// Returns from the program.
 	///

=== modified file 'src/logic/map_objects/tribes/productionsite.cc'
--- src/logic/map_objects/tribes/productionsite.cc	2016-09-02 11:48:37 +0000
+++ src/logic/map_objects/tribes/productionsite.cc	2016-11-01 14:41:18 +0000
@@ -30,6 +30,7 @@
 #include "economy/request.h"
 #include "economy/ware_instance.h"
 #include "economy/wares_queue.h"
+#include "economy/workers_queue.h"
 #include "graphic/text_constants.h"
 #include "logic/editor_game_base.h"
 #include "logic/game.h"
@@ -115,7 +116,7 @@
 				if (amount < 1 || 255 < amount) {
 					throw wexception("amount is out of range 1 .. 255");
 				}
-				DescriptionIndex const idx = egbase.tribes().ware_index(ware_name);
+				DescriptionIndex idx = egbase.tribes().ware_index(ware_name);
 				if (egbase.tribes().ware_exists(idx)) {
 					for (const auto& temp_inputs : inputs()) {
 						if (temp_inputs.first == idx) {
@@ -124,7 +125,17 @@
 					}
 					inputs_.push_back(WareAmount(idx, amount));
 				} else {
-					throw wexception("tribes do not define a ware type with this name");
+				    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");
+                            }
+                        }
+                        input_workers_.push_back(WareAmount(idx, amount));
+                    } else {
+                        throw wexception("tribes do not define a ware or worker type with this name");
+                    }
 				}
 			} catch (const WException& e) {
 				throw wexception("input \"%s=%d\": %s", ware_name.c_str(), amount, e.what());
@@ -334,6 +345,22 @@
 	throw wexception("%s (%u) has no WaresQueue for %u", descr().name().c_str(), serial(), wi);
 }
 
+WorkersQueue & ProductionSite::workersqueue(DescriptionIndex const wi) {
+	// Check for perfect match first
+	for (WorkersQueue * ip_queue : input_worker_queues_) {
+		if (ip_queue->get_worker() == wi) {
+			return *ip_queue;
+		}
+	}
+	// No perfect match, check for similar jobs
+	for (WorkersQueue * ip_queue : input_worker_queues_) {
+		if (owner().egbase().tribes().get_worker_descr(wi)->can_act_as(ip_queue->get_worker())) {
+			return *ip_queue;
+		}
+	}
+	throw wexception("%s (%u) has no WorkersQueue for %u", descr().name().c_str(), serial(), wi);
+}
+
 /**
  * Calculate statistic.
  */
@@ -406,6 +433,16 @@
 	for (WareRange i(inputs); i; ++i)
 		input_queues_[i.i] = new WaresQueue(*this, i.current->first, i.current->second);
 
+	const BillOfMaterials & input_workers = descr().input_workers();
+	input_worker_queues_.resize(input_workers.size());
+	for (WareRange i(input_workers); i; ++i) {
+		input_worker_queues_[i.i] =
+			new WorkersQueue
+			(*this,
+			 i.current->first,
+			 i.current->second);
+	}
+
 	//  Request missing workers.
 	WorkingPosition* wp = working_positions_;
 	for (const auto& temp_wp : descr().working_positions()) {
@@ -431,6 +468,9 @@
 		for (WaresQueue* ip_queue : input_queues_) {
 			ip_queue->remove_from_economy(*old);
 		}
+		for (WorkersQueue * ip_queue : input_worker_queues_) {
+			ip_queue->remove_from_economy(*old);
+		}
 	}
 
 	Building::set_economy(e);
@@ -442,6 +482,9 @@
 		for (WaresQueue* ip_queue : input_queues_) {
 			ip_queue->add_to_economy(*e);
 		}
+		for (WorkersQueue * ip_queue : input_worker_queues_) {
+			ip_queue->add_to_economy(*e);
+		}
 	}
 }
 

=== modified file 'src/logic/map_objects/tribes/productionsite.h'
--- src/logic/map_objects/tribes/productionsite.h	2016-08-04 15:49:05 +0000
+++ src/logic/map_objects/tribes/productionsite.h	2016-11-01 14:41:18 +0000
@@ -88,6 +88,9 @@
 	const BillOfMaterials& inputs() const {
 		return inputs_;
 	}
+	const BillOfMaterials & input_workers() const {
+		return input_workers_;
+	}
 	using Output = std::set<DescriptionIndex>;
 	const Output& output_ware_types() const {
 		return output_ware_types_;
@@ -119,6 +122,7 @@
 private:
 	BillOfMaterials working_positions_;
 	BillOfMaterials inputs_;
+	BillOfMaterials input_workers_;
 	Output output_ware_types_;
 	Output output_worker_types_;
 	Programs programs_;
@@ -192,6 +196,7 @@
 	}
 
 	WaresQueue& waresqueue(DescriptionIndex) override;
+	WorkersQueue& workersqueue(DescriptionIndex) override;
 
 	void init(EditorGameBase&) override;
 	void cleanup(EditorGameBase&) override;
@@ -209,6 +214,12 @@
 	const InputQueues& warequeues() const {
 		return input_queues_;
 	}
+
+	using InputWorkerQueues = std::vector<WorkersQueue*>;
+	const InputWorkerQueues& workerqueues() const {
+		return input_worker_queues_;
+	}
+
 	const std::vector<Worker*>& workers() const;
 
 	bool can_start_working() const;
@@ -303,6 +314,7 @@
 	BillOfMaterials produced_wares_;
 	BillOfMaterials recruited_workers_;
 	InputQueues input_queues_;  ///< input queues for all inputs
+	InputWorkerQueues input_worker_queues_; ///< input queues for workers
 	std::vector<bool> statistics_;
 	uint8_t last_stat_percent_;
 	// integer 0-10000000, to be divided by 10000 to get a percent, to avoid float (target range:

=== modified file 'src/logic/playercommand.cc'
--- src/logic/playercommand.cc	2016-08-04 15:49:05 +0000
+++ src/logic/playercommand.cc	2016-11-01 14:41:18 +0000
@@ -24,6 +24,7 @@
 #include "base/wexception.h"
 #include "economy/economy.h"
 #include "economy/wares_queue.h"
+#include "economy/workers_queue.h"
 #include "io/fileread.h"
 #include "io/filewrite.h"
 #include "io/streamwrite.h"
@@ -88,7 +89,9 @@
 	PLCMD_SHIP_EXPLORE = 27,
 	PLCMD_SHIP_CONSTRUCT = 28,
 	PLCMD_SHIP_SINK = 29,
-	PLCMD_SHIP_CANCELEXPEDITION = 30
+	PLCMD_SHIP_CANCELEXPEDITION = 30,
+	PLCMD_DROPWORKER = 31,
+	PLCMD_CHANGEWORKERCAPACITY = 32
 };
 
 /*** class PlayerCommand ***/
@@ -127,6 +130,10 @@
 		return new CmdEnhanceBuilding(des);
 	case PLCMD_CHANGETRAININGOPTIONS:
 		return new CmdChangeTrainingOptions(des);
+	case PLCMD_DROPWORKER:
+		return new CmdDropWorker(des);
+	case PLCMD_CHANGEWORKERCAPACITY:
+		return new CmdChangeWorkerCapacity(des);
 	case PLCMD_DROPSOLDIER:
 		return new CmdDropSoldier(des);
 	case PLCMD_CHANGESOLDIERCAPACITY:
@@ -1431,6 +1438,121 @@
 	fw.unsigned_16(value);
 }
 
+/*** class Cmd_DropWorker ***/
+
+CmdDropWorker::CmdDropWorker(StreamRead & des) :
+PlayerCommand (0, des.unsigned_8()) {
+	serial  = des.unsigned_32(); //  Serial of the building
+	worker = des.unsigned_32(); //  Serial of worker
+}
+
+void CmdDropWorker::execute (Game & game) {
+	if (upcast(ProductionSite, building, game.objects().get_object(serial))) {
+		if (&building->owner() == game.get_player(sender())) {
+			if (upcast(Worker, w, game.objects().get_object(worker)))
+				building->workersqueue(w->descr().worker_index()).drop(*w);
+		}
+	}
+}
+
+void CmdDropWorker::serialize (StreamWrite & ser) {
+	ser.unsigned_8 (PLCMD_DROPWORKER);
+	ser.unsigned_8 (sender());
+	ser.unsigned_32(serial);
+	ser.unsigned_32(worker);
+}
+
+constexpr uint16_t kCurrentPacketVersionCmdDropWorker = 1;
+
+void CmdDropWorker::read(FileRead & fr, EditorGameBase & egbase, MapObjectLoader & mol) {
+	try {
+		const uint16_t packet_version = fr.unsigned_16();
+		if (packet_version == kCurrentPacketVersionCmdDropWorker) {
+			PlayerCommand::read(fr, egbase, mol);
+			serial = get_object_serial_or_zero<PlayerImmovable>(fr.unsigned_32(), mol);
+			worker = get_object_serial_or_zero<Worker>(fr.unsigned_32(), mol);
+		} else {
+			throw UnhandledVersionError("CmdDropWorker",
+												 packet_version, kCurrentPacketVersionCmdDropWorker);
+		}
+	} catch (const WException & e) {
+		throw GameDataError("drop worker: %s", e.what());
+	}
+}
+
+void CmdDropWorker::write(FileWrite & fw, EditorGameBase & egbase, MapObjectSaver & mos) {
+	// First, write version
+	fw.unsigned_16(kCurrentPacketVersionCmdDropWorker);
+	// Write base classes
+	PlayerCommand::write(fw, egbase, mos);
+
+	//  site serial
+	fw.unsigned_32(mos.get_object_file_index_or_zero(egbase.objects().get_object(serial)));
+	//  worker serial
+	fw.unsigned_32(mos.get_object_file_index_or_zero(egbase.objects().get_object(worker)));
+}
+
+/*** Cmd_ChangeWorkerCapacity ***/
+
+CmdChangeWorkerCapacity::CmdChangeWorkerCapacity(StreamRead & des)
+	: PlayerCommand (0, des.unsigned_8()) {
+	serial = des.unsigned_32();
+	worker_type_ = des.signed_32();
+	val    = des.signed_16();
+}
+
+void CmdChangeWorkerCapacity::execute (Game & game) {
+	if (upcast(ProductionSite, building, game.objects().get_object(serial))) {
+		if (&building->owner() == game.get_player(sender())) {
+            building->workersqueue(worker_type_).change_capacity(val);
+		}
+	}
+}
+
+void CmdChangeWorkerCapacity::serialize (StreamWrite & ser) {
+	ser.unsigned_8 (PLCMD_CHANGEWORKERCAPACITY);
+	ser.unsigned_8 (sender());
+	ser.unsigned_32(serial);
+	ser.signed_32(worker_type_);
+	ser.signed_16(val);
+}
+
+constexpr uint16_t kCurrentPacketVersionChangeWorkerCapacity = 1;
+
+void CmdChangeWorkerCapacity::read(FileRead & fr, EditorGameBase & egbase, MapObjectLoader & mol) {
+	try {
+		const uint16_t packet_version = fr.unsigned_16();
+		if (packet_version == kCurrentPacketVersionChangeWorkerCapacity) {
+			PlayerCommand::read(fr, egbase, mol);
+			serial = get_object_serial_or_zero<Building>(fr.unsigned_32(), mol);
+			worker_type_ = fr.signed_32();
+			val = fr.signed_16();
+		} else {
+			throw UnhandledVersionError("CmdChangeWorkerCapacity",
+												 packet_version, kCurrentPacketVersionChangeWorkerCapacity);
+		}
+	} catch (const WException & e) {
+		throw GameDataError("change worker capacity: %s", e.what());
+	}
+}
+
+void CmdChangeWorkerCapacity::write(FileWrite & fw, EditorGameBase & egbase, MapObjectSaver & mos) {
+	// First, write version
+	fw.unsigned_16(kCurrentPacketVersionChangeWorkerCapacity);
+	// Write base classes
+	PlayerCommand::write(fw, egbase, mos);
+
+	// Now serial
+	fw.unsigned_32(mos.get_object_file_index_or_zero(egbase.objects().get_object(serial)));
+
+	// Now queue index
+	fw.signed_32(worker_type_);
+
+	// Now capacity
+	fw.signed_16(val);
+
+}
+
 /*** class Cmd_DropSoldier ***/
 
 CmdDropSoldier::CmdDropSoldier(StreamRead& des) : PlayerCommand(0, des.unsigned_8()) {

=== modified file 'src/logic/playercommand.h'
--- src/logic/playercommand.h	2016-08-04 15:49:05 +0000
+++ src/logic/playercommand.h	2016-11-01 14:41:18 +0000
@@ -674,6 +674,56 @@
 	int32_t value;
 };
 
+struct CmdDropWorker : public PlayerCommand {
+	CmdDropWorker () : PlayerCommand(), serial(0), worker(0) {} //  for savegames
+	CmdDropWorker
+		(const uint32_t    t,
+		 const int32_t    p,
+		 Building &       b,
+		 const int32_t    init_worker)
+		: PlayerCommand(t, p), serial(b.serial()), worker(init_worker)
+	{ }
+
+	// Write these commands to a file (for savegames)
+	void write(FileWrite &, EditorGameBase &, MapObjectSaver  &) override;
+	void read (FileRead  &, EditorGameBase &, MapObjectLoader &) override;
+
+	QueueCommandTypes id() const override {return QueueCommandTypes::kDropWorker;}
+
+	CmdDropWorker(StreamRead &);
+
+	void execute (Game &) override;
+	void serialize (StreamWrite &) override;
+
+private:
+	Serial serial;
+	Serial worker;
+};
+
+struct CmdChangeWorkerCapacity : public PlayerCommand {
+	CmdChangeWorkerCapacity () : PlayerCommand(), serial(0), worker_type_(), val(0) {} //  for savegames
+	CmdChangeWorkerCapacity
+		(const uint32_t t, const int32_t p, Building & b, DescriptionIndex worker_type, const int16_t delta)
+		: PlayerCommand(t, p), serial(b.serial()), worker_type_(worker_type), val(delta)
+	{ }
+
+	// Write these commands to a file (for savegames)
+	void write(FileWrite &, EditorGameBase &, MapObjectSaver  &) override;
+	void read (FileRead  &, EditorGameBase &, MapObjectLoader &) override;
+
+	QueueCommandTypes id() const override {return QueueCommandTypes::kChangeWorkerCapacity;}
+
+	CmdChangeWorkerCapacity (StreamRead &);
+
+	void execute (Game &) override;
+	void serialize (StreamWrite &) override;
+
+private:
+	Serial serial;
+	DescriptionIndex worker_type_;
+	int16_t val;
+};
+
 struct CmdDropSoldier : public PlayerCommand {
 	CmdDropSoldier() : PlayerCommand(), serial(0), soldier(0) {
 	}  //  for savegames

=== modified file 'src/logic/queue_cmd_factory.cc'
--- src/logic/queue_cmd_factory.cc	2016-08-04 15:49:05 +0000
+++ src/logic/queue_cmd_factory.cc	2016-11-01 14:41:18 +0000
@@ -46,6 +46,10 @@
 		return *new CmdEnhanceBuilding();
 	case QueueCommandTypes::kBulldoze:
 		return *new CmdBulldoze();
+	case QueueCommandTypes::kDropWorker:
+		return *new CmdDropWorker();
+	case QueueCommandTypes::kChangeWorkerCapacity:
+		return *new CmdChangeWorkerCapacity();
 	case QueueCommandTypes::kChangeTrainingOptions:
 		return *new CmdChangeTrainingOptions();
 	case QueueCommandTypes::kDropSoldier:

=== modified file 'src/logic/queue_cmd_ids.h'
--- src/logic/queue_cmd_ids.h	2016-08-04 15:49:05 +0000
+++ src/logic/queue_cmd_ids.h	2016-11-01 14:41:18 +0000
@@ -72,6 +72,9 @@
 
 	kMilitarysiteSetSoldierPreference,  // 26
 
+	kDropWorker,
+	kChangeWorkerCapacity,
+
 	kSinkShip = 121,
 	kShipCancelExpedition,
 	kPortStartExpedition,

=== modified file 'src/map_io/map_buildingdata_packet.cc'
--- src/map_io/map_buildingdata_packet.cc	2016-08-04 15:49:05 +0000
+++ src/map_io/map_buildingdata_packet.cc	2016-11-01 14:41:18 +0000
@@ -30,6 +30,7 @@
 #include "economy/request.h"
 #include "economy/warehousesupply.h"
 #include "economy/wares_queue.h"
+#include "economy/workers_queue.h"
 #include "io/fileread.h"
 #include "io/filewrite.h"
 #include "logic/editor_game_base.h"
@@ -685,6 +686,19 @@
 				}
 			}
 
+			nr_queues = fr.unsigned_16();
+			assert(!productionsite.input_worker_queues_.size());
+			for (uint16_t i = 0; i < nr_queues; ++i) {
+				WorkersQueue * wq = new WorkersQueue(productionsite, INVALID_INDEX, 0);
+				wq->read(fr, game, mol);
+
+				if (!game.tribes().worker_exists(wq->get_worker())) {
+					delete wq;
+				} else {
+					productionsite.input_worker_queues_.push_back(wq);
+				}
+			}
+
 			uint16_t const stats_size = fr.unsigned_16();
 			productionsite.statistics_.resize(stats_size);
 			for (uint32_t i = 0; i < productionsite.statistics_.size(); ++i)
@@ -1129,6 +1143,11 @@
 	for (uint16_t i = 0; i < input_queues_size; ++i)
 		productionsite.input_queues_[i]->write(fw, game, mos);
 
+    const uint16_t input_worker_queues_size = productionsite.input_worker_queues_.size();
+	fw.unsigned_16(input_worker_queues_size);
+	for (uint16_t i = 0; i < input_worker_queues_size; ++i)
+		productionsite.input_worker_queues_[i]->write(fw, game, mos);
+
 	const uint16_t statistics_size = productionsite.statistics_.size();
 	fw.unsigned_16(statistics_size);
 	for (uint32_t i = 0; i < statistics_size; ++i)

=== modified file 'src/scripting/lua_map.cc'
--- src/scripting/lua_map.cc	2016-10-16 09:31:42 +0000
+++ src/scripting/lua_map.cc	2016-11-01 14:41:18 +0000
@@ -27,6 +27,7 @@
 #include "base/macros.h"
 #include "base/wexception.h"
 #include "economy/wares_queue.h"
+#include "economy/workers_queue.h"
 #include "graphic/graphic.h"
 #include "logic/findimmovable.h"
 #include "logic/map_objects/checkstep.h"
@@ -148,11 +149,13 @@
 
 using SoldiersMap = std::map<SoldierMapDescr, Widelands::Quantity>;
 using WaresMap = std::map<Widelands::DescriptionIndex, Widelands::Quantity>;
+using InputMap = std::map<std::pair<Widelands::DescriptionIndex, Widelands::WareWorker>, Widelands::Quantity>;
 using WorkersMap = std::map<Widelands::DescriptionIndex, Widelands::Quantity>;
 using SoldierAmount = std::pair<SoldierMapDescr, Widelands::Quantity>;
 using WorkerAmount = std::pair<Widelands::DescriptionIndex, Widelands::Quantity>;
 using PlrInfluence = std::pair<Widelands::PlayerNumber, Widelands::MilitaryInfluence>;
 using WaresSet = std::set<Widelands::DescriptionIndex>;
+using InputSet = std::set<std::pair<Widelands::DescriptionIndex, Widelands::WareWorker>>;
 using WorkersSet = std::set<Widelands::DescriptionIndex>;
 using SoldiersList = std::vector<Widelands::Soldier*>;
 
@@ -227,6 +230,105 @@
 PARSERS(worker, Worker)
 #undef PARSERS
 
+// Versions of the above macros which accept wares and workers
+InputSet parse_get_input_arguments(
+   lua_State* L, const TribeDescr& tribe, bool* return_number) {
+	/* takes either "all", a name or an array of names */
+	int32_t nargs = lua_gettop(L);
+	if (nargs != 2)
+		report_error(L, "Wrong number of arguments to get_inputs!");
+	*return_number = false;
+	InputSet rv;
+	if (lua_isstring(L, 2)) {
+		std::string what = luaL_checkstring(L, -1);
+		if (what == "all") {
+			for (const DescriptionIndex& i : tribe.wares()) {
+				rv.insert(std::make_pair(i, wwWARE));
+			}
+			for (const DescriptionIndex& i : tribe.workers()) {
+				rv.insert(std::make_pair(i, wwWORKER));
+			}
+		} else {
+			/* Only one item requested */
+			DescriptionIndex index = tribe.ware_index(what);
+			if (tribe.has_ware(index)) {
+				rv.insert(std::make_pair(index, wwWARE));
+				*return_number = true;
+			} else {
+				index = tribe.worker_index(what);
+				if (tribe.has_worker(index)) {
+					rv.insert(std::make_pair(index, wwWORKER));
+					*return_number = true;
+				} else {
+					report_error(L, "Invalid input: <%s>", what.c_str());
+				}
+			}
+		}
+	} else {
+		/* array of names */
+		luaL_checktype(L, 2, LUA_TTABLE);
+		lua_pushnil(L);
+		while (lua_next(L, 2) != 0) {
+			std::string what = luaL_checkstring(L, -1);
+			DescriptionIndex index = tribe.ware_index(what);
+			if (tribe.has_ware(index)) {
+				rv.insert(std::make_pair(index, wwWARE));
+			} else {
+				index = tribe.worker_index(what);
+				if (tribe.has_worker(index)) {
+					rv.insert(std::make_pair(index, wwWORKER));
+				} else {
+					report_error(L, "Invalid input: <%s>", what.c_str());
+				}
+			}
+			lua_pop(L, 1);
+		}
+	}
+	return rv;
+}
+
+InputMap parse_set_input_arguments(lua_State* L, const TribeDescr& tribe) {
+	int32_t nargs = lua_gettop(L);
+	if (nargs != 2 && nargs != 3)
+		report_error(L, "Wrong number of arguments to set_inputs!");
+	InputMap rv;
+	if (nargs == 3) {
+		/* name amount */
+		std::string what = luaL_checkstring(L, 2);
+		DescriptionIndex index = tribe.ware_index(what);
+		if (tribe.has_ware(index)) {
+			rv.insert(std::make_pair(std::make_pair(index, wwWARE), luaL_checkuint32(L, 3)));
+		} else {
+			index = tribe.worker_index(what);
+			if (tribe.has_worker(index)) {
+				rv.insert(std::make_pair(std::make_pair(index, wwWORKER), luaL_checkuint32(L, 3)));
+			} else {
+				report_error(L, "Invalid input: <%s>", what.c_str());
+			}
+		}
+	} else {
+		/* array of (name, count) */
+		luaL_checktype(L, 2, LUA_TTABLE);
+		lua_pushnil(L);
+		while (lua_next(L, 2) != 0) {
+			std::string what = luaL_checkstring(L, -2);
+			DescriptionIndex index = tribe.ware_index(what);
+			if (tribe.has_ware(index)) {
+				rv.insert(std::make_pair(std::make_pair(index, wwWARE), luaL_checkuint32(L, -1)));
+			} else {
+				index = tribe.worker_index(what);
+				if (tribe.has_worker(index)) {
+					rv.insert(std::make_pair(std::make_pair(index, wwWORKER), luaL_checkuint32(L, -1)));
+				} else {
+					report_error(L, "Invalid input: <%s>", what.c_str());
+				}
+			}
+			lua_pop(L, 1);
+		}
+	}
+	return rv;
+}
+
 WaresMap count_wares_on_flag_(Flag& f, const Tribes& tribes) {
 	WaresMap rv;
 
@@ -1998,8 +2100,12 @@
 		for (const auto& group : program.consumed_wares()) {
 			lua_pushuint32(L, ++counter);
 			lua_newtable(L);
-			for (const DescriptionIndex& ware_index : group.first) {
-				lua_pushstring(L, get_egbase(L).tribes().get_ware_descr(ware_index)->name());
+			for (const auto& entry : group.first) {
+			    const DescriptionIndex& index = entry.first;
+                if (entry.second == wwWARE)
+                    lua_pushstring(L, get_egbase(L).tribes().get_ware_descr(index)->name());
+                else
+                    lua_pushstring(L, get_egbase(L).tribes().get_worker_descr(index)->name());
 				lua_pushuint32(L, group.second);
 				lua_settable(L, -3);
 			}
@@ -4065,15 +4171,15 @@
 */
 const char LuaProductionSite::className[] = "ProductionSite";
 const MethodType<LuaProductionSite> LuaProductionSite::Methods[] = {
-   METHOD(LuaProductionSite, set_wares),
-   METHOD(LuaProductionSite, get_wares),
+   METHOD(LuaProductionSite, set_inputs),
+   METHOD(LuaProductionSite, get_inputs),
    METHOD(LuaProductionSite, get_workers),
    METHOD(LuaProductionSite, set_workers),
    {nullptr, nullptr},
 };
 const PropertyType<LuaProductionSite> LuaProductionSite::Properties[] = {
    PROP_RO(LuaProductionSite, valid_workers),
-   PROP_RO(LuaProductionSite, valid_wares),
+   PROP_RO(LuaProductionSite, valid_inputs),
    {nullptr, nullptr, nullptr},
 };
 
@@ -4083,7 +4189,7 @@
  ==========================================================
  */
 // documented in parent class
-int LuaProductionSite::get_valid_wares(lua_State* L) {
+int LuaProductionSite::get_valid_inputs(lua_State* L) {
 	EditorGameBase& egbase = get_egbase(L);
 	ProductionSite* ps = get(L, egbase);
 
@@ -4094,6 +4200,12 @@
 		lua_pushuint32(L, input_ware.second);
 		lua_rawset(L, -3);
 	}
+	for (const auto& input_worker : ps->descr().input_workers()) {
+		const WorkerDescr* descr = egbase.tribes().get_worker_descr(input_worker.first);
+		lua_pushstring(L, descr->name());
+		lua_pushuint32(L, input_worker.second);
+		lua_rawset(L, -3);
+	}
 	return 1;
 }
 
@@ -4108,62 +4220,88 @@
  LUA METHODS
  ==========================================================
  */
-
 // documented in parent class
-int LuaProductionSite::set_wares(lua_State* L) {
+int LuaProductionSite::set_inputs(lua_State* L) {
 	ProductionSite* ps = get(L, get_egbase(L));
 	const TribeDescr& tribe = ps->owner().tribe();
-	WaresMap setpoints = parse_set_wares_arguments(L, tribe);
+	InputMap setpoints = parse_set_input_arguments(L, tribe);
 
-	WaresSet valid_wares;
+	InputSet valid_inputs;
 	for (const auto& input_ware : ps->descr().inputs()) {
-		valid_wares.insert(input_ware.first);
+		valid_inputs.insert(std::make_pair(input_ware.first, wwWARE));
+	}
+	for (const auto& input_worker : ps->descr().input_workers()) {
+		valid_inputs.insert(std::make_pair(input_worker.first, wwWORKER));
 	}
 	for (const auto& sp : setpoints) {
-		if (!valid_wares.count(sp.first)) {
-			report_error(L, "<%s> can't be stored in this building: %s!",
-			             tribe.get_ware_descr(sp.first)->name().c_str(), ps->descr().name().c_str());
-		}
-		WaresQueue& wq = ps->waresqueue(sp.first);
-		if (sp.second > wq.get_max_size()) {
-			report_error(
-			   L, "Not enough space for %u items, only for %i", sp.second, wq.get_max_size());
-		}
-		wq.set_filled(sp.second);
+		if (!valid_inputs.count(sp.first)) {
+			if (sp.first.second == wwWARE)
+				report_error(L, "<%s> can't be stored in this building: %s!",
+						tribe.get_ware_descr(sp.first.first)->name().c_str(), ps->descr().name().c_str());
+			else
+				report_error(L, "<%s> can't be stored in this building: %s!",
+						tribe.get_worker_descr(sp.first.first)->name().c_str(), ps->descr().name().c_str());
+		}
+		if (sp.first.second == wwWARE) {
+			WaresQueue& wq = ps->waresqueue(sp.first.first);
+			if (sp.second > wq.get_max_size()) {
+				report_error(
+				   L, "Not enough space for %u items, only for %i", sp.second, wq.get_max_size());
+			}
+			wq.set_filled(sp.second);
+		} else {
+			assert(sp.first.second == wwWORKER);
+			WorkersQueue& wq = ps->workersqueue(sp.first.first);
+			if (sp.second > wq.max_capacity()) {
+				report_error(
+				   L, "Not enough space for %u workers, only for %i", sp.second, wq.max_capacity());
+			}
+			wq.set_filled(sp.second);
+		}
 	}
 
 	return 0;
 }
 
 // documented in parent class
-int LuaProductionSite::get_wares(lua_State* L) {
+int LuaProductionSite::get_inputs(lua_State* L) {
 	ProductionSite* ps = get(L, get_egbase(L));
 	const TribeDescr& tribe = ps->owner().tribe();
 
 	bool return_number = false;
-	WaresSet wares_set = parse_get_wares_arguments(L, tribe, &return_number);
+	InputSet input_set = parse_get_input_arguments(L, tribe, &return_number);
 
-	WaresSet valid_wares;
+	InputSet valid_inputs;
 	for (const auto& input_ware : ps->descr().inputs()) {
-		valid_wares.insert(input_ware.first);
+		valid_inputs.insert(std::make_pair(input_ware.first, wwWARE));
+	}
+	for (const auto& input_worker : ps->descr().input_workers()) {
+		valid_inputs.insert(std::make_pair(input_worker.first, wwWORKER));
 	}
 
-	if (wares_set.size() == tribe.get_nrwares())  // Want all returned
-		wares_set = valid_wares;
+	if (input_set.size() == tribe.get_nrwares() + tribe.get_nrworkers())  // Want all returned
+		input_set = valid_inputs;
 
 	if (!return_number)
 		lua_newtable(L);
 
-	for (const Widelands::DescriptionIndex& ware : wares_set) {
+	for (const auto& input : input_set) {
 		uint32_t cnt = 0;
-		if (valid_wares.count(ware))
-			cnt = ps->waresqueue(ware).get_filled();
+		if (valid_inputs.count(input)) {
+			if (input.second == wwWARE)
+				cnt = ps->waresqueue(input.first).get_filled();
+			else
+				cnt = ps->workersqueue(input.first).get_filled();
+		}
 
 		if (return_number) {  // this is the only thing the customer wants to know
 			lua_pushuint32(L, cnt);
 			break;
 		} else {
-			lua_pushstring(L, tribe.get_ware_descr(ware)->name());
+			if (input.second == wwWARE)
+				lua_pushstring(L, tribe.get_ware_descr(input.first)->name());
+			else
+				lua_pushstring(L, tribe.get_worker_descr(input.first)->name());
 			lua_pushuint32(L, cnt);
 			lua_rawset(L, -3);
 		}

=== modified file 'src/scripting/lua_map.h'
--- src/scripting/lua_map.h	2016-08-04 15:49:05 +0000
+++ src/scripting/lua_map.h	2016-11-01 14:41:18 +0000
@@ -1033,15 +1033,15 @@
 	/*
 	 * Properties
 	 */
-	int get_valid_wares(lua_State* L);
+	int get_valid_inputs(lua_State* L);
 	int get_valid_workers(lua_State* L);
 
 	/*
 	 * Lua Methods
 	 */
-	int get_wares(lua_State* L);
+	int get_inputs(lua_State* L);
 	int get_workers(lua_State* L);
-	int set_wares(lua_State* L);
+	int set_inputs(lua_State* L);
 	int set_workers(lua_State* L);
 
 	/*

=== modified file 'src/wui/CMakeLists.txt'
--- src/wui/CMakeLists.txt	2016-10-25 07:07:14 +0000
+++ src/wui/CMakeLists.txt	2016-11-01 14:41:18 +0000
@@ -179,11 +179,15 @@
     productionsitewindow.cc
     productionsitewindow.h
     shipwindow.cc
+    workercapacitycontrol.cc
+    workercapacitycontrol.h
     shipwindow.h
     soldiercapacitycontrol.cc
     soldiercapacitycontrol.h
     soldierlist.cc
     soldierlist.h
+    workerpanel.cc
+    workerpanel.h
     stock_menu.cc
     stock_menu.h
     story_message_box.cc

=== modified file 'src/wui/productionsitewindow.cc'
--- src/wui/productionsitewindow.cc	2016-08-04 15:49:05 +0000
+++ src/wui/productionsitewindow.cc	2016-11-01 14:41:18 +0000
@@ -31,6 +31,7 @@
 #include "ui_basic/tabpanel.h"
 #include "ui_basic/textarea.h"
 #include "wui/waresqueuedisplay.h"
+#include "wui/workerpanel.h"
 
 using Widelands::ProductionSite;
 
@@ -97,6 +98,12 @@
 		   (ngettext("Worker", "Workers", productionsite().descr().nr_working_positions())));
 		update_worker_table();
 	}
+
+    // Input workers. Make "worker-worker"-panel first to match order in training sites
+    const std::vector<Widelands::WorkersQueue*>& workerqueues = ps.workerqueues();
+    for (uint32_t i = 0; i < workerqueues.size(); ++i)
+        add_worker_panel(get_tabs(), parent, ps, i, *workerqueues[i]);
+
 }
 
 void ProductionSiteWindow::think() {

=== added file 'src/wui/workercapacitycontrol.cc'
--- src/wui/workercapacitycontrol.cc	1970-01-01 00:00:00 +0000
+++ src/wui/workercapacitycontrol.cc	2016-11-01 14:41:18 +0000
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2002-2004, 2006-2010 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 "wui/workercapacitycontrol.h"
+
+#include "economy/workers_queue.h"
+#include "graphic/graphic.h"
+#include "logic/player.h"
+#include "ui_basic/button.h"
+#include "ui_basic/radiobutton.h"
+#include "wui/interactive_gamebase.h"
+
+/**
+ * Widget to control the capacity of \ref ProductionBuilding
+ * Adapted copy of \ref SoldierCapacityControl
+ */
+struct WorkerCapacityControl : UI::Box {
+	WorkerCapacityControl
+		(UI::Panel * parent, InteractiveGameBase & igb,
+		 Widelands::Building & building,
+		 Widelands::DescriptionIndex index,
+		 Widelands::WorkersQueue & workers);
+
+protected:
+	void think() override;
+
+private:
+	void change_worker_capacity(int16_t delta);
+	void click_decrease();
+	void click_increase();
+
+	InteractiveGameBase & igbase_;
+	Widelands::Building & building_;
+	Widelands::DescriptionIndex index_;
+	Widelands::WorkersQueue & workers_;
+
+	UI::Button decrease_;
+	UI::Button increase_;
+	UI::Textarea value_;
+};
+
+WorkerCapacityControl::WorkerCapacityControl
+	(UI::Panel * parent, InteractiveGameBase & igb,
+	 Widelands::Building & building,
+	 Widelands::DescriptionIndex index,
+	 Widelands::WorkersQueue & workers)
+:
+Box(parent, 0, 0, Horizontal),
+igbase_(igb),
+building_(building),
+index_(index),
+workers_(workers),
+decrease_
+	(this, "decrease", 0, 0, 32, 32,
+	 g_gr->images().get("images/ui_basic/but4.png"),
+	 g_gr->images().get("images/wui/buildings/menu_down_train.png"), _("Decrease capacity")),
+increase_
+	(this, "increase", 0, 0, 32, 32,
+	 g_gr->images().get("images/ui_basic/but4.png"),
+	 g_gr->images().get("images/wui/buildings/menu_up_train.png"), _("Increase capacity")),
+value_(this, "199", UI::Align::kCenter)
+{
+	decrease_.sigclicked.connect(boost::bind(&WorkerCapacityControl::click_decrease, boost::ref(*this)));
+	increase_.sigclicked.connect(boost::bind(&WorkerCapacityControl::click_increase, boost::ref(*this)));
+
+	add(new UI::Textarea(this, _("Capacity")), UI::Align::kHCenter);
+	add(&decrease_, UI::Align::kHCenter);
+	add(&value_, UI::Align::kHCenter);
+	add(&increase_, UI::Align::kHCenter);
+
+	decrease_.set_repeating(true);
+	increase_.set_repeating(true);
+
+	set_thinks(true);
+}
+
+void WorkerCapacityControl::think()
+{
+	uint32_t const capacity = workers_.capacity();
+	char buffer[sizeof("4294967295")];
+
+	sprintf(buffer, "%2u", capacity);
+	value_.set_text(buffer);
+
+	bool const can_act = igbase_.can_act(building_.owner().player_number());
+	decrease_.set_enabled(can_act && 0 < capacity);
+	increase_.set_enabled(can_act && workers_.max_capacity() > capacity);
+}
+
+void WorkerCapacityControl::change_worker_capacity(int16_t delta)
+{
+	igbase_.game().send_player_change_worker_capacity(building_, workers_.get_worker(), delta);
+}
+
+void WorkerCapacityControl::click_decrease()
+{
+	change_worker_capacity(-1);
+}
+
+void WorkerCapacityControl::click_increase()
+{
+	change_worker_capacity(1);
+}
+
+UI::Panel * create_worker_capacity_control
+	(UI::Panel & parent, InteractiveGameBase & igb,
+	 Widelands::Building & building,
+	 Widelands::DescriptionIndex index,
+	 Widelands::WorkersQueue & workers)
+{
+	return new WorkerCapacityControl(&parent, igb, building, index, workers);
+}

=== added file 'src/wui/workercapacitycontrol.h'
--- src/wui/workercapacitycontrol.h	1970-01-01 00:00:00 +0000
+++ src/wui/workercapacitycontrol.h	2016-11-01 14:41:18 +0000
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2002-2004, 2006-2013 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_WUI_WORKERCAPACITYCONTROL_H
+#define WL_WUI_WORKERCAPACITYCONTROL_H
+
+#include "logic/widelands.h"
+
+class InteractiveGameBase;
+
+namespace UI {
+class Panel;
+}
+
+namespace Widelands {
+class Building;
+class WorkersQueue;
+}
+
+UI::Panel * create_worker_capacity_control
+	(UI::Panel & parent,
+	 InteractiveGameBase & igb,
+	 Widelands::Building & building,
+	 Widelands::DescriptionIndex index,
+	 Widelands::WorkersQueue & workers);
+
+#endif  // end of include guard: WL_WUI_WORKERCAPACITYCONTROL_H

=== added file 'src/wui/workerpanel.cc'
--- src/wui/workerpanel.cc	1970-01-01 00:00:00 +0000
+++ src/wui/workerpanel.cc	2016-11-01 14:41:18 +0000
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2002-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 "wui/workerpanel.h"
+
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+
+#include "base/macros.h"
+#include "economy/workers_queue.h"
+#include "graphic/font_handler1.h"
+#include "graphic/graphic.h"
+#include "graphic/rendertarget.h"
+#include "logic/map_objects/tribes/building.h"
+#include "logic/map_objects/tribes/soldier.h"
+#include "logic/map_objects/tribes/worker.h"
+#include "logic/map_objects/tribes/worker_descr.h"
+#include "logic/player.h"
+#include "logic/widelands.h"
+#include "ui_basic/box.h"
+#include "ui_basic/button.h"
+#include "ui_basic/tabpanel.h"
+#include "wlapplication.h"
+#include "wui/interactive_gamebase.h"
+#include "wui/workercapacitycontrol.h"
+
+
+using Widelands::Worker;
+using Widelands::WorkersQueue;
+using Widelands::DescriptionIndex;
+using Widelands::Building;
+
+namespace {
+
+constexpr uint32_t kMaxColumns = 6;
+constexpr uint32_t kAnimateSpeed = 300; ///< in pixels per second
+constexpr uint32_t kIconBorder = 2;
+
+} // namespace
+
+/**
+ * Iconic representation of workers in a queue.
+ * Adapted copy of \ref SoldierPanel
+ */
+struct WorkerPanel : UI::Panel {
+	using WorkerFn = boost::function<void (const Worker *)>;
+
+	WorkerPanel(UI::Panel & parent, Widelands::EditorGameBase & egbase, WorkersQueue & workers_queue);
+
+	Widelands::EditorGameBase & egbase() const {return egbase_;}
+
+	void think() override;
+	void draw(RenderTarget &) override;
+
+	void set_mouseover(const WorkerFn & fn);
+	void set_click(const WorkerFn & fn);
+
+protected:
+	void handle_mousein(bool inside) override;
+	bool handle_mousemove(uint8_t state, int32_t x, int32_t y, int32_t xdiff, int32_t ydiff) override;
+	bool handle_mousepress(uint8_t btn, int32_t x, int32_t y) override;
+
+private:
+	Vector2i calc_pos(uint32_t row, uint32_t col) const;
+	const Worker * find_worker(int32_t x, int32_t y) const;
+
+	struct Icon {
+		Widelands::OPtr<Worker> worker;
+		uint32_t row;
+		uint32_t col;
+		Vector2i pos;
+
+		/**
+		 * Experience when last drawing
+		 */
+		uint32_t cache_experience;
+        // The experience is currently not shown on top of the icon,
+        // this remains as further work.
+	};
+
+	Widelands::EditorGameBase & egbase_;
+	WorkersQueue& workers_;
+
+	WorkerFn mouseover_fn_;
+	WorkerFn click_fn_;
+
+	std::vector<Icon> icons_;
+
+	uint32_t rows_;
+	uint32_t cols_;
+
+	uint32_t icon_width_;
+	uint32_t icon_height_;
+
+	int32_t last_animate_time_;
+};
+
+WorkerPanel::WorkerPanel
+	(UI::Panel & parent,
+	 Widelands::EditorGameBase & gegbase,
+	 WorkersQueue & workers_queue)
+:
+Panel(&parent, 0, 0, 0, 0),
+egbase_(gegbase),
+workers_(workers_queue),
+last_animate_time_(0)
+{
+	{
+	    // Fetch the icon of some worker and retrieve its size
+	    assert(egbase_.tribes().nrtribes() > 0);
+	    assert(egbase_.tribes().nrworkers() > 0);
+	    // ID=0 should be the first worker created and anyone is good enough
+        const Image * some_icon = egbase_.tribes().get_worker_descr(0)->icon();
+        icon_width_ = some_icon->width();
+        icon_height_ = some_icon->height();
+	}
+	icon_width_ += 2 * kIconBorder;
+	icon_height_ += 2 * kIconBorder;
+
+	Widelands::Quantity maxcapacity = workers_.max_capacity();
+	if (maxcapacity <= kMaxColumns) {
+		cols_ = maxcapacity;
+		rows_ = 1;
+	} else {
+		cols_ = kMaxColumns;
+		rows_ = (maxcapacity + cols_ - 1) / cols_;
+	}
+
+	set_size(cols_ * icon_width_, rows_ * icon_height_);
+	set_desired_size(cols_ * icon_width_, rows_ * icon_height_);
+	set_thinks(true);
+
+	// Initialize the icons
+	uint32_t row = 0;
+	uint32_t col = 0;
+	for (Worker * worker : workers_.workers()) {
+		Icon icon;
+		icon.worker = worker;
+		icon.row = row;
+		icon.col = col;
+		icon.pos = calc_pos(row, col);
+		icons_.push_back(icon);
+
+		if (++col >= cols_) {
+			col = 0;
+			row++;
+		}
+	}
+}
+
+/**
+ * Set the callback function that indicates which worker the mouse is over.
+ */
+void WorkerPanel::set_mouseover(const WorkerPanel::WorkerFn & fn)
+{
+	mouseover_fn_ = fn;
+}
+
+/**
+ * Set the callback function that is called when a worker is clicked.
+ */
+void WorkerPanel::set_click(const WorkerPanel::WorkerFn & fn)
+{
+	click_fn_ = fn;
+}
+
+void WorkerPanel::think()
+{
+	bool changes = false;
+	uint32_t capacity = workers_.capacity();
+
+	// Update worker list and target row/col:
+	std::vector<Worker *> workerlist = workers_.workers();
+	std::vector<uint32_t> row_occupancy;
+	row_occupancy.resize(rows_);
+
+	// First pass: check whether existing icons are still valid, and compact them
+	for (uint32_t idx = 0; idx < icons_.size(); ++idx) {
+		Icon & icon = icons_[idx];
+		Worker * worker = icon.worker.get(egbase());
+		if (worker) {
+			std::vector<Worker *>::iterator it = std::find(workerlist.begin(), workerlist.end(), worker);
+			if (it != workerlist.end())
+				workerlist.erase(it);
+			else
+				worker = nullptr;
+		}
+
+		if (!worker) {
+			icons_.erase(icons_.begin() + idx);
+			idx--;
+			changes = true;
+			continue;
+		}
+
+		while
+			(icon.row &&
+			 (row_occupancy[icon.row] >= kMaxColumns ||
+			  icon.row * kMaxColumns + row_occupancy[icon.row] >= capacity))
+			icon.row--;
+
+		icon.col = row_occupancy[icon.row]++;
+	}
+
+	// Second pass: add new workers
+	while (!workerlist.empty()) {
+		Icon icon;
+		icon.worker = workerlist.back();
+		workerlist.pop_back();
+		icon.row = 0;
+		while (row_occupancy[icon.row] >= kMaxColumns)
+			icon.row++;
+		icon.col = row_occupancy[icon.row]++;
+		icon.pos = calc_pos(icon.row, icon.col);
+
+		// Let workers slide in from the right border
+		icon.pos.x = get_w();
+
+		std::vector<Icon>::iterator insertpos = icons_.begin();
+
+		for (std::vector<Icon>::iterator icon_iter = icons_.begin();
+			  icon_iter != icons_.end();
+			  ++icon_iter) {
+
+			if (icon_iter->row <= icon.row)
+				insertpos = icon_iter + 1;
+
+			icon.pos.x = std::max<int32_t>(icon.pos.x, icon_iter->pos.x + icon_width_);
+		}
+
+		icon.cache_experience = 0;
+
+		icons_.insert(insertpos, icon);
+		changes = true;
+	}
+
+	// Third pass: animate icons
+	int32_t curtime = SDL_GetTicks();
+	int32_t dt = std::min(std::max(curtime - last_animate_time_, 0), 1000);
+	int32_t maxdist = dt * kAnimateSpeed / 1000;
+	last_animate_time_ = curtime;
+
+	for (Icon& icon : icons_) {
+		Vector2i goal = calc_pos(icon.row, icon.col);
+		Vector2i dp = goal - icon.pos;
+
+		dp.x = std::min(std::max(dp.x, -maxdist), maxdist);
+		dp.y = std::min(std::max(dp.y, -maxdist), maxdist);
+
+		if (dp.x != 0 || dp.y != 0)
+			changes = true;
+
+		icon.pos += dp;
+
+		// Check whether experience of the worker has changed
+		Worker * worker = icon.worker.get(egbase());
+		uint32_t experience = worker->get_current_experience();
+
+		if (experience != icon.cache_experience) {
+			icon.cache_experience = experience;
+			changes = true;
+		}
+	}
+
+	if (changes) {
+		Vector2i mousepos = get_mouse_position();
+		mouseover_fn_(find_worker(mousepos.x, mousepos.y));
+	}
+}
+
+void WorkerPanel::draw(RenderTarget & dst)
+{
+	// Fill a region matching the current site capacity with black
+	uint32_t capacity = workers_.capacity();
+	uint32_t fullrows = capacity / kMaxColumns;
+
+	if (fullrows)
+		dst.fill_rect(Rectf(0, 0, get_w(), icon_height_ * fullrows), RGBAColor(0, 0, 0, 0));
+	if (capacity % kMaxColumns)
+		dst.fill_rect(Rectf(0, icon_height_ * fullrows,
+			icon_width_ * (capacity % kMaxColumns), icon_height_), RGBAColor(0, 0, 0, 0));
+
+	// Draw icons
+	for (const Icon& icon : icons_) {
+		const Worker * worker = icon.worker.get(egbase());
+		if (!worker)
+			continue;
+
+		// This should probably call something similar to soldier::draw_info_icon()
+		dst.blit(icon.pos.cast<float>() + Vector2f(kIconBorder, kIconBorder), worker->descr().icon());
+		// TODO(Notabilis): Maybe print experience on top of icon
+	}
+}
+
+Vector2i WorkerPanel::calc_pos(uint32_t row, uint32_t col) const
+{
+	return Vector2i(col * icon_width_, row * icon_height_);
+}
+
+/**
+ * Return the worker (if any) at the given coordinates.
+ */
+const Worker * WorkerPanel::find_worker(int32_t x, int32_t y) const
+{
+	for (const Icon& icon : icons_) {
+		Rectf r(icon.pos, icon_width_, icon_height_);
+		if (r.contains(Vector2i(x, y))) {
+			return icon.worker.get(egbase());
+		}
+	}
+
+	return nullptr;
+}
+
+void WorkerPanel::handle_mousein(bool inside)
+{
+	if (!inside && mouseover_fn_)
+		mouseover_fn_(nullptr);
+}
+
+bool WorkerPanel::handle_mousemove
+	(uint8_t /* state */,
+	 int32_t x,
+	 int32_t y,
+	 int32_t /* xdiff */,
+	 int32_t /* ydiff */)
+{
+	if (mouseover_fn_)
+		mouseover_fn_(find_worker(x, y));
+	return true;
+}
+
+bool WorkerPanel::handle_mousepress(uint8_t btn, int32_t x, int32_t y)
+{
+	if (btn == SDL_BUTTON_LEFT) {
+		if (click_fn_) {
+			if (const Worker * worker = find_worker(x, y))
+				click_fn_(worker);
+		}
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * List of workers
+ */
+struct WorkerList : UI::Box {
+	WorkerList
+		(UI::Panel & parent,
+		 InteractiveGameBase & igb,
+		 Building & building,
+		 DescriptionIndex index,
+		 WorkersQueue & workers_queue);
+
+private:
+	void mouseover(const Worker * worker);
+	void eject(const Worker * worker);
+
+	InteractiveGameBase& igbase_;
+	Building& building_;
+	DescriptionIndex index_;
+	WorkersQueue & workers_queue_;
+	WorkerPanel workerpanel_;
+	UI::Textarea infotext_;
+};
+
+WorkerList::WorkerList
+	(UI::Panel & parent,
+	 InteractiveGameBase & igb,
+	 Building & building,
+	 DescriptionIndex index,
+	 WorkersQueue & workers_queue)
+:
+UI::Box(&parent, 0, 0, UI::Box::Vertical),
+
+igbase_(igb),
+building_(building),
+index_(index),
+workers_queue_(workers_queue),
+workerpanel_(*this, igb.egbase(), workers_queue),
+infotext_(this, _("Click worker to send away"))
+{
+	add(&workerpanel_, UI::Align::kHCenter);
+
+	add_space(2);
+
+	add(&infotext_, UI::Align::kHCenter);
+
+	workerpanel_.set_mouseover(boost::bind(&WorkerList::mouseover, this, _1));
+	workerpanel_.set_click(boost::bind(&WorkerList::eject, this, _1));
+
+	// We don't want translators to translate this twice, so it's a bit involved.
+	int w = UI::g_fh1->render(
+				  as_uifont((boost::format("%s ") // We need some extra space to fix bug 724169
+								 /** TRANSLATORS: Workertype (current experience / required experience) */
+								 % (boost::format(_("%1$s (Exp: %2$u/%3$u)"))
+									 % 8 % 8 % 8)).str()))->width();
+	uint32_t maxtextwidth = std::max(w,
+												UI::g_fh1->render(as_uifont(_("Click worker to send away")))->width());
+	set_min_desired_breadth(maxtextwidth + 4);
+
+	UI::Box * buttons = new UI::Box(this, 0, 0, UI::Box::Horizontal);
+
+	buttons->add_inf_space();
+	buttons->add
+		(create_worker_capacity_control(*buttons, igb, building, index_, workers_queue_),
+		 UI::Align::kRight);
+
+	add(buttons, UI::Align::kHCenter, true);
+}
+
+void WorkerList::mouseover(const Worker * worker)
+{
+	if (!worker) {
+		infotext_.set_text(_("Click worker to send away"));
+		return;
+	}
+
+	if (worker->needs_experience()) {
+		infotext_.set_text(
+			(boost::format(_("%1$s (Exp: %2$u/%3$u)"))
+				% worker->descr().descname()
+				% worker->get_current_experience()
+				% worker->descr().get_needed_experience()
+			).str()
+		);
+	} else {
+		infotext_.set_text(
+			(boost::format(_("%1$s (Exp: -/-)"))
+				% worker->descr().descname()
+		).str());
+	}
+}
+
+void WorkerList::eject(const Worker * worker)
+{
+	uint32_t const capacity_min = 0;
+	bool can_act = igbase_.can_act(building_.owner().player_number());
+	bool over_min = capacity_min < workers_queue_.workers().size();
+
+	if (can_act && over_min)
+		igbase_.game().send_player_drop_worker(building_, worker->serial());
+}
+
+void add_worker_panel
+	(UI::TabPanel * parent,
+	 InteractiveGameBase & igb,
+	 Widelands::Building & building,
+	 DescriptionIndex index,
+	 Widelands::WorkersQueue & workers_queue)
+{
+
+    const Widelands::WorkerDescr * desc = igb.egbase().tribes().get_worker_descr(workers_queue.get_worker());
+	parent->add(desc->name(), desc->icon(),
+                 new WorkerList(*parent, igb, building, index, workers_queue),
+                 desc->descname());
+}

=== added file 'src/wui/workerpanel.h'
--- src/wui/workerpanel.h	1970-01-01 00:00:00 +0000
+++ src/wui/workerpanel.h	2016-11-01 14:41:18 +0000
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2002-2004, 2006-2013 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_WUI_WORKERPANEL_H
+#define WL_WUI_WORKERPANEL_H
+
+#include "logic/widelands.h"
+
+class InteractiveGameBase;
+
+namespace UI {
+class TabPanel;
+}
+
+namespace Widelands {
+class Building;
+class WorkersQueue;
+}
+
+void add_worker_panel
+	(UI::TabPanel * parent,
+	 InteractiveGameBase & igb,
+	 Widelands::Building & building,
+	 Widelands::DescriptionIndex index,
+	 Widelands::WorkersQueue & workers_queue);
+
+#endif  // end of include guard: WL_WUI_WORKERPANEL_H


Follow ups