1--[[ Copyright (c) 2009 Peter "Corsix" Cawley
2
3Permission is hereby granted, free of charge, to any person obtaining a copy of
4this software and associated documentation files (the "Software"), to deal in
5the Software without restriction, including without limitation the rights to
6use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7of the Software, and to permit persons to whom the Software is furnished to do
8so, subject to the following conditions:
9
10The above copyright notice and this permission notice shall be included in all
11copies or substantial portions of the Software.
12
13THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19SOFTWARE. --]]
20
21--! Lua extensions to the C++ THMap class
22class "Map"
23
24---@type Map
25local Map = _G["Map"]
26
27local pathsep = package.config:sub(1, 1)
28local math_floor, tostring, table_concat
29    = math.floor, tostring, table.concat
30local thMap = require("TH").map
31
32function Map:Map(app)
33  self.width = false
34  self.height = false
35  self.th = thMap()
36  self.app = app
37  self.debug_text = false
38  self.debug_flags = false
39  self.debug_font = false
40  self.debug_tick_timer = 1
41  self:setTemperatureDisplayMethod(app.config.warmth_colors_display_default)
42
43  -- Difficulty of the level (string) "easy", "full", "hard".
44  -- Use map:getDifficulty() to query the value.
45  self.difficulty = nil
46  self.hotfix1 = true
47end
48
49local flag_cache = {}
50
51--! Get the value of the given flag from the tile at x, y in the map.
52--!param x (int) Horizontal position of the tile to query in the map.
53--!param x (int) Vertical position of the tile to query in the map.
54--!param flag (string) Name of the queried flag.
55--!return (?) value of the queried flag.
56function Map:getCellFlag(x, y, flag)
57  return self.th:getCellFlags(math.floor(x), math.floor(y), flag_cache)[flag]
58end
59
60--! Get the ID of the room of the tile at x, y in the map.
61--!param x (int) Horizontal position of the tile to query in the map.
62--!param x (int) Vertical position of the tile to query in the map.
63--!return ID of the room at the queried tile.
64function Map:getRoomId(x, y)
65  return self.th:getCellFlags(math.floor(x), math.floor(y)).roomId
66end
67
68function Map:setPlayerCount(count)
69  self.th:setPlayerCount(count)
70end
71
72function Map:getPlayerCount(count)
73  self.th:getPlayerCount(count)
74end
75
76--! Set the camera tile for the given player on the map
77--!param x (int) Horizontal position of tile to set camera on
78--!param y (int) Vertical position of the tile to set the camera on
79--!param player (int) Player number (1-4)
80function Map:setCameraTile(x, y, player)
81  self.th:setCameraTile(x, y, player)
82end
83
84--! Set the heliport tile for the given player on the map
85--!param x (int) Horizontal position of tile to set heliport on
86--!param y (int) Vertical position of the tile to set the heliport on
87--!param player (int) Player number (1-4)
88function Map:setHeliportTile(x, y, player)
89  self.th:setHeliportTile(x, y, player)
90end
91
92--! Set how to display the room temperature in the hospital map.
93--!param method (int) Way of displaying the temperature. See also THMapTemperatureDisplay enum.
94--! 1=red gradients, 2=blue/green/red colour shifts, 3=yellow/orange/red colour shifts
95function Map:setTemperatureDisplayMethod(method)
96  if method ~= 1 and method ~= 2 and method ~= 3 then
97    method = 1
98  end
99  self.temperature_display_method = method
100  self.app.config.warmth_colors_display_default = method
101  self.th:setTemperatureDisplay(method)
102end
103
104--! Copy the temperature display method from the Lua data, if available, else use the default.
105function Map:registerTemperatureDisplayMethod()
106  if not self.temperature_display_method then
107    self:setTemperatureDisplayMethod(self.app.config.warmth_colors_display_default)
108  end
109  self.th:setTemperatureDisplay(self.temperature_display_method)
110end
111
112-- Convert between world coordinates and screen coordinates
113-- World coordinates are (at least for standard maps) in the range [1, 128)
114-- for both x and y, with the floor of the values giving the cell index.
115-- Screen coordinates are pixels relative to the map origin - NOT relative to
116-- the top-left corner of the screen (use UI:WorldToScreen and UI:ScreenToWorld
117-- for this).
118
119function Map:WorldToScreen(x, y)
120  if x == nil then x = 0 end
121  if y == nil then y = 0 end
122
123  -- Adjust origin from (1, 1) to (0, 0) and then linear transform by matrix:
124  -- 32 -32
125  -- 16  16
126  return 32 * (x - y), 16 * (x + y - 2)
127end
128
129function Map:ScreenToWorld(x, y)
130  -- Transform by matrix: (inverse of the WorldToScreen matrix)
131  --  1/64 1/32
132  -- -1/64 1/32
133  -- And then adjust origin from (0, 0) to (1, 1)
134  y = y / 32 + 1
135  x = x / 64
136  local tile_x, tile_y = y + x, y - x
137  if self.width ~= nil and self.height ~= nil then
138    if tile_x < 1 then tile_x = 1 end
139    if tile_x > self.width then tile_x = self.width end
140    if tile_y < 1 then tile_y = 1 end
141    if tile_y > self.height then tile_y = self.height end
142  end
143  return tile_x, tile_y
144end
145
146local function bits(n)
147  local vals = {}
148  local m = 256
149  while m >= 1 do
150    if n >= m then
151      vals[#vals + 1] = m
152      n = n - m
153    end
154    m = m / 2
155  end
156  if vals[1] then
157    return unpack(vals)
158  else
159    return 0
160  end
161end
162
163--[[! Loads the specified level. If a string is passed it looks for the file with the same name
164 in the "Levels" and/or "Campaigns" folder of CorsixTH, if it is a number it tries to load
165 that level from the original game.
166!param level (string or int) The name (or number) of the level to load. If this is a number the game assumes
167the original game levels are considered.
168!param level_name (string) The name of the actual map/area/hospital as written in the config file.
169!param map_file (string) The path to the map file as supplied by the config file.
170!param level_intro (string) If loading a custom level this message will be shown as soon as the level
171has been loaded.
172]]
173function Map:load(level, difficulty, level_name, map_file, level_intro, map_editor)
174  local objects
175  if not difficulty then
176    difficulty = "full"
177  end
178  -- Load CorsixTH base configuration for all levels.
179  -- We want to load the file again each time.
180  local function file (filename)
181      local f = assert(loadfile(filename))
182      return f()
183    end
184  local path = debug.getinfo(1, "S").source:sub(2, -12)
185  local result = file(path .. "Lua" .. pathsep .. "base_config.lua")
186
187  local base_config = result
188  local _
189  if type(level) == "number" then
190    local errors, data
191    -- Playing the original campaign.
192    -- Add TH's base config if possible, otherwise our own config
193    -- roughly corresponds to "full".
194    errors, base_config = self:loadMapConfig(difficulty .. "00.SAM", base_config)
195    -- If it couldn't be loaded the new difficulty level is full no matter what.
196    if errors then
197      difficulty = "full"
198    end
199    self.difficulty = difficulty
200    self.level_number = level
201    data, errors = self:getRawData(map_file)
202    if data then
203      _, objects = self.th:load(data)
204    else
205      return nil, errors
206    end
207    self.level_name = _S.level_names[level]:upper()
208    -- Check if we're using the demo files. If we are, that special config should be loaded.
209    if self.app.using_demo_files then
210      -- Try to load our own configuration file for the demo.
211      local p = debug.getinfo(1, "S").source:sub(2, -12) .. "Levels" .. pathsep .. "demo.level"
212      errors, result = self:loadMapConfig(p, base_config, true)
213      if errors then
214        print("Warning: Could not find the demo configuration, try reinstalling the game")
215      end
216      self.level_config = result
217    else
218      local level_no = level
219      if level_no < 10 then
220        level_no = "0" .. level
221      end
222      -- Override with the specific configuration for this level
223      _, result = self:loadMapConfig(difficulty .. level_no .. ".SAM", base_config)
224      -- Finally load additional CorsixTH config per level
225      local p = debug.getinfo(1, "S").source:sub(2, -12) .. "Levels" .. pathsep .. "original" .. level_no .. ".level"
226      _, result = self:loadMapConfig(p, result, true)
227      self.level_config = result
228    end
229  elseif map_editor then
230    -- We're being fed data by the map editor.
231    self.level_name = "MAP EDITOR"
232    self.level_number = "MAP EDITOR"
233    if level == "" then
234      _, objects = self.th:loadBlank()
235    else
236      local data, errors = self:getRawData(level)
237      if data then
238        _, objects = self.th:load(data)
239      else
240        return nil, errors
241      end
242    end
243    assert(base_config, "No base config has been loaded!")
244
245    self.level_config = base_config
246  else
247    -- We're loading a custom level.
248    self.level_name = level_name
249    self.level_intro = level_intro
250    self.level_number = level
251    self.map_file = map_file
252    local data, errors = self:getRawData(map_file)
253    if data then
254      _, objects = self.th:load(data)
255    else
256      return nil, errors
257    end
258    assert(base_config, "No base config has been loaded!")
259    errors, result = self:loadMapConfig(self.app:getAbsolutePathToLevelFile(level), base_config, true)
260    if errors then
261      print(errors)
262    end
263    self.level_config = result
264  end
265
266  self.width, self.height = self.th:size()
267
268  self.parcelTileCounts = {}
269  for plot = 1, self.th:getPlotCount() do
270    self.parcelTileCounts[plot] = self.th:getParcelTileCount(plot)
271    if not map_editor then
272      self:setPlotOwner(plot, plot <= self.th:getPlayerCount() and plot or 0)
273    end
274  end
275
276  -- fix original level 6 map
277  if level == 6 and not map_file and not map_editor then
278    self:setCellFlags(56, 71, {hospital = true, buildable = true, buildableNorth = true, buildableSouth = true, buildableEast = true, buildableWest = true})
279    self:setCellFlags(58, 72, {passable = false})
280  end
281
282  return objects
283end
284
285--! Get the difficulty of the level. Custom levels and campaign always have medium difficulty.
286--!return (int) difficulty of the level, 1=easy, 2=medium, 3=hard.
287function Map:getDifficulty()
288  if self.difficulty == "easy" then return 1 end
289  if self.difficulty == "hard" then return 3 end
290  return 2
291end
292
293--[[! Sets the plot owner of the given plot number to the given new owner. Makes sure
294      that any room adjacent to the new plot have walls in all directions after the purchase.
295!param plot_number (int) Number of the plot to change owner of. Plot 0 is the outside and
296       should never change owner.
297!param new_owner (int) The player number that should own plot_number. 0 means no owner.
298]]
299function Map:setPlotOwner(plot_number, new_owner)
300  local split_tiles = self.th:setPlotOwner(plot_number, new_owner)
301  for _, coordinates in ipairs(split_tiles) do
302    local x = coordinates[1]
303    local y = coordinates[2]
304
305    local _, north_wall, west_wall = self.th:getCell(x, y)
306    local cell_flags = self.th:getCellFlags(x, y)
307    local north_cell_flags = self.th:getCellFlags(x, y - 1)
308    local west_cell_flags = self.th:getCellFlags(x - 1, y)
309    local wall_dirs = {
310      south = { layer = north_wall,
311                block_id = 2,
312                room_cell_flags = cell_flags,
313                adj_cell_flags = north_cell_flags,
314                tile_cat = 'inside_tiles',
315                wall_dir = 'north'
316              },
317      north = { layer = north_wall,
318                block_id = 2,
319                room_cell_flags = north_cell_flags,
320                adj_cell_flags = cell_flags,
321                tile_cat = 'outside_tiles',
322                wall_dir = 'north'
323              },
324      east = { layer = west_wall,
325               block_id = 3,
326               room_cell_flags = cell_flags,
327               adj_cell_flags = west_cell_flags,
328               tile_cat = 'inside_tiles',
329               wall_dir = 'west'
330             },
331      west = { layer = west_wall,
332               block_id = 3,
333               room_cell_flags = west_cell_flags,
334               adj_cell_flags = cell_flags,
335               tile_cat = 'outside_tiles',
336               wall_dir = 'west'
337             },
338    }
339    for _, dir in pairs(wall_dirs) do
340      if dir.layer == 0 and dir.room_cell_flags.roomId ~= 0 and
341          dir.room_cell_flags.parcelId ~= dir.adj_cell_flags.parcelId then
342        local room = self.app.world.rooms[dir.room_cell_flags.roomId]
343        local wall_type = self.app.walls[room.room_info.wall_type][dir.tile_cat][dir.wall_dir]
344        self.th:setCell(x, y, dir.block_id, wall_type)
345      end
346    end
347  end
348  self.th:updatePathfinding()
349end
350
351--[[! Saves the map to a .map file
352!param filename (string) Name of the file to save the map in
353]]
354function Map:save(filename)
355  self.th:save(filename)
356end
357
358--[[! Loads map configurations from files. Returns nil as first result
359if no configuration could be loaded and config as second result no matter what.
360!param filename (string) The absolute path to the config file to load
361!param config (string) If a base config already exists and only some values should be overridden
362this is the base config
363!param custom If true The configuration file is searched for where filename points, otherwise
364it is assumed that we're looking in the theme_hospital_install path.
365]]
366function Map:loadMapConfig(filename, config, custom)
367  local function iterator()
368    if custom then
369      return io.lines(filename)
370    else
371      return self.app.fs:readContents("Levels", filename):gmatch"[^\r\n]+"
372    end
373  end
374  if self.app.fs:readContents("Levels", filename) or io.open(filename) then
375    for line in iterator() do
376      if line:sub(1, 1) == "#" then
377        local parts = {}
378        local nkeys = 0
379        for part in line:gmatch"%.?[-?a-zA-Z0-9%[_%]]+" do
380          if part:sub(1, 1) == "." and #parts == nkeys + 1 then
381            nkeys = nkeys + 1
382          end
383          parts[#parts + 1] = part
384        end
385        if nkeys == 0 then
386          parts[3] = parts[2]
387          parts[2] = ".Value"
388          nkeys = 1
389        end
390        for i = 2, nkeys + 1 do
391          local key = parts[1] .. parts[i]
392          local t, n
393          for name in key:gmatch"[^.%[%]]+" do
394            name = tonumber(name) or name
395            if t then
396              if not t[n] then
397                t[n] = {}
398              end
399              t = t[n]
400            else
401              t = config
402            end
403            n = name
404          end
405          t[n] = tonumber(parts[nkeys + i]) or parts[nkeys + i]
406        end
407      end
408    end
409    return nil, config
410  else
411    return "Error: Could not find the configuration file, only 'Base Config' will be loaded for this level.", config
412  end
413end
414
415local temp_debug_text
416local temp_debug_flags
417local temp_updateDebugOverlay
418local temp_thData
419
420-- Keep debug information in temporary local vars, do not save them
421function Map:prepareForSave()
422  temp_debug_text = self.debug_text
423  self.debug_text = false
424  temp_debug_flags = self.debug_flags
425  self.debug_flags = false
426  temp_updateDebugOverlay = self.updateDebugOverlay
427  self.updateDebugOverlay = nil
428  temp_thData = self.thData
429  self.thData = nil
430end
431
432-- Restore the temporarily stored debug information after saving
433function Map:afterSave()
434  self.debug_text = temp_debug_text
435  temp_debug_text = nil
436  self.debug_flags = temp_debug_flags
437  temp_debug_flags = nil
438  self.updateDebugOverlay = temp_updateDebugOverlay
439  temp_updateDebugOverlay = nil
440  self.thData = temp_thData
441  temp_thData = nil
442end
443
444function Map:clearDebugText()
445  self.debug_text = false
446  self.debug_flags = false
447  self.updateDebugOverlay = nil
448end
449
450function Map:getRawData(map_file)
451  if not self.thData then
452    local data, errors
453    if not map_file then
454      data, errors = self.app:readDataFile("Levels", "Level.L".. self.level_number)
455    else
456      data, errors = self.app:readDataFile("Levels", map_file)
457    end
458    if data then
459      self.thData = data
460    else
461      return nil, errors
462    end
463  end
464  return self.thData
465end
466
467function Map:updateDebugOverlayFlags()
468  for x = 1, self.width do
469    for y = 1, self.height do
470      local xy = (y - 1) * self.width + x - 1
471      self.th:getCellFlags(x, y, self.debug_flags[xy])
472    end
473  end
474end
475
476function Map:updateDebugOverlayHeat()
477  for x = 1, self.width do
478    for y = 1, self.height do
479      local xy = (y - 1) * self.width + x - 1
480      self.debug_text[xy] = ("%02.1f"):format(self.th:getCellTemperature(x, y) * 50)
481    end
482  end
483end
484
485function Map:updateDebugOverlayParcels()
486  for x = 1, self.width do
487    for y = 1, self.height do
488      local xy = (y - 1) * self.width + x - 1
489      self.debug_text[xy] = self.th:getCellFlags(x, y).parcelId
490      if self.debug_text[xy] == 0 then
491        self.debug_text[xy] = ''
492      end
493    end
494  end
495end
496
497function Map:updateDebugOverlayCamera()
498  for x = 1, self.width do
499    for y = 1, self.height do
500      local xy = (y - 1) * self.width + x - 1
501      self.debug_text[xy] = ''
502    end
503  end
504  for p = 1, self.th:getPlayerCount() do
505    local x, y = self.th:getCameraTile(p)
506    if x and y then
507      local xy = (y - 1) * self.width + x - 1
508      self.debug_text[xy] = 'C'..p
509    end
510  end
511end
512
513function Map:updateDebugOverlayHeliport()
514  for x = 1, self.width do
515    for y = 1, self.height do
516      local xy = (y - 1) * self.width + x - 1
517      self.debug_text[xy] = ''
518    end
519  end
520  for p = 1, self.th:getPlayerCount() do
521    local x, y = self.th:getHeliportTile(p)
522    if x and y then
523      local xy = (y - 1) * self.width + x - 1
524      self.debug_text[xy] = 'H'..p
525    end
526  end
527end
528
529function Map:loadDebugText(base_offset, xy_offset, first, last, bits_)
530  self.debug_text = false
531  self.debug_flags = false
532  self.updateDebugOverlay = nil
533  if base_offset == "flags" then
534    self.debug_flags = {}
535    for x = 1, self.width do
536      for y = 1, self.height do
537        local xy = (y - 1) * self.width + x - 1
538        self.debug_flags[xy] = {}
539      end
540    end
541    self.updateDebugOverlay = self.updateDebugOverlayFlags
542    self:updateDebugOverlay()
543  elseif base_offset == "positions" then
544    self.debug_text = {}
545    for x = 1, self.width do
546      for y = 1, self.height do
547        local xy = (y - 1) * self.width + x - 1
548        self.debug_text[xy] = x .. "," .. y
549      end
550    end
551  elseif base_offset == "heat" then
552    self.debug_text = {}
553    self.updateDebugOverlay = self.updateDebugOverlayHeat
554    self:updateDebugOverlay()
555  elseif base_offset == "parcel" then
556    self.debug_text = {}
557    self.updateDebugOverlay = self.updateDebugOverlayParcels
558    self:updateDebugOverlay()
559  elseif base_offset == "camera" then
560    self.debug_text = {}
561    self.updateDebugOverlay = self.updateDebugOverlayCamera
562    self:updateDebugOverlay()
563  elseif base_offset == "heliport" then
564    self.debug_text = {}
565    self.updateDebugOverlay = self.updateDebugOverlayHeliport
566    self:updateDebugOverlay()
567  else
568    local thData = self:getRawData()
569    for x = 1, self.width do
570      for y = 1, self.height do
571        local xy = (y - 1) * self.width + x - 1
572        local offset = base_offset + xy * xy_offset
573        if bits_ then
574          self:setDebugText(x, y, bits(thData:byte(offset + first, offset + last)))
575        else
576          self:setDebugText(x, y, thData:byte(offset + first, offset + last))
577        end
578      end
579    end
580  end
581end
582
583function Map:onTick()
584  if self.debug_tick_timer == 1 then
585    if self.updateDebugOverlay then
586      self:updateDebugOverlay()
587    end
588    self.debug_tick_timer = 10
589  else
590    self.debug_tick_timer = self.debug_tick_timer - 1
591  end
592end
593
594--! Set the sprites to be used by the map.
595--!param blocks (object) Sprite sheet for the map.
596function Map:setBlocks(blocks)
597  self.blocks = blocks
598  self.th:setSheet(blocks)
599end
600
601function Map:setCellFlags(...)
602  self.th:setCellFlags(...)
603end
604
605function Map:setDebugFont(font)
606  self.debug_font = font
607  self.cell_outline = TheApp.gfx:loadSpriteTable("Bitmap", "aux_ui", true)
608end
609
610function Map:setDebugText(x, y, msg, ...)
611  if not self.debug_text then
612    self.debug_text = {}
613  end
614  local text
615  if ... then
616    text = {msg, ...}
617    for i, v in ipairs(text) do
618      text[i] = tostring(v)
619    end
620    text = table_concat(text, ",")
621  else
622    text = msg ~= 0 and msg or nil
623  end
624  self.debug_text[(y - 1) * self.width + x - 1] = text
625end
626
627--! Draws the rectangle of the map given by (sx, sy, sw, sh) at position (dx, dy) on the canvas
628--!param canvas
629--!param sx Horizontal start position at the screen.
630--!param sy Vertical start position at the screen.
631--!param sw (int) Width of the screen.
632--!param sh (int) Height of the screen.
633--!param dx (jnt) Horizontal destination at the canvas.
634--!param dy (int) Vertical destination at the canvas.
635--]]
636function Map:draw(canvas, sx, sy, sw, sh, dx, dy)
637  -- All the heavy work is done by C code:
638  self.th:draw(canvas, sx, sy, sw, sh, dx, dy)
639
640  -- Draw any debug overlays
641  if self.debug_font and (self.debug_text or self.debug_flags) then
642    local startX = 0
643    local startY = math_floor((sy - 32) / 16)
644    if startY < 0 then
645      startY = 0
646    elseif startY >= self.height then
647      startX = startY - self.height + 1
648      startY = self.height - 1
649      if startX >= self.width then
650        startX = self.width - 1
651      end
652    end
653    local baseX = startX
654    local baseY = startY
655    while true do
656      local x = baseX
657      local y = baseY
658      local screenX = 32 * (x - y) - sx
659      local screenY = 16 * (x + y) - sy
660      if screenY >= sh + 70 then
661        break
662      elseif screenY > -32 then
663        repeat
664          if screenX < -32 then
665          elseif screenX < sw + 32 then
666            local xy = y * self.width + x
667            local xpos = dx + screenX - 32
668            local ypos = dy + screenY
669            if self.debug_flags then
670              local flags = self.debug_flags[xy]
671              if flags.passable then
672                self.cell_outline:draw(canvas, 3, xpos, ypos)
673              end
674              if flags.hospital then
675                self.cell_outline:draw(canvas, 8, xpos, ypos)
676              end
677              if flags.buildable then
678                self.cell_outline:draw(canvas, 9, xpos, ypos)
679              end
680              if flags.travelNorth and self.debug_flags[xy - self.width].passable then
681                self.cell_outline:draw(canvas, 4, xpos, ypos)
682              end
683              if flags.travelEast and self.debug_flags[xy + 1].passable then
684                self.cell_outline:draw(canvas, 5, xpos, ypos)
685              end
686              if flags.travelSouth and self.debug_flags[xy + self.width].passable then
687                self.cell_outline:draw(canvas, 6, xpos, ypos)
688              end
689              if flags.travelWest and self.debug_flags[xy - 1].passable then
690                self.cell_outline:draw(canvas, 7, xpos, ypos)
691              end
692              if flags.thob ~= 0 then
693                self.debug_font:draw(canvas, "T"..flags.thob, xpos, ypos, 64, 16)
694              end
695              if flags.roomId ~= 0 then
696                self.debug_font:draw(canvas, "R"..flags.roomId, xpos, ypos + 16, 64, 16)
697              end
698            else
699              local msg = self.debug_text[xy]
700              if msg and msg ~= "" then
701                self.cell_outline:draw(canvas, 2, xpos, ypos)
702                self.debug_font:draw(canvas, msg, xpos, ypos, 64, 32)
703              end
704            end
705          else
706            break
707          end
708          x = x + 1
709          y = y - 1
710          screenX = screenX + 64
711        until y < 0 or x >= self.width
712      end
713      if baseY == self.height - 1 then
714        baseX = baseX + 1
715        if baseX == self.width then
716          break
717        end
718      else
719        baseY = baseY + 1
720      end
721    end
722  end
723end
724
725--! Get the price of a parcel
726--!param parcel (int) Parcel number being queried.
727--!return Price of the queried parcel.
728function Map:getParcelPrice(parcel)
729  local conf = self.level_config
730  conf = conf and conf.gbv
731  conf = conf and conf.LandCostPerTile
732  return self:getParcelTileCount(parcel) * (conf or 25)
733end
734
735--! Get the number of tiles in a parcel.
736--!param parcel (int) Parcel number being queried.
737--!return Number of tiles in the queried parcel.
738function Map:getParcelTileCount(parcel)
739  return self.parcelTileCounts[parcel] or 0
740end
741
742--! Apply a hotfix to this save..
743--! Note this is only temporary as a backport for 0.65
744--!param num (int) the hotfix number
745--!param issue (num) the issue number
746function Map:applyHotfix(num, issue)
747  assert((num and issue), "Hotfix parameters given are undefined!")
748  if num == 1 then
749    self.level_config.awards_trophies.TrophyAllCuredBonus = 20000
750    self.level_config.awards_trophies.AllCuresBonus = 5000
751    self.hotfix1 = true
752    local string = "Game successfully patched with hotfix " .. num .. " (Issue #" .. issue .. ")"
753    self.app.world:gameLog(string)
754  else
755    local string = "Hotfix " .. num .. " not found. The game may result in unexpected behavior."
756    self.app.world:gameLog(string)
757  end
758end
759
760function Map:afterLoad(old, new)
761  if old < 6 then
762    self.parcelTileCounts = {}
763    for plot = 1,self.th:getPlotCount() do
764      self.parcelTileCounts[plot] = self.th:getParcelTileCount(plot)
765    end
766  end
767  if old < 18 then
768    self.difficulty = "full"
769  end
770  if old < 44 then
771    self.level_config.expertise[2].MaxDiagDiff = 700
772    self.level_config.expertise[3].MaxDiagDiff = 250
773    self.level_config.expertise[4].MaxDiagDiff = 250
774    self.level_config.expertise[5].MaxDiagDiff = 250
775    self.level_config.expertise[6].MaxDiagDiff = 250
776    self.level_config.expertise[7].MaxDiagDiff = 250
777    self.level_config.expertise[8].MaxDiagDiff = 350
778    self.level_config.expertise[9].MaxDiagDiff = 250
779    self.level_config.expertise[10].MaxDiagDiff = 250
780    self.level_config.expertise[11].MaxDiagDiff = 700
781    self.level_config.expertise[12].MaxDiagDiff = 1000
782    self.level_config.expertise[13].MaxDiagDiff = 700
783    self.level_config.expertise[14].MaxDiagDiff = 400
784    self.level_config.expertise[15].MaxDiagDiff = 350
785    self.level_config.expertise[16].MaxDiagDiff = 350
786    self.level_config.expertise[17].MaxDiagDiff = 1000
787    self.level_config.expertise[18].MaxDiagDiff = 350
788    self.level_config.expertise[19].MaxDiagDiff = 700
789    self.level_config.expertise[20].MaxDiagDiff = 700
790    self.level_config.expertise[21].MaxDiagDiff = 700
791    self.level_config.expertise[22].MaxDiagDiff = 350
792    self.level_config.expertise[23].MaxDiagDiff = 350
793    self.level_config.expertise[24].MaxDiagDiff = 700
794    self.level_config.expertise[25].MaxDiagDiff = 700
795    self.level_config.expertise[26].MaxDiagDiff = 700
796    self.level_config.expertise[27].MaxDiagDiff = 350
797    self.level_config.expertise[28].MaxDiagDiff = 700
798    self.level_config.expertise[29].MaxDiagDiff = 1000
799    self.level_config.expertise[30].MaxDiagDiff = 700
800    self.level_config.expertise[31].MaxDiagDiff = 1000
801    self.level_config.expertise[32].MaxDiagDiff = 700
802    self.level_config.expertise[33].MaxDiagDiff = 1000
803    self.level_config.expertise[34].MaxDiagDiff = 700
804    self.level_config.expertise[35].MaxDiagDiff = 700
805  end
806  if old < 57 then
807    local flags_to_set = {buildableNorth = true, buildableSouth = true, buildableWest = true, buildableEast = true}
808    for x = 1, self.width do
809      for y = 1, self.height do
810        self:setCellFlags(x, y, flags_to_set)
811      end
812    end
813  end
814  if old < 120 then
815    -- Issue #1105 update pathfinding (rebuild walls) potentially broken by side object placement
816    self.th:updatePathfinding()
817  end
818  if old < 136 then
819    if self.level_number == 6 then
820      self:setCellFlags(56, 71, {hospital = true, buildable = true, buildableNorth = true, buildableSouth = true, buildableEast = true, buildableWest = true})
821      self:setCellFlags(58, 72, {passable = false})
822    end
823  end
824  -- 0.65 has a hotfix, make sure we patch any affected games
825  if not self.hotfix1 then
826    self:applyHotfix(1, 2004)
827  end
828end
829