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