1-- RST
2-- infrastructure.lua
3-- ------------------
4--
5-- This script contains function that ease setting up an initial infrastructure
6-- for maps. This includes placing buildings and roads.
7
8-- RST
9-- .. function:: connected_road(roadtype, plr, sflag, descr[, create_carriers = true])
10--
11--    This is a convenience function to create roads and waterways. It is basically a frontend
12--    to :meth:`wl.game.Player.place_road`. The road is forced, that is other
13--    stuff in the way is removed. A sample usage:
14--
15--    .. code-block:: lua
16--
17--       local game = wl.Game()
18--       connected_road("normal", game.players[1], game.map:get_field(20,20).immovable, "r,r|br,r|r,r")
19--
20--    This would create a road starting from the Flag standing at field(20,20)
21--    which must exist and goes from there 2 steps right (east), places a new
22--    flag. Then it goes one step bottom-right and one step right, places the
23--    next flag and then goes two steps right again and places the last flag.
24--
25--    :arg roadtype: 'normal', 'busy', or 'waterway'
26--    :type roadtype: :class:`string`
27--    :arg plr: The player for which the road is created
28--    :type plr: :class:`wl.game.Player`
29--    :arg sflag: The starting flag for this road.
30--    :type sflag: :class:`wl.map.Flag`
31--    :arg descr: The description of this road. This are comma separated
32--       directions as those accepted by :meth:`wl.game.Player.place_road`. A
33--       :const:`|` defines a flag.
34--    :arg create_carriers: If this is :const:`true` carriers are created for
35--       the roads. Otherwise no carriers will be created.
36--    :type create_carriers: :class:`boolean`
37
38function connected_road(roadtype, p, start, cmd, g_create_carriers)
39   create_carriers = true
40   if g_create_carriers ~= nil then
41      create_carriers = g_create_carriers
42   end
43
44   if cmd:sub(-1) ~= "|" then
45      cmd = cmd .. "|"
46   end
47
48   moves = {}
49   for m in cmd:gmatch("%a+[,|]") do
50      moves[#moves+1] = m:sub(1,-2)
51      if(m:sub(-1) == '|') then
52         moves[#moves+1] = true -- Force the road
53         r = p:place_road(roadtype, start, table.unpack(moves))
54         start = r.end_flag
55         if create_carriers then
56            if roadtype == "normal" then r:set_workers(p.tribe.carrier, 1)
57            elseif roadtype == "busy" then r:set_workers({[p.tribe.carrier] = 1, [p.tribe.carrier2] = 1})
58            else r:set_workers(p.tribe.ferry, 1) end
59         end
60         moves = {}
61      end
62   end
63end
64
65-- RST
66-- .. function:: prefilled_buildings(plr, b1_descr[, b2_descr, ...])
67--
68--    Create pre-filled buildings. Each description is a arrays which contain
69--    building type, build field and pre-fill information. A sample usage:
70--
71--    .. code-block:: lua
72--
73--       prefilled_buildings(wl.Game().players[1],
74--          {"sentry", 57, 9}, -- Sentry completely full with soldiers
75--          {"sentry", 57, 9, soldier={[{0,0,0,0}]=1}}, -- Sentry with one soldier
76--          {"bakery", 55, 20, inputs = {wheat=6, water=6}}, -- bakery with wares and workers
77--          {"well", 52, 30}, -- a well with workers
78--       )
79--
80--    :arg plr: The player for which the building is created
81--    :type plr: :class:`wl.game.Player`
82--    :arg b1_descr: An array of tables. Each table must contain at
83--       least the name of the building, and the x and y positions of the field
84--       where the building should be created. Optional entries are:
85--
86--       wares
87--          A table of (name,count) as expected by
88--          :meth:`wl.map.Warehouse.set_wares`. This is valid for
89--          :class:`wl.map.Warehouse` and must not be used otherwise.
90--       inputs
91--          A table of (name,count) as expected by
92--          :meth:`wl.map.ProductionSite.set_inputs`. Inputs are wares or workers
93--          which are consumed by the building. This is valid for
94--          :class:`wl.map.ProductionSite` and must not be used otherwise.
95--       soldiers
96--          A table of (soldier_descr,count) as expected by
97--          :meth:`wl.map.HasSoldiers.set_soldiers`.  If this is nil, the site
98--          will be filled with {0,0,0,0} soldiers.
99--       workers
100--          A table of (workers_name,count) as expected by
101--          :meth:`wl.map.Warehouse.set_workers`.  Note that ProductionSites
102--          are filled with workers by default.
103--    :type b1_descr: :class:`array`
104--
105--    :returns: A table of created buildings
106
107function prefilled_buildings(p, ...)
108   local b_table = {}
109   for idx,bdescr in ipairs({...}) do
110      local b = p:place_building(bdescr[1], wl.Game().map:get_field(bdescr[2],bdescr[3]), false, true)
111      -- Fill with workers
112      if b.valid_workers then b:set_workers(b.valid_workers) end
113      if bdescr.workers then b:set_workers(bdescr.workers) end
114      -- Fill with soldiers
115      if b.max_soldiers and b.set_soldiers then
116         if bdescr.soldiers then
117            b:set_soldiers(bdescr.soldiers)
118         else
119            b:set_soldiers({0,0,0,0}, b.max_soldiers)
120         end
121      elseif bdescr.soldiers then -- Must be a warehouse
122         b:set_soldiers(bdescr.soldiers)
123      end
124      -- Fill with wares if this is requested
125      if bdescr.wares then b:set_wares(bdescr.wares) end
126      if bdescr.inputs then b:set_inputs(bdescr.inputs) end
127
128      table.insert(b_table, b)
129   end
130   return b_table
131end
132
133-- RST
134-- .. function:: place_building_in_region(plr, building, region[, opts])
135--
136--    Tries to place a building randomly in the given region. It places houses
137--    using :func:`prefilled_buildings`, therefore uses the same options.
138--    If it fails, an error is thrown. This is a most useful function when
139--    defining starting conditions (initializations) in normal games.
140--
141--    :arg plr: The player for which the building is created
142--    :type plr: :class:`wl.game.Player`
143--    :arg building: The name of the building to create.
144--    :type building: :class:`string`
145--    :arg region: The fields which are tested for suitability.
146--    :type region: :class:`array`
147--    :arg opts:  A table with prefill information (wares, soldiers, workers,
148--       see :func:`prefilled_buildings`)
149--    :type opts: :class:`table`
150--
151--    :returns: The building created
152
153function place_building_in_region(plr, building, fields, gargs)
154   set_textdomain("widelands")
155   local idx
156   local f
157   local args = gargs or {}
158
159   while #fields > 0 do
160      local idx = math.random(#fields)
161      local f = fields[idx]
162
163      if plr:get_suitability(building, f) then
164         args[1] = building
165         args[2] = f.x
166         args[3] = f.y
167         return prefilled_buildings(plr, args)[1]
168      end
169      table.remove(fields, idx)
170   end
171   plr:send_message(
172      -- TRANSLATORS: Short for "Not enough space"
173      _"No Space",
174      p(_([[Some of your starting buildings didn’t have enough room and weren’t built. You are at a disadvantage with this; consider restarting this map with a fair starting condition.]])),
175      {popup=true, heading=_"Not enough space"}
176   )
177end
178
179
180-- RST
181-- .. function:: is_building(immovable)
182--
183--    Checks whether an immovable is a finished building, i.e. not
184--    a construction site.
185--
186--    :arg immovable: The immovable to test
187--
188--    :returns: true if the immovable is a building
189
190function is_building(immovable)
191   return immovable.descr.type_name == "productionsite" or
192      immovable.descr.type_name == "warehouse" or
193      immovable.descr.type_name == "militarysite" or
194      immovable.descr.type_name == "trainingsite"
195end
196
197-- RST
198-- .. function:: add_wares_to_warehouse(player, warehouse, waretable)
199--
200--    Adds(subtracts) wares to the first warehouse of specified type
201--
202--    :arg player: the player to add wares to
203--    :arg warehouse: The type of warehouse (string) to add wares to
204--    :arg waretable: a table of pairs {ware = value}
205
206function add_wares_to_warehouse(player, warehouse, waretable)
207   local hq = player:get_buildings(warehouse)[1]
208   for ware,warecount in pairs(waretable) do
209      local oldwarecount = hq:get_wares(ware) or 0
210      if warecount < 0 and -warecount > oldwarecount then
211         warecount = -oldwarecount
212      end
213      hq:set_wares(ware, oldwarecount+warecount)
214   end
215end
216
217-- RST
218-- .. function:: check_trees_rocks_poor_hamlet(player, sf, warehouse, waretable_rocks, waretable_trees)
219--
220--    Checks for rocks or trees in the region of the player starting field and adds wares if no immovables found
221--
222--    :arg player: the player to check
223--    :arg sf: starting field of the player
224--    :arg warehouse: The type of warehouse (string) to add wares to
225--    :arg waretable_rocks: a table of pairs {ware = value} to add if no rocks
226--    :arg waretable_trees: a table of pairs {ware = value} to add if no trees
227
228function check_trees_rocks_poor_hamlet(player, sf, warehouse, waretable_rocks, waretable_trees)
229   -- NOTE: pessimistically, this could be a single rock and a single tree
230   local has_rocks = false
231   local has_trees = false
232   for k,f in pairs(sf:region(10)) do
233      if f.immovable then
234         if not has_rocks and f.immovable:has_attribute('rocks') then
235            has_rocks = true
236         elseif not has_trees and f.immovable:has_attribute('tree') then
237            has_trees = true
238         end
239         if has_trees and has_rocks then
240            break
241         end
242      end
243   end
244   if not has_rocks then
245      add_wares_to_warehouse(player, warehouse, waretable_rocks)
246      player:send_message(_"No rocks nearby", _"There are no rocks near to your starting position. Therefore, you receive extra resources for bootstrapping your economy.")
247   end
248   -- adding exactly one forester
249   if not has_trees then
250      add_wares_to_warehouse(player, warehouse, waretable_trees)
251      player:send_message(_"No trees nearby", _"There are no trees near to your starting position. Therefore, you receive extra resources for bootstrapping your economy.")
252   end
253end
254