mirror of
https://github.com/CorsixTH/CorsixTH.git
synced 2025-07-23 04:13:01 +02:00
Merge branch 'map_editor2'
Conflicts: CorsixTH/Lua/dialogs/resizables/main_menu.lua CorsixTH/Lua/map.lua CorsixTH/Src/th_lua_map.cpp CorsixTH/Src/th_map.h
This commit is contained in:
@@ -110,8 +110,6 @@ else
|
||||
_DECODA = false
|
||||
end
|
||||
|
||||
_MAP_EDITOR = _MAP_EDITOR or false
|
||||
|
||||
-- Enable strict mode
|
||||
dofile "strict"
|
||||
require = destrict(require)
|
||||
|
@@ -114,6 +114,7 @@ function App:init()
|
||||
local good_install_folder, error_message = self:checkInstallFolder()
|
||||
self.good_install_folder = good_install_folder
|
||||
-- self:checkLanguageFile()
|
||||
self.level_dir = debug.getinfo(1, "S").source:sub(2, -12) .. "Levels" .. pathsep
|
||||
|
||||
self:initSavegameDir()
|
||||
|
||||
@@ -141,12 +142,7 @@ function App:init()
|
||||
modes[#modes + 1] = "opengl"
|
||||
end
|
||||
self.fullscreen = false
|
||||
if _MAP_EDITOR then
|
||||
MapEditorInitWithLuaApp(self)
|
||||
modes[#modes + 1] = "reuse context"
|
||||
self.config.width = 640
|
||||
self.config.height = 480
|
||||
elseif self.config.fullscreen then
|
||||
if self.config.fullscreen then
|
||||
self.fullscreen = true
|
||||
modes[#modes + 1] = "fullscreen"
|
||||
end
|
||||
@@ -292,41 +288,37 @@ function App:init()
|
||||
end
|
||||
|
||||
-- Load main menu (which creates UI)
|
||||
if _MAP_EDITOR then
|
||||
self:loadLevel("")
|
||||
else
|
||||
local function callback_after_movie()
|
||||
self:loadMainMenu()
|
||||
-- If we couldn't properly load the language, show an information dialog
|
||||
if not language_load_success then
|
||||
-- At this point we know the language is english, so no use having
|
||||
-- localized strings.
|
||||
self.ui:addWindow(UIInformation(self.ui, {"The game language has been reverted"..
|
||||
" to English because the desired language could not be loaded. "..
|
||||
"Please make sure you have specified a font file in the config file."}))
|
||||
end
|
||||
local function callback_after_movie()
|
||||
self:loadMainMenu()
|
||||
-- If we couldn't properly load the language, show an information dialog
|
||||
if not language_load_success then
|
||||
-- At this point we know the language is english, so no use having
|
||||
-- localized strings.
|
||||
self.ui:addWindow(UIInformation(self.ui, {"The game language has been reverted"..
|
||||
" to English because the desired language could not be loaded. "..
|
||||
"Please make sure you have specified a font file in the config file."}))
|
||||
end
|
||||
|
||||
-- If a savegame was specified, load it
|
||||
if self.command_line.load then
|
||||
local status, err = pcall(self.load, self, self.savegame_dir .. self.command_line.load)
|
||||
if not status then
|
||||
err = _S.errors.load_prefix .. err
|
||||
print(err)
|
||||
self.ui:addWindow(UIInformation(self.ui, {err}))
|
||||
end
|
||||
end
|
||||
-- There might also be a message from the earlier initialization process that should be shown.
|
||||
-- Show it using the built-in font in case the game's font is messed up.
|
||||
if error_message then
|
||||
self.ui:addWindow(UIInformation(self.ui, error_message, true))
|
||||
-- If a savegame was specified, load it
|
||||
if self.command_line.load then
|
||||
local status, err = pcall(self.load, self, self.savegame_dir .. self.command_line.load)
|
||||
if not status then
|
||||
err = _S.errors.load_prefix .. err
|
||||
print(err)
|
||||
self.ui:addWindow(UIInformation(self.ui, {err}))
|
||||
end
|
||||
end
|
||||
if self.config.play_intro then
|
||||
self.moviePlayer:playIntro(callback_after_movie)
|
||||
else
|
||||
callback_after_movie()
|
||||
-- There might also be a message from the earlier initialization process that should be shown.
|
||||
-- Show it using the built-in font in case the game's font is messed up.
|
||||
if error_message then
|
||||
self.ui:addWindow(UIInformation(self.ui, error_message, true))
|
||||
end
|
||||
end
|
||||
if self.config.play_intro then
|
||||
self.moviePlayer:playIntro(callback_after_movie)
|
||||
else
|
||||
callback_after_movie()
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -546,14 +538,14 @@ end
|
||||
-- Loads the specified level. If a string is passed it looks for the file with the same name
|
||||
-- in the "Levels" folder of CorsixTH, if it is a number it tries to load that level from
|
||||
-- the original game.
|
||||
function App:loadLevel(level, ...)
|
||||
function App:loadLevel(level, difficulty, level_name, level_file, level_intro, map_editor)
|
||||
if self.world then
|
||||
self:worldExited()
|
||||
end
|
||||
|
||||
-- Check that we can load the data before unloading current map
|
||||
local new_map = Map(self)
|
||||
local map_objects, errors = new_map:load(level, ...)
|
||||
local map_objects, errors = new_map:load(level, difficulty, level_name, level_file, level_intro, map_editor)
|
||||
if not map_objects then
|
||||
self.world.ui:addWindow(UIInformation(self.ui, {errors}))
|
||||
return
|
||||
@@ -596,7 +588,7 @@ function App:loadLevel(level, ...)
|
||||
self.audio:playSoundEffects(self.config.play_sounds)
|
||||
|
||||
-- Load UI
|
||||
self.ui = GameUI(self, self.world:getLocalPlayerHospital())
|
||||
self.ui = GameUI(self, self.world:getLocalPlayerHospital(), map_editor)
|
||||
self.world:setUI(self.ui) -- Function call allows world to set up its keyHandlers
|
||||
|
||||
if tonumber(level) then
|
||||
@@ -1368,6 +1360,11 @@ function App:restart()
|
||||
end))
|
||||
end
|
||||
|
||||
--! Begin the map editor
|
||||
function App:mapEdit()
|
||||
self:loadLevel("", nil, nil, nil, nil, true);
|
||||
end
|
||||
|
||||
--! Exits the game completely (no confirmation window)
|
||||
function App:exit()
|
||||
-- Save config before exiting
|
||||
|
@@ -66,9 +66,7 @@ function Audio:init()
|
||||
return
|
||||
end
|
||||
if not SDL.audio.loaded then
|
||||
if not _MAP_EDITOR then
|
||||
print "Notice: Audio system not loaded as CorsixTH compiled without it"
|
||||
end
|
||||
print "Notice: Audio system not loaded as CorsixTH compiled without it"
|
||||
self.not_loaded = true
|
||||
return
|
||||
end
|
||||
|
@@ -1,663 +0,0 @@
|
||||
--[[ Copyright (c) 2010 Peter "Corsix" Cawley
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE. --]]
|
||||
|
||||
dofile "command_stack"
|
||||
dofile "command"
|
||||
dofile "commands/set_map_cell"
|
||||
dofile "commands/set_map_cell_flags"
|
||||
dofile "commands/compound"
|
||||
|
||||
class "UIMapEditor" (Window)
|
||||
|
||||
---@type UIMapEditor
|
||||
local UIMapEditor = _G["UIMapEditor"]
|
||||
|
||||
local math_floor
|
||||
= math.floor
|
||||
|
||||
function UIMapEditor:UIMapEditor(ui)
|
||||
-- Put ourselves in an easily findable global for the UI code to find.
|
||||
_MAP_EDITOR = self
|
||||
self:Window()
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.width = math.huge
|
||||
self.height = math.huge
|
||||
self.ui = ui
|
||||
|
||||
self.command_stack = CommandStack()
|
||||
|
||||
|
||||
|
||||
-- For when there are multiple things which could be sampled from a tile,
|
||||
-- keep track of the index of which one was most recently sampled, so that
|
||||
-- next time a different one is sampled.
|
||||
self.sample_i = 1
|
||||
|
||||
-- The block to put on the UI layer as a preview for what will be placed
|
||||
-- by the current drawing operation.
|
||||
self.block_brush_preview = 0
|
||||
|
||||
self:classifyBlocks()
|
||||
|
||||
-- A sprite table containing a "cell outline" sprite
|
||||
self.cell_outline = TheApp.gfx:loadSpriteTable("Bitmap", "aux_ui", true)
|
||||
|
||||
-- Coordinates in Lua tile space of the mouse cursor.
|
||||
self.mouse_cell_x = 0
|
||||
self.mouse_cell_y = 0
|
||||
end
|
||||
|
||||
function UIMapEditor:classifyBlocks()
|
||||
-- Classify each block / tile with a type, subtype, and category.
|
||||
-- Type and subtype are used by this file, and by the UI code to determine
|
||||
-- which gallery to put the block in. Category is used purely by the UI to
|
||||
-- allow subsets of a gallery to be hidden.
|
||||
local block_info = {}
|
||||
for i = 1, 15 do
|
||||
block_info[i] = {"floor", "simple", "Outside"}
|
||||
end
|
||||
for i = 16, 23 do
|
||||
block_info[i] = {"floor", "simple", "Inside"}
|
||||
end
|
||||
for i = 24, 40 do
|
||||
block_info[i] = {"object"}
|
||||
end
|
||||
for i = 41, 58 do
|
||||
block_info[i] = {"floor", "simple", "Road"}
|
||||
end
|
||||
block_info[59] = {"floor", "decorated", "Pond"}
|
||||
block_info[60] = {"floor", "decorated", "Pond"}
|
||||
for i = 61, 64 do
|
||||
block_info[i] = {"floor", "simple", "Outside"}
|
||||
end
|
||||
block_info[65] = {"floor", "decorated", "Pond"}
|
||||
block_info[66] = {"floor", "simple", "Inside"} -- 67 is UI.
|
||||
block_info[68] = {"floor", "decorated", "Pond"}
|
||||
block_info[69] = {"floor", "decorated", "Pond"}
|
||||
block_info[70] = {"floor", "simple", "Inside"}
|
||||
for i = 71, 73 do
|
||||
block_info[i] = {"floor", "decorated", "Pond", base = 69}
|
||||
end
|
||||
block_info[76] = {"floor", "simple", "Inside"}
|
||||
for i = 77, 80 do
|
||||
block_info[i] = {"floor", "decorated", "Pond"}
|
||||
end
|
||||
for i = 114, 164 do -- 82-113 are internal walls
|
||||
local pair
|
||||
local category = "External"
|
||||
local dir = i % 2 == 0 and "north" or "west"
|
||||
if 114 <= i and i <= 127 then
|
||||
if 114 <= i and i <= 119 then
|
||||
pair = i + 8
|
||||
elseif 122 <= i and i < 127 then
|
||||
pair = i - 8
|
||||
end
|
||||
elseif 157 <= i and i <= 164 then
|
||||
category = "Doorway"
|
||||
if 157 <= i and i <= 160 then
|
||||
pair = i + 4
|
||||
elseif 161 <= i and i < 164 then
|
||||
pair = i - 4
|
||||
end
|
||||
dir = dir == "north" and "west" or "north"
|
||||
end
|
||||
for i = 128, 156 do
|
||||
block_info[i] = {"object"}
|
||||
end
|
||||
for i = 120, 121 do -- removes the complete windows as they disappear in the game
|
||||
block_info[i] = {"object"}
|
||||
end
|
||||
if i ~= 144 and i ~= 145 and i ~= 156 then
|
||||
block_info[i] = {"wall", dir, category, pair = pair}
|
||||
end
|
||||
end
|
||||
for i = 176, 191 do
|
||||
block_info[i] = {"floor", "decorated", "Hegderow", base = 2}
|
||||
end
|
||||
for i = 192, 196 do
|
||||
block_info[i] = {"floor", "decorated", "Foliage", base = 2}
|
||||
end
|
||||
for i = 197, 204 do
|
||||
block_info[i] = {"floor", "decorated", "Foliage", base = 2}
|
||||
end
|
||||
for i = 205, 208 do
|
||||
block_info[i] = {"floor", "simple", "Outside"}
|
||||
end
|
||||
block_info[208].base = 3
|
||||
-- adds street lights, could do with mirrors of these to have lamps facing different directions
|
||||
for i = 209, 210 do
|
||||
local pair
|
||||
local category = "External"
|
||||
local dir = i % 2 == 0 and "north" or "west"
|
||||
if i ~= 209 then
|
||||
pair = i - 1
|
||||
end
|
||||
block_info[i] = {"wall", dir, category, pair = pair}
|
||||
end
|
||||
|
||||
MapEditorSetBlocks(self.ui.app.map.blocks, block_info) -- pass data to UI
|
||||
self.block_info = block_info
|
||||
end
|
||||
|
||||
function UIMapEditor:draw(canvas, ...)
|
||||
local ui = self.ui
|
||||
local x, y = ui:WorldToScreen(self.mouse_cell_x, self.mouse_cell_y)
|
||||
self.cell_outline:draw(canvas, 2, x - 32, y)
|
||||
|
||||
Window.draw(self, canvas, ...)
|
||||
end
|
||||
|
||||
|
||||
function UIMapEditor:onMouseMove(x, y, dx, dy)
|
||||
local repaint = Window.onMouseMove(self, x, y, dx, dy)
|
||||
|
||||
local ui = self.ui
|
||||
local wxr, wyr = ui:ScreenToWorld(self.x + x, self.y + y)
|
||||
local wx = math_floor(wxr)
|
||||
local wy = math_floor(wyr)
|
||||
local map = self.ui.app.map
|
||||
|
||||
-- Update the stored state of cursor position, and trigger a repaint as the
|
||||
-- cell outline sprite should track the cursor position.
|
||||
if wx ~= self.mouse_cell_x or wy ~= self.mouse_cell_y then
|
||||
repaint = true
|
||||
self.mouse_cell_x = wx
|
||||
self.mouse_cell_y = wy
|
||||
-- Right button down: sample the block under the cursor (unless left is
|
||||
-- held, as that indicates a paint in progress).
|
||||
if self.buttons_down.mouse_right and not self.buttons_down.mouse_left then
|
||||
self:sampleBlock(x, y)
|
||||
end
|
||||
end
|
||||
|
||||
-- Left button down: Expand / contract the rectangle which will be painted to
|
||||
-- include the original mouse down cell and the current cell, or clear the
|
||||
-- rectangle if the cursor is returned to where the mouse down position.
|
||||
if self.buttons_down.mouse_left and self.paint_rect and 1 <= wx and 1 <= wy
|
||||
and wx <= map.width and wy <= map.height then
|
||||
local p1x = math_floor(self.paint_start_wx)
|
||||
local p1y = math_floor(self.paint_start_wy)
|
||||
local p2x = wx
|
||||
local p2y = wy
|
||||
local x, y, w, h
|
||||
if p1x < p2x then
|
||||
x = p1x
|
||||
w = p2x - p1x + 1
|
||||
else
|
||||
x = p2x
|
||||
w = p1x - p2x + 1
|
||||
end
|
||||
if p1y < p2y then
|
||||
y = p1y
|
||||
h = p2y - p1y + 1
|
||||
else
|
||||
y = p2y
|
||||
h = p1y - p2y + 1
|
||||
end
|
||||
if w*h > 1 then
|
||||
-- The paint rectangle extends beyond the original mouse down cell, so
|
||||
-- make the area in the middle of said cell into a "null area" so that
|
||||
-- the paint operation can be cancelled by returning the cursor to this
|
||||
-- area and ending the drag.
|
||||
self.has_paint_null_area = true
|
||||
elseif w == 1 and h == 1 and self.has_paint_null_area then
|
||||
if ((wxr % 1) - 0.5)^2 + ((wyr % 1) - 0.5)^2 < 0.25^2 then
|
||||
w = 0
|
||||
h = 0
|
||||
end
|
||||
end
|
||||
local rect = self.paint_rect
|
||||
if x ~= rect.x or y ~= rect.y or w ~= rect.w or h ~= rect.h then
|
||||
self:setPaintRect(x, y, w, h)
|
||||
repaint = true
|
||||
end
|
||||
end
|
||||
|
||||
return repaint
|
||||
end
|
||||
|
||||
function UIMapEditor:onMouseDown(button, x, y)
|
||||
local repaint = false
|
||||
local map = self.ui.app.map.th
|
||||
if button == "right" then
|
||||
if self.buttons_down.mouse_left then
|
||||
-- Right click while left is down: set paint step size
|
||||
local wx, wy = self.ui:ScreenToWorld(x, y)
|
||||
local xstep = math.max(1, math.abs(math.floor(wx) - math.floor(self.paint_start_wx)))
|
||||
local ystep = math.max(1, math.abs(math.floor(wy) - math.floor(self.paint_start_wy)))
|
||||
local rect = self.paint_rect
|
||||
self:setPaintRect(rect.x, rect.y, rect.w, rect.h, xstep, ystep)
|
||||
repaint = true
|
||||
else
|
||||
-- Normal right click: sample the block under the cursor
|
||||
self:sampleBlock(x, y)
|
||||
end
|
||||
elseif button == "left" then
|
||||
self.current_command = CompoundCommand()
|
||||
self.current_command_cell = SetMapCellCommand(map)
|
||||
self.current_command_cell_flags = SetMapCellFlagsCommand(map)
|
||||
self:startPaint(x, y)
|
||||
repaint = true
|
||||
elseif button == "left_double" then
|
||||
self.current_command = CompoundCommand()
|
||||
self.current_command_cell = SetMapCellCommand(map)
|
||||
self.current_command_cell_flags = SetMapCellFlagsCommand(map)
|
||||
self:doLargePaint(x, y)
|
||||
-- Set a dummy paint rect for the mouse up event.
|
||||
self:setPaintRect(0, 0, 0, 0)
|
||||
repaint = true
|
||||
end
|
||||
|
||||
return Window.onMouseDown(self, button, x, y) or repaint
|
||||
end
|
||||
|
||||
function UIMapEditor:onMouseUp(button, x, y)
|
||||
local repaint = false
|
||||
if button == "left" and self.paint_rect then
|
||||
self:finishPaint(true)
|
||||
if #self.current_command_cell.paint_list ~=0 then
|
||||
self.current_command:addCommand(self.current_command_cell)
|
||||
end
|
||||
if #self.current_command_cell_flags.paint_list ~= 0 then
|
||||
self.current_command:addCommand(self.current_command_cell_flags)
|
||||
end
|
||||
self.command_stack:add(self.current_command)
|
||||
repaint = true
|
||||
end
|
||||
|
||||
return Window.onMouseUp(self, button, x, y) or repaint
|
||||
end
|
||||
|
||||
function UIMapEditor:startPaint(x, y)
|
||||
-- Save the Lua world coordinates of where the painting started.
|
||||
self.paint_start_wx, self.paint_start_wy = self.ui:ScreenToWorld(x, y)
|
||||
-- Initialise an empty paint rectangle.
|
||||
self.paint_rect = {
|
||||
x = math_floor(self.paint_start_wx),
|
||||
y = math_floor(self.paint_start_wy),
|
||||
w = 0, h = 0,
|
||||
}
|
||||
self.has_paint_null_area = false
|
||||
-- Check that starting point isn't out of bounds
|
||||
local map = self.ui.app.map
|
||||
if self.paint_rect.x < 1 or self.paint_rect.y < 1
|
||||
or self.paint_rect.x > map.width or self.paint_rect.y > map.height then
|
||||
self.paint_rect = nil
|
||||
return
|
||||
end
|
||||
-- Reset the paint step.
|
||||
self.paint_step_x = 1
|
||||
self.paint_step_y = 1
|
||||
-- Extend the paint rectangle to the single cell.
|
||||
self:setPaintRect(self.paint_rect.x, self.paint_rect.y, 1, 1)
|
||||
end
|
||||
|
||||
-- Injective function from NxN to N which allows for a pair of positive
|
||||
-- integers to be combined into a single value suitable for use as a key in a
|
||||
-- table.
|
||||
local function combine_ints(x, y)
|
||||
local sum = x + y
|
||||
return y + sum * (sum + 1) / 2
|
||||
end
|
||||
|
||||
function UIMapEditor:doLargePaint(x, y)
|
||||
-- Perform a "large" paint. At the moment, this is triggered by a double
|
||||
-- left click, and results in a floor tile "flood fill" operation.
|
||||
|
||||
-- Get the Lua tile coordinate of the tile to start filling from.
|
||||
x, y = self.ui:ScreenToWorld(x, y)
|
||||
x = math_floor(x)
|
||||
y = math_floor(y)
|
||||
if x <= 0 or y <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- The click which preceeded the double click should have set "old_floors"
|
||||
-- with the contents of the tile prior to the single click.
|
||||
if not self.old_floors then
|
||||
return
|
||||
end
|
||||
-- brush_f is the tile which is to be painted
|
||||
local brush_f = self.block_brush_f
|
||||
if not brush_f or brush_f == 0 then
|
||||
return
|
||||
end
|
||||
local map = self.ui.app.map.th
|
||||
local key = combine_ints(x, y)
|
||||
-- match_f is the tile which is to be replaced by brush_f
|
||||
local match_f = self.old_floors[key]
|
||||
if not match_f then
|
||||
return
|
||||
end
|
||||
match_f = match_f % 256 -- discard shadow flags, etc.
|
||||
-- If the operation wouldn't change anything, don't do it.
|
||||
if match_f == brush_f then
|
||||
return
|
||||
end
|
||||
|
||||
-- Reset the starting tile as to simplify the upcoming loop.
|
||||
self.current_command_cell:addTile(x, y, 1, match_f)
|
||||
map:setCell(x, y, 1, match_f)
|
||||
|
||||
|
||||
local to_visit = {[key] = {x, y}}
|
||||
local visited = {[key] = true}
|
||||
-- Mark the tiles beyond the edge of the map as visited, as to prevent the
|
||||
-- pathfinding from exceeding the bounds of the map.
|
||||
local size = map:size()
|
||||
for i = 1, size do
|
||||
visited[combine_ints(0, i)] = true
|
||||
visited[combine_ints(i, 0)] = true
|
||||
visited[combine_ints(size + 1, i)] = true
|
||||
visited[combine_ints(i, size + 1)] = true
|
||||
end
|
||||
-- When a tile is added to "visited" to the first time, also add it to
|
||||
-- "to_visit". This ensures each tile is visited no more than once.
|
||||
setmetatable(visited, {__newindex = function(t, k, v)
|
||||
rawset(t, k, v)
|
||||
to_visit[k] = v
|
||||
end})
|
||||
-- Iterate over the tiles to visit, and if they are suitable for replacement,
|
||||
-- do the replacement and add their neighbours to the list of things to do.
|
||||
repeat
|
||||
x = to_visit[key][1]
|
||||
y = to_visit[key][2]
|
||||
to_visit[key] = nil
|
||||
local f = map:getCell(x, y)
|
||||
if f % 256 == match_f then
|
||||
self.current_command_cell:addTile(x, y, 1, f - match_f + brush_f)
|
||||
map:setCell(x, y, 1, f - match_f + brush_f)
|
||||
visited[combine_ints(x, y + 1)] = {x, y + 1}
|
||||
visited[combine_ints(x, y - 1)] = {x, y - 1}
|
||||
visited[combine_ints(x + 1, y)] = {x + 1, y}
|
||||
visited[combine_ints(x - 1, y)] = {x - 1, y}
|
||||
end
|
||||
to_visit[key] = nil
|
||||
key = next(to_visit)
|
||||
until not key
|
||||
end
|
||||
|
||||
-- Remove the paint preview from the UI layer, and optionally apply the paint
|
||||
-- to the actual layers.
|
||||
function UIMapEditor:finishPaint(apply)
|
||||
-- Grab local copies of all the fields which we need
|
||||
local map = self.ui.app.map.th
|
||||
local brush_f = self.block_brush_f
|
||||
local brush_parcel = self.block_brush_parcel
|
||||
if brush_parcel then
|
||||
brush_parcel = {parcelId = brush_parcel}
|
||||
end
|
||||
local brush_w1 = self.block_brush_w1
|
||||
local brush_w1p = (self.block_info[brush_w1] or '').pair
|
||||
local brush_w2 = self.block_brush_w2
|
||||
local brush_w2p = (self.block_info[brush_w2] or '').pair
|
||||
local x_first, x_last = self.paint_rect.x, self.paint_rect.x + self.paint_rect.w - 1
|
||||
local y_first, y_last = self.paint_rect.y, self.paint_rect.y + self.paint_rect.h - 1
|
||||
local step_base_x = math.floor(self.paint_start_wx)
|
||||
local step_base_y = math.floor(self.paint_start_wy)
|
||||
local xstep = self.paint_step_x
|
||||
local ystep = self.paint_step_y
|
||||
|
||||
-- Determine what kind of thing is being painted.
|
||||
local is_wall = self.block_info[self.block_brush_preview]
|
||||
is_wall = is_wall and is_wall[1] == "wall" and is_wall[2]
|
||||
local is_simple_floor = self.block_info[self.block_brush_preview]
|
||||
is_simple_floor = is_simple_floor and is_simple_floor[1] == "floor" and is_simple_floor[2] == "simple"
|
||||
|
||||
-- To allow the double click handler to know what was present before the
|
||||
-- single click which preceeds it, the prior contents of the floor layer is
|
||||
-- saved.
|
||||
local old_floors = {}
|
||||
self.old_floors = old_floors
|
||||
for tx = x_first, x_last do
|
||||
for ty = y_first, y_last do
|
||||
-- Grab and save the contents of the tile
|
||||
local f, w1, w2 = map:getCell(tx, ty)
|
||||
old_floors[combine_ints(tx, ty)] = f
|
||||
local flags
|
||||
-- Change the contents according to what is being painted
|
||||
repeat
|
||||
-- If not painting, do not change anything (apart from UI layer).
|
||||
if not apply then
|
||||
break
|
||||
end
|
||||
-- If the paint is happening at an interval, do not change things
|
||||
-- apart from at the occurances of the interval.
|
||||
if ((tx - step_base_x) % xstep) ~= 0 then
|
||||
break
|
||||
end
|
||||
if ((ty - step_base_y) % ystep) ~= 0 then
|
||||
break
|
||||
end
|
||||
flags = brush_parcel
|
||||
-- If painting walls, only apply to the two edges which get painted.
|
||||
if is_wall == "north" and ty ~= y_first and ty ~= y_last then
|
||||
break
|
||||
end
|
||||
if is_wall == "west" and tx ~= x_first and tx ~= x_last then
|
||||
break
|
||||
end
|
||||
-- If painting a floor component, apply it.
|
||||
if not brush_parcel and brush_f and brush_f ~= 0 then
|
||||
f = f - (f % 256) + brush_f
|
||||
-- If painting just a floor component, then remove any decoration
|
||||
-- and/or walls which get painted over.
|
||||
if is_simple_floor then
|
||||
local w1b = self.block_info[w1 % 256]
|
||||
if not w1b or w1b[1] ~= "wall" or ty ~= y_first then
|
||||
w1 = w1 - (w1 % 256)
|
||||
end
|
||||
local w2b = self.block_info[w2 % 256]
|
||||
if not w2b or w2b[1] ~= "wall" or tx ~= x_first then
|
||||
w2 = w2 - (w2 % 256)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- If painting wall components, apply them.
|
||||
if brush_w1 and brush_w1 ~= 0 then
|
||||
w1 = w1 - (w1 % 256) + (ty ~= step_base_y and brush_w1p or brush_w1)
|
||||
end
|
||||
if brush_w2 and brush_w2 ~= 0 then
|
||||
w2 = w2 - (w2 % 256) + (tx ~= step_base_x and brush_w2p or brush_w2)
|
||||
end
|
||||
until true
|
||||
-- Remove the UI layer and perform the adjustment of the other layers.
|
||||
self.current_command_cell:addTile(tx,ty,f,w1,w2,0)
|
||||
map:setCell(tx, ty, f, w1, w2, 0)
|
||||
if flags then
|
||||
self.current_command_cell_flags:addTile(tx, ty, flags)
|
||||
map:setCellFlags(tx, ty, flags)
|
||||
end
|
||||
end
|
||||
end
|
||||
map:updateShadows()
|
||||
|
||||
end
|
||||
|
||||
-- Move/resize the rectangle to be painted, and update the UI preview layer
|
||||
-- to reflect the new rectangle.
|
||||
function UIMapEditor:setPaintRect(x, y, w, h, xstep, ystep)
|
||||
local map = self.ui.app.map.th
|
||||
local rect = self.paint_rect
|
||||
local old_xstep = self.paint_step_x or 1
|
||||
local old_ystep = self.paint_step_y or 1
|
||||
local step_base_x = math.floor(self.paint_start_wx)
|
||||
local step_base_y = math.floor(self.paint_start_wy)
|
||||
xstep = xstep or old_xstep
|
||||
ystep = ystep or old_ystep
|
||||
|
||||
-- Create a rectangle which contains both the old and new rectangles, as
|
||||
-- this contains all tiles which may need to change.
|
||||
local left, right, top, bottom = x, x + w - 1, y, y + h - 1
|
||||
if rect then
|
||||
if rect.x < left then
|
||||
left = rect.x
|
||||
end
|
||||
if rect.x + rect.w - 1 > right then
|
||||
right = rect.x + rect.w - 1
|
||||
end
|
||||
if rect.y < top then
|
||||
top = rect.y
|
||||
end
|
||||
if rect.y + rect.h - 1 > bottom then
|
||||
bottom = rect.y + rect.h - 1
|
||||
end
|
||||
end
|
||||
|
||||
-- Determine what kind of thing is being painted
|
||||
local is_wall = self.block_info[self.block_brush_preview]
|
||||
local block_brush_preview_pair = is_wall and is_wall.pair
|
||||
is_wall = is_wall and is_wall[1] == "wall" and is_wall[2]
|
||||
|
||||
for tx = left, right do
|
||||
for ty = top, bottom do
|
||||
local now_in, was_in
|
||||
-- Non-walls: paint at every tile within the rectangle
|
||||
if not is_wall then
|
||||
now_in = (x <= tx and tx < x + w and y <= ty and ty < y + h)
|
||||
was_in = (rect.x <= tx and tx < rect.x + rect.w and rect.y <= ty and ty < rect.y + rect.h)
|
||||
-- Walls: paint at two edges of the rectangle
|
||||
elseif is_wall == "north" then
|
||||
now_in = (x <= tx and tx < x + w and (y == ty or ty == y + h - 1))
|
||||
was_in = (rect.x <= tx and tx < rect.x + rect.w and (rect.y == ty or ty == rect.y + rect.h - 1))
|
||||
elseif is_wall == "west" then
|
||||
now_in = ((x == tx or tx == x + w - 1) and y <= ty and ty < y + h)
|
||||
was_in = ((rect.x == tx or tx == rect.x + rect.w - 1) and rect.y <= ty and ty < rect.y + rect.h)
|
||||
end
|
||||
-- Restrict the paint to tiles which fall on the appropriate intervals
|
||||
now_in = now_in and ((tx - step_base_x) % xstep) == 0
|
||||
now_in = now_in and ((ty - step_base_y) % ystep) == 0
|
||||
was_in = was_in and ((tx - step_base_x) % old_xstep) == 0
|
||||
was_in = was_in and ((ty - step_base_y) % old_ystep) == 0
|
||||
-- Update the tile, but only if it needs changing
|
||||
if now_in ~= was_in then
|
||||
local ui_layer = 0
|
||||
if now_in then
|
||||
local brush = self.block_brush_preview
|
||||
if is_wall == "north" and ty ~= step_base_y then
|
||||
brush = block_brush_preview_pair or brush
|
||||
end
|
||||
if is_wall == "west" and tx ~= step_base_x then
|
||||
brush = block_brush_preview_pair or brush
|
||||
end
|
||||
ui_layer = brush + 256 * DrawFlags.Alpha50
|
||||
end
|
||||
self.current_command_cell:addTile(tx, ty, 4, ui_layer)
|
||||
map:setCell(tx, ty, 4, ui_layer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Save the details of the new rectangle
|
||||
if not rect then
|
||||
rect = {}
|
||||
self.paint_rect = rect
|
||||
end
|
||||
rect.x = x
|
||||
rect.y = y
|
||||
rect.w = w
|
||||
rect.h = h
|
||||
self.paint_step_x = xstep
|
||||
self.paint_step_y = ystep
|
||||
end
|
||||
|
||||
function UIMapEditor:sampleBlock(x, y)
|
||||
local ui = self.ui
|
||||
local wx, wy = self.ui:ScreenToWorld(x, y)
|
||||
wx = math_floor(wx)
|
||||
wy = math_floor(wy)
|
||||
local map = self.ui.app.map
|
||||
if wx < 1 or wy < 1 or wx > map.width or wy > map.height then
|
||||
return
|
||||
end
|
||||
local floor, wall1, wall2 = map.th:getCell(wx, wy)
|
||||
local set = {}
|
||||
set[floor % 256] = true
|
||||
set[wall1 % 256] = true
|
||||
set[wall2 % 256] = true
|
||||
if wx < map.width then
|
||||
floor, wall1, wall2 = map.th:getCell(wx + 1, wy)
|
||||
set[wall2 % 256] = true
|
||||
end
|
||||
if wy < map.height then
|
||||
floor, wall1, wall2 = map.th:getCell(wx, wy + 1)
|
||||
set[wall1 % 256] = true
|
||||
end
|
||||
set[0] = nil
|
||||
local floor_list = {}
|
||||
local wall_list = {}
|
||||
for i in pairs(set) do
|
||||
if self.block_info[i] then
|
||||
if self.block_info[i][1] == "floor" then
|
||||
floor_list[#floor_list + 1] = i
|
||||
elseif self.block_info[i][1] == "wall" then
|
||||
wall_list[#wall_list + 1] = i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if wx == self.recent_sample_x and wy == self.recent_sample_y then
|
||||
self.sample_i = self.sample_i + 1
|
||||
else
|
||||
self.sample_i = 1
|
||||
self.recent_sample_x = wx
|
||||
self.recent_sample_y = wy
|
||||
end
|
||||
MapEditorSetBlockBrush(
|
||||
floor_list[1 + (self.sample_i - 1) % #floor_list] or 0,
|
||||
wall_list [1 + (self.sample_i - 1) % #wall_list ] or 0
|
||||
)
|
||||
end
|
||||
|
||||
-- Called by the UI to set what should be painted.
|
||||
function UIMapEditor:setBlockBrush(f, w1, w2)
|
||||
local preview = f
|
||||
if w2 ~= 0 then
|
||||
preview = w2
|
||||
elseif w1 ~= 0 then
|
||||
preview = w1
|
||||
end
|
||||
self.block_brush_preview = preview
|
||||
self.block_brush_parcel = nil
|
||||
self.block_brush_f = f
|
||||
self.block_brush_w1 = w1
|
||||
self.block_brush_w2 = w2
|
||||
end
|
||||
|
||||
function UIMapEditor:setBlockBrushParcel(parcel)
|
||||
self.block_brush_preview = 24
|
||||
self.block_brush_parcel = parcel
|
||||
self.block_brush_f = self.block_brush_preview
|
||||
self.block_brush_w1 = 0
|
||||
self.block_brush_w2 = 0
|
||||
end
|
||||
|
||||
function UIMapEditor:undo()
|
||||
local last = self.command_stack:undo()
|
||||
self.ui.app.map.th:updateShadows()
|
||||
return last
|
||||
end
|
||||
|
||||
function UIMapEditor:redo()
|
||||
local last = self.command_stack:redo()
|
||||
self.ui.app.map.th:updateShadows()
|
||||
return last
|
||||
end
|
@@ -28,7 +28,7 @@ class "UIMenuBar" (Window)
|
||||
---@type UIMenuBar
|
||||
local UIMenuBar = _G["UIMenuBar"]
|
||||
|
||||
function UIMenuBar:UIMenuBar(ui)
|
||||
function UIMenuBar:UIMenuBar(ui, map_editor)
|
||||
self:Window()
|
||||
|
||||
local app = ui.app
|
||||
@@ -51,7 +51,11 @@ function UIMenuBar:UIMenuBar(ui)
|
||||
-- This list satifies: open_menus[x] == nil or open_menus[x].level == x
|
||||
self.open_menus = {}
|
||||
|
||||
self:makeMenu(app)
|
||||
if map_editor then
|
||||
self:makeMapeditorMenu(app)
|
||||
else
|
||||
self:makeGameMenu(app)
|
||||
end
|
||||
end
|
||||
|
||||
function UIMenuBar:onTick()
|
||||
@@ -521,7 +525,26 @@ function UIMenu:appendMenu(text, menu)
|
||||
}
|
||||
end
|
||||
|
||||
function UIMenuBar:makeMenu(app)
|
||||
--! Make a menu for the map editor.
|
||||
--!param app Application.
|
||||
function UIMenuBar:makeMapeditorMenu(app)
|
||||
local menu = UIMenu()
|
||||
menu:appendItem(_S.menu_file.load, function() self.ui:addWindow(UILoadMap(self.ui, "map")) end)
|
||||
:appendItem(_S.menu_file.save, function() self.ui:addWindow(UISaveMap(self.ui)) end)
|
||||
:appendItem(_S.menu_file.quit, function() self.ui:quit() end)
|
||||
self:addMenu(_S.menu.file, menu)
|
||||
|
||||
menu = UIMenu()
|
||||
menu:appendItem(_S.menu_player_count.players_1, function() self.ui.map_editor:setPlayerCount(1) end)
|
||||
:appendItem(_S.menu_player_count.players_2, function() self.ui.map_editor:setPlayerCount(2) end)
|
||||
:appendItem(_S.menu_player_count.players_3, function() self.ui.map_editor:setPlayerCount(3) end)
|
||||
:appendItem(_S.menu_player_count.players_4, function() self.ui.map_editor:setPlayerCount(4) end)
|
||||
self:addMenu(_S.menu.player_count, menu)
|
||||
end
|
||||
|
||||
--! Make a menu for the game.
|
||||
--!param app Application.
|
||||
function UIMenuBar:makeGameMenu(app)
|
||||
local menu = UIMenu()
|
||||
menu:appendItem(_S.menu_file.load, function() self.ui:addWindow(UILoadGame(self.ui, "game")) end)
|
||||
:appendItem(_S.menu_file.save, function() self.ui:addWindow(UISaveGame(self.ui)) end)
|
||||
@@ -754,7 +777,7 @@ function UIMenuBar:makeMenu(app)
|
||||
:appendCheckItem(_S.menu_debug_overlay.byte_5, false, overlay(35, 8, 5, 5, true), "")
|
||||
:appendCheckItem(_S.menu_debug_overlay.byte_6, false, overlay(35, 8, 6, 6, true), "")
|
||||
:appendCheckItem(_S.menu_debug_overlay.byte_7, false, overlay(35, 8, 7, 7, true), "")
|
||||
:appendCheckItem(_S.menu_debug_overlay.parcel, false, overlay(131107, 2, 0, 0, false), "")
|
||||
:appendCheckItem(_S.menu_debug_overlay.parcel, false, overlay("parcel"), "")
|
||||
)
|
||||
:appendItem(_S.menu_debug.sprite_viewer, function() dofile "sprite_viewer" end)
|
||||
)
|
||||
|
56
CorsixTH/Lua/dialogs/resizables/file_browsers/load_map.lua
Normal file
56
CorsixTH/Lua/dialogs/resizables/file_browsers/load_map.lua
Normal file
@@ -0,0 +1,56 @@
|
||||
--[[ Copyright (c) 2010 Manuel "Roujin" Wolf
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE. --]]
|
||||
|
||||
--! Load Map Window
|
||||
class "UILoadMap" (UIFileBrowser)
|
||||
|
||||
---@type UILoadMap
|
||||
local UILoadMap = _G["UILoadMap"]
|
||||
|
||||
function UILoadMap:UILoadMap(ui, mode)
|
||||
local path = ui.app.level_dir
|
||||
local treenode = FilteredFileTreeNode(path, ".map")
|
||||
treenode.label = "Maps"
|
||||
self:UIFileBrowser(ui, mode, _S.load_game_window.caption:format(".map"), 295, treenode, true)
|
||||
-- The most probable preference of sorting is by date - what you played last
|
||||
-- is the thing you want to play soon again.
|
||||
self.control:sortByDate()
|
||||
end
|
||||
|
||||
function UILoadMap:choiceMade(name)
|
||||
local app = self.ui.app
|
||||
-- Make sure there is no blue filter active.
|
||||
app.video:setBlueFilterActive(false)
|
||||
name = name:sub(app.level_dir:len() + 1)
|
||||
local status, err = pcall(app.loadLevel, app, name, nil, nil, nil, nil, true)
|
||||
if not status then
|
||||
err = _S.errors.load_prefix .. err
|
||||
print(err)
|
||||
app:loadMainMenu()
|
||||
app.ui:addWindow(UIInformation(self.ui, {err}))
|
||||
end
|
||||
end
|
||||
|
||||
function UILoadMap:close()
|
||||
UIResizable.close(self)
|
||||
if self.mode == "menu" then
|
||||
self.ui:addWindow(UIMainMenu(self.ui))
|
||||
end
|
||||
end
|
106
CorsixTH/Lua/dialogs/resizables/file_browsers/save_map.lua
Normal file
106
CorsixTH/Lua/dialogs/resizables/file_browsers/save_map.lua
Normal file
@@ -0,0 +1,106 @@
|
||||
--[[ Copyright (c) 2015 Stephen E. Baker et al.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE. --]]
|
||||
|
||||
--! Save Map Window
|
||||
class "UISaveMap" (UIFileBrowser)
|
||||
|
||||
---@type UISaveMap
|
||||
local UISaveMap = _G["UISaveMap"]
|
||||
|
||||
local pathsep = package.config:sub(1, 1)
|
||||
|
||||
local col_textbox = {
|
||||
red = 0,
|
||||
green = 0,
|
||||
blue = 0,
|
||||
}
|
||||
|
||||
local col_highlight = {
|
||||
red = 174,
|
||||
green = 166,
|
||||
blue = 218,
|
||||
}
|
||||
|
||||
local col_shadow = {
|
||||
red = 134,
|
||||
green = 126,
|
||||
blue = 178,
|
||||
}
|
||||
|
||||
function UISaveMap:UISaveMap(ui)
|
||||
local treenode = FilteredFileTreeNode(ui.app.level_dir, ".map")
|
||||
treenode.label = "Maps"
|
||||
self:UIFileBrowser(ui, "map", _S.save_map_window.caption:format(".map"), 265, treenode, true)
|
||||
-- The most probable preference of sorting is by date - what you played last
|
||||
-- is the thing you want to play soon again.
|
||||
self.control:sortByDate()
|
||||
|
||||
-- Textbox for entering new save name
|
||||
self.new_map_textbox = self:addBevelPanel(5, 310, self.width - 10, 17, col_textbox, col_highlight, col_shadow)
|
||||
:setLabel(_S.save_map_window.new_map, nil, "left"):setTooltip(_S.tooltip.save_map_window.new_map)
|
||||
:makeTextbox(--[[persistable:save_map_new_map_textbox_confirm_callback]] function() self:confirmName() end,
|
||||
--[[persistable:save_map_new_map_textbox_abort_callback]] function() self:abortName() end)
|
||||
end
|
||||
|
||||
--! Function called when textbox is aborted (e.g. by pressing escape)
|
||||
function UISaveMap:abortName()
|
||||
self.new_map_textbox.text = ""
|
||||
self.new_map_textbox.panel:setLabel(_S.save_map_window.new_map)
|
||||
end
|
||||
|
||||
--! Function called when textbox is confirmed (e.g. by pressing enter)
|
||||
function UISaveMap:confirmName()
|
||||
local filename = self.new_map_textbox.text
|
||||
local app = self.ui.app
|
||||
if filename == "" then
|
||||
self:abortName()
|
||||
return
|
||||
end
|
||||
self:trySave(app.level_dir .. filename .. ".map")
|
||||
end
|
||||
|
||||
--! Function called by clicking button of existing save #num
|
||||
function UISaveMap:choiceMade(name)
|
||||
self:trySave(name)
|
||||
end
|
||||
|
||||
--! Try to save the game with given filename; if already exists, create confirmation window first.
|
||||
function UISaveMap:trySave(filename)
|
||||
if lfs.attributes(filename, "size") ~= nil then
|
||||
self.ui:addWindow(UIConfirmDialog(self.ui, _S.confirmation.overwrite_save, --[[persistable:save_map_confirmation]] function() self:doSave(filename) end))
|
||||
else
|
||||
self:doSave(filename)
|
||||
end
|
||||
end
|
||||
|
||||
--! Actually do save the map with given filename.
|
||||
function UISaveMap:doSave(filename)
|
||||
filename = filename
|
||||
local ui = self.ui
|
||||
local map = ui.app.map
|
||||
self:close()
|
||||
|
||||
local status, err = pcall(map.save, map, filename)
|
||||
if not status then
|
||||
err = _S.errors.save_prefix .. err
|
||||
print(err)
|
||||
ui:addWindow(UIInformation(ui, {err}))
|
||||
end
|
||||
end
|
@@ -43,6 +43,7 @@ function UIMainMenu:UIMainMenu(ui)
|
||||
{_S.main_menu.continue, self.buttonContinueGame, _S.tooltip.main_menu.continue},
|
||||
{_S.main_menu.load_game, self.buttonLoadGame, _S.tooltip.main_menu.load_game},
|
||||
{_S.main_menu.options, self.buttonOptions, _S.tooltip.main_menu.options},
|
||||
{_S.main_menu.map_edit, self.buttonMapEdit, _S.tooltip.main_menu.map_edit},
|
||||
{_S.main_menu.exit, self.buttonExit, _S.tooltip.main_menu.exit}
|
||||
}
|
||||
self.no_menu_entries = #menu_items
|
||||
@@ -149,6 +150,10 @@ function UIMainMenu:buttonOptions()
|
||||
self.ui:addWindow(window)
|
||||
end
|
||||
|
||||
function UIMainMenu:buttonMapEdit()
|
||||
self.ui.app:mapEdit()
|
||||
end
|
||||
|
||||
function UIMainMenu:buttonExit()
|
||||
self.ui:addWindow(UIConfirmDialog(self.ui,
|
||||
_S.tooltip.main_menu.quit,
|
||||
|
1569
CorsixTH/Lua/dialogs/resizables/map_editor.lua
Normal file
1569
CorsixTH/Lua/dialogs/resizables/map_editor.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -39,9 +39,7 @@ function UICustomGame:UICustomGame(ui)
|
||||
self.label_font = TheApp.gfx:loadFont("QData", "Font01V")
|
||||
|
||||
-- Supply the required list of items to UIMenuList
|
||||
local path = debug.getinfo(1, "S").source:sub(2, -57)
|
||||
|
||||
path = path .. "Levels" .. pathsep
|
||||
local path = ui.app.level_dir
|
||||
|
||||
-- Create the actual list
|
||||
local items = {}
|
||||
|
@@ -32,30 +32,36 @@ local WM = SDL.wm
|
||||
local lfs = require "lfs"
|
||||
local pathsep = package.config:sub(1, 1)
|
||||
|
||||
function GameUI:GameUI(app, local_hospital)
|
||||
--! Game UI constructor.
|
||||
--!param app (Application) Application object.
|
||||
--!param local_hospital Hospital to display
|
||||
--!param map_editor (bool) Whether the map is editable.
|
||||
function GameUI:GameUI(app, local_hospital, map_editor)
|
||||
self:UI(app)
|
||||
|
||||
self.hospital = local_hospital
|
||||
self.tutorial = { chapter = 0, phase = 0 }
|
||||
|
||||
if _MAP_EDITOR then
|
||||
self:addWindow(UIMapEditor(self))
|
||||
if map_editor then
|
||||
self.map_editor = UIMapEditor(self)
|
||||
self:addWindow(self.map_editor)
|
||||
else
|
||||
self.adviser = UIAdviser(self)
|
||||
self.bottom_panel = UIBottomPanel(self)
|
||||
self.menu_bar = UIMenuBar(self)
|
||||
self.bottom_panel:addWindow(self.adviser)
|
||||
self:addWindow(self.bottom_panel)
|
||||
self:addWindow(self.menu_bar)
|
||||
end
|
||||
|
||||
-- UI widgets
|
||||
self.menu_bar = UIMenuBar(self, self.map_editor)
|
||||
self:addWindow(self.menu_bar)
|
||||
|
||||
local scr_w = app.config.width
|
||||
local scr_h = app.config.height
|
||||
self.visible_diamond = self:makeVisibleDiamond(scr_w, scr_h)
|
||||
if self.visible_diamond.w <= 0 or self.visible_diamond.h <= 0 then
|
||||
-- For a standard 128x128 map, screen size would have to be in the
|
||||
-- region of 3276x2457 in order to be too large.
|
||||
if not _MAP_EDITOR then
|
||||
if not self.map_editor then
|
||||
error "Screen size too large for the map"
|
||||
end
|
||||
end
|
||||
@@ -63,7 +69,7 @@ function GameUI:GameUI(app, local_hospital)
|
||||
app.map.th:getCameraTile(local_hospital:getPlayerIndex()))
|
||||
self.zoom_factor = 1
|
||||
self:scrollMap(-scr_w / 2, 16 - scr_h / 2)
|
||||
self.limit_to_visible_diamond = not _MAP_EDITOR
|
||||
self.limit_to_visible_diamond = not self.map_editor
|
||||
self.transparent_walls = false
|
||||
self.do_world_hit_test = true
|
||||
|
||||
@@ -141,7 +147,7 @@ end
|
||||
function GameUI:draw(canvas)
|
||||
local app = self.app
|
||||
local config = app.config
|
||||
if _MAP_EDITOR or not self.in_visible_diamond then
|
||||
if self.map_editor or not self.in_visible_diamond then
|
||||
canvas:fillBlack()
|
||||
end
|
||||
local zoom = self.zoom_factor
|
||||
@@ -505,7 +511,7 @@ function GameUI:onMouseUp(code, x, y)
|
||||
end
|
||||
|
||||
local button = self.button_codes[code]
|
||||
if button == "right" and not _MAP_EDITOR and highlight_x then
|
||||
if button == "right" and not self.map_editor and highlight_x then
|
||||
local window = self:getWindow(UIPatient)
|
||||
local patient = (window and window.patient.is_debug and window.patient) or self.hospital:getDebugPatient()
|
||||
if patient then
|
||||
|
@@ -845,9 +845,9 @@ end
|
||||
function Hospital:getHeliportPosition()
|
||||
local x, y = self.world.map.th:getHeliportTile(self:getPlayerIndex())
|
||||
-- NB: Level 2 has a heliport tile set, but no heliport, so we ensure that
|
||||
-- the specified tile is suitable by checking the adjacent spawn tile for
|
||||
-- the specified tile is suitable by checking the spawn tile for
|
||||
-- passability.
|
||||
if y > 1 and self.world.map:getCellFlag(x, y - 1, "passable") then
|
||||
if y > 0 and self.world.map:getCellFlag(x, y, "passable") then
|
||||
return x, y
|
||||
end
|
||||
end
|
||||
|
@@ -80,6 +80,8 @@ tooltip.casebook.cure_type.unknown = "You do not yet know how to treat this dise
|
||||
tooltip.research_policy.no_research = "No research is being carried out in this category at the moment"
|
||||
tooltip.research_policy.research_progress = "Progress towards the next discovery in this category: %1%/%2%"
|
||||
|
||||
menu["player_count"] = "PLAYER COUNT"
|
||||
|
||||
menu_file = {
|
||||
load = " (SHIFT+L) LOAD ",
|
||||
save = " (SHIFT+S) SAVE ",
|
||||
@@ -164,6 +166,12 @@ menu_debug_overlay = {
|
||||
byte_7 = " BYTE 7 ",
|
||||
parcel = " PARCEL ",
|
||||
}
|
||||
menu_player_count = {
|
||||
players_1 = " 1 PLAYER ",
|
||||
players_2 = " 2 PLAYERS ",
|
||||
players_3 = " 3 PLAYERS ",
|
||||
players_4 = " 4 PLAYERS ",
|
||||
}
|
||||
adviser = {
|
||||
room_forbidden_non_reachable_parts = "Placing the room in this location would result in parts of the hospital not being reachable.",
|
||||
warnings = {
|
||||
@@ -253,6 +261,7 @@ main_menu = {
|
||||
continue = "Continue Game",
|
||||
load_game = "Load Game",
|
||||
options = "Settings",
|
||||
map_edit = "Map Editor",
|
||||
savegame_version = "Savegame version: ",
|
||||
version = "Version: ",
|
||||
exit = "Exit",
|
||||
@@ -265,6 +274,7 @@ tooltip.main_menu = {
|
||||
continue = "Continue your latest saved game",
|
||||
load_game = "Load a saved game",
|
||||
options = "Tweak various settings",
|
||||
map_edit = "Create a custom map",
|
||||
exit = "No, no, please don't leave!",
|
||||
quit = "You are about to quit from CorsixTH. Are you sure this is what you want to do?",
|
||||
}
|
||||
@@ -311,6 +321,16 @@ tooltip.save_game_window = {
|
||||
new_save_game = "Enter name for a new savegame",
|
||||
}
|
||||
|
||||
save_map_window = {
|
||||
caption = "Save Map (%1%)",
|
||||
new_map = "New Map",
|
||||
}
|
||||
|
||||
tooltip.save_map_window = {
|
||||
map = "Overwrite map %s",
|
||||
new_map = "Enter name for a map savegame",
|
||||
}
|
||||
|
||||
menu_list_window = {
|
||||
name = "Name",
|
||||
save_date = "Modified",
|
||||
@@ -631,6 +651,40 @@ tooltip.update_window = {
|
||||
ignore = "Ignore this update for now. You will be notified again when you next open CorsixTH",
|
||||
}
|
||||
|
||||
map_editor_window = {
|
||||
pages = {
|
||||
inside = "Inside",
|
||||
outside = "Outside",
|
||||
foliage = "Foliage",
|
||||
hedgerow = "Hedgerow",
|
||||
pond = "Pond",
|
||||
road = "Road",
|
||||
north_wall = "North wall",
|
||||
west_wall = "West wall",
|
||||
helipad = "Helipad",
|
||||
delete_wall = "Delete walls",
|
||||
parcel_0 = "Parcel 0",
|
||||
parcel_1 = "Parcel 1",
|
||||
parcel_2 = "Parcel 2",
|
||||
parcel_3 = "Parcel 3",
|
||||
parcel_4 = "Parcel 4",
|
||||
parcel_5 = "Parcel 5",
|
||||
parcel_6 = "Parcel 6",
|
||||
parcel_7 = "Parcel 7",
|
||||
parcel_8 = "Parcel 8",
|
||||
parcel_9 = "Parcel 9",
|
||||
camera_1 = "Camera 1",
|
||||
camera_2 = "Camera 2",
|
||||
camera_3 = "Camera 3",
|
||||
camera_4 = "Camera 4",
|
||||
heliport_1 = "Heliport 1",
|
||||
heliport_2 = "Heliport 2",
|
||||
heliport_3 = "Heliport 3",
|
||||
heliport_4 = "Heliport 4",
|
||||
paste = "Paste area",
|
||||
}
|
||||
}
|
||||
|
||||
-------------------------------- UNUSED -----------------------------------
|
||||
------------------- (kept for backwards compatibility) ----------------------
|
||||
|
||||
|
@@ -60,6 +60,30 @@ function Map:getRoomId(x, y)
|
||||
return self.th:getCellFlags(math.floor(x), math.floor(y)).roomId
|
||||
end
|
||||
|
||||
function Map:setPlayerCount(count)
|
||||
self.th:setPlayerCount(count)
|
||||
end
|
||||
|
||||
function Map:getPlayerCount(count)
|
||||
self.th:getPlayerCount(count)
|
||||
end
|
||||
|
||||
--! Set the camera tile for the given player on the map
|
||||
--!param x (int) Horizontal position of tile to set camera on
|
||||
--!param y (int) Vertical position of the tile to set the camera on
|
||||
--!param player (int) Player number (1-4)
|
||||
function Map:setCameraTile(x, y, player)
|
||||
self.th:setCameraTile(x, y, player);
|
||||
end
|
||||
|
||||
--! Set the heliport tile for the given player on the map
|
||||
--!param x (int) Horizontal position of tile to set heliport on
|
||||
--!param y (int) Vertical position of the tile to set the heliport on
|
||||
--!param player (int) Player number (1-4)
|
||||
function Map:setHeliportTile(x, y, player)
|
||||
self.th:setHeliportTile(x, y, player);
|
||||
end
|
||||
|
||||
--! Set how to display the room temperature in the hospital map.
|
||||
--!param method (int) Way of displaying the temperature. See also THMapTemperatureDisplay enum.
|
||||
--! 1=red gradients, 2=blue/green/red colour shifts, 3=yellow/orange/red colour shifts
|
||||
@@ -141,7 +165,7 @@ the original game levels are considered.
|
||||
!param level_intro (string) If loading a custom level this message will be shown as soon as the level
|
||||
has been loaded.
|
||||
]]
|
||||
function Map:load(level, difficulty, level_name, map_file, level_intro)
|
||||
function Map:load(level, difficulty, level_name, map_file, level_intro, map_editor)
|
||||
local objects, i
|
||||
if not difficulty then
|
||||
difficulty = "full"
|
||||
@@ -197,14 +221,19 @@ function Map:load(level, difficulty, level_name, map_file, level_intro)
|
||||
errors, result = self:loadMapConfig(p, result, true)
|
||||
self.level_config = result
|
||||
end
|
||||
elseif _MAP_EDITOR then
|
||||
elseif map_editor then
|
||||
-- We're being fed data by the map editor.
|
||||
self.level_name = "MAP EDITOR"
|
||||
self.level_number = "MAP EDITOR"
|
||||
if level == "" then
|
||||
i, objects = self.th:loadBlank()
|
||||
else
|
||||
i, objects = self.th:load(level)
|
||||
local data, errors = self:getRawData(level)
|
||||
if data then
|
||||
i, objects = self.th:load(data)
|
||||
else
|
||||
return nil, errors
|
||||
end
|
||||
end
|
||||
assert(base_config, "No base config has been loaded!")
|
||||
|
||||
@@ -234,7 +263,7 @@ function Map:load(level, difficulty, level_name, map_file, level_intro)
|
||||
self.parcelTileCounts = {}
|
||||
for plot = 1, self.th:getPlotCount() do
|
||||
self.parcelTileCounts[plot] = self.th:getParcelTileCount(plot)
|
||||
if plot > 1 and not _MAP_EDITOR then
|
||||
if plot > 1 and not map_editor then
|
||||
-- TODO: On multiplayer maps, assign plots 2-N to players 2-N
|
||||
self.th:setPlotOwner(plot, 0)
|
||||
end
|
||||
@@ -243,6 +272,13 @@ function Map:load(level, difficulty, level_name, map_file, level_intro)
|
||||
return objects
|
||||
end
|
||||
|
||||
--[[! Saves the map to a .map file
|
||||
!param filename (string) Name of the file to save the map in
|
||||
]]
|
||||
function Map:save(filename)
|
||||
self.th:save(filename)
|
||||
end
|
||||
|
||||
--[[! Loads map configurations from files. Returns nil as first result
|
||||
if no configuration could be loaded and config as second result no matter what.
|
||||
!param filename (string) The absolute path to the config file to load
|
||||
@@ -370,6 +406,50 @@ function Map:updateDebugOverlayHeat()
|
||||
end
|
||||
end
|
||||
|
||||
function Map:updateDebugOverlayParcels()
|
||||
for x = 1, self.width do
|
||||
for y = 1, self.height do
|
||||
local xy = (y - 1) * self.width + x - 1
|
||||
self.debug_text[xy] = self.th:getCellFlags(x, y).parcelId
|
||||
if self.debug_text[xy] == 0 then
|
||||
self.debug_text[xy] = ''
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Map:updateDebugOverlayCamera()
|
||||
for x = 1, self.width do
|
||||
for y = 1, self.height do
|
||||
local xy = (y - 1) * self.width + x - 1
|
||||
self.debug_text[xy] = ''
|
||||
end
|
||||
end
|
||||
for p = 1, self.th:getPlayerCount() do
|
||||
local x, y = self.th:getCameraTile(p)
|
||||
if x and y then
|
||||
local xy = (y - 1) * self.width + x - 1
|
||||
self.debug_text[xy] = 'C'..p
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Map:updateDebugOverlayHeliport()
|
||||
for x = 1, self.width do
|
||||
for y = 1, self.height do
|
||||
local xy = (y - 1) * self.width + x - 1
|
||||
self.debug_text[xy] = ''
|
||||
end
|
||||
end
|
||||
for p = 1, self.th:getPlayerCount() do
|
||||
local x, y = self.th:getHeliportTile(p)
|
||||
if x and y then
|
||||
local xy = (y - 1) * self.width + x - 1
|
||||
self.debug_text[xy] = 'H'..p
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Map:loadDebugText(base_offset, xy_offset, first, last, bits_)
|
||||
self.debug_text = false
|
||||
self.debug_flags = false
|
||||
@@ -396,6 +476,18 @@ function Map:loadDebugText(base_offset, xy_offset, first, last, bits_)
|
||||
self.debug_text = {}
|
||||
self.updateDebugOverlay = self.updateDebugOverlayHeat
|
||||
self:updateDebugOverlay()
|
||||
elseif base_offset == "parcel" then
|
||||
self.debug_text = {}
|
||||
self.updateDebugOverlay = self.updateDebugOverlayParcels
|
||||
self:updateDebugOverlay()
|
||||
elseif base_offset == "camera" then
|
||||
self.debug_text = {}
|
||||
self.updateDebugOverlay = self.updateDebugOverlayCamera
|
||||
self:updateDebugOverlay()
|
||||
elseif base_offset == "heliport" then
|
||||
self.debug_text = {}
|
||||
self.updateDebugOverlay = self.updateDebugOverlayHeliport
|
||||
self:updateDebugOverlay()
|
||||
else
|
||||
local thData = self:getRawData()
|
||||
for x = 1, self.width do
|
||||
|
@@ -42,6 +42,8 @@ local Helicopter = _G["Helicopter"]
|
||||
|
||||
function Helicopter:Helicopter(world, object_type, hospital, direction, etc)
|
||||
local x, y = hospital:getHeliportPosition()
|
||||
-- Helicoptor needs to land below tile to be positioned correctly
|
||||
y = y + 1
|
||||
self:Object(world, object_type, x, y, direction, etc)
|
||||
self.th:makeInvisible()
|
||||
self:setPosition(0, -600)
|
||||
|
@@ -262,7 +262,7 @@ function World:initLevel(app, avail_rooms)
|
||||
end
|
||||
end
|
||||
end
|
||||
if #self.available_diseases == 0 and not _MAP_EDITOR then
|
||||
if #self.available_diseases == 0 and not self.map.level_number == "MAP EDITOR" then
|
||||
-- No diseases are needed if we're actually in the map editor!
|
||||
print("Warning: This level does not contain any diseases")
|
||||
end
|
||||
@@ -975,6 +975,8 @@ local outside_temperatures = {
|
||||
--! World ticks are translated to game ticks (or hours) depending on the
|
||||
-- current speed of the game. There are 50 hours in a TH day.
|
||||
function World:onTick()
|
||||
if self.map.level_number == "MAP EDITOR" then return end
|
||||
|
||||
if self.tick_timer == 0 then
|
||||
if self.autosave_next_tick then
|
||||
self.autosave_next_tick = nil
|
||||
|
@@ -24,6 +24,10 @@ SOFTWARE.
|
||||
#include "th_map.h"
|
||||
#include "th_pathfind.h"
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <exception>
|
||||
|
||||
static const int player_max = 4;
|
||||
|
||||
static int l_map_new(lua_State *L)
|
||||
{
|
||||
@@ -108,6 +112,14 @@ static int l_map_loadblank(lua_State *L)
|
||||
return 2;
|
||||
}
|
||||
|
||||
static int l_map_save(lua_State *L)
|
||||
{
|
||||
THMap *pMap = luaT_testuserdata<THMap>(L);
|
||||
std::string filename(luaL_checkstring(L, 2));
|
||||
pMap->save(filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static THAnimation* l_map_updateblueprint_getnextanim(lua_State *L, int& iIndex)
|
||||
{
|
||||
THAnimation *pAnim;
|
||||
@@ -344,6 +356,22 @@ static int l_map_get_player_count(lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_map_set_player_count(lua_State *L)
|
||||
{
|
||||
THMap* pMap = luaT_testuserdata<THMap>(L);
|
||||
int count = static_cast<int>(luaL_checkinteger(L, 2));
|
||||
|
||||
try
|
||||
{
|
||||
pMap->setPlayerCount(count);
|
||||
}
|
||||
catch (std::out_of_range)
|
||||
{
|
||||
return luaL_error(L, "Player count out of range %d", count);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_map_get_player_camera(lua_State *L)
|
||||
{
|
||||
THMap* pMap = luaT_testuserdata<THMap>(L);
|
||||
@@ -357,6 +385,20 @@ static int l_map_get_player_camera(lua_State *L)
|
||||
return 2;
|
||||
}
|
||||
|
||||
static int l_map_set_player_camera(lua_State *L)
|
||||
{
|
||||
THMap* pMap = luaT_testuserdata<THMap>(L);
|
||||
int iX = static_cast<int>(luaL_checkinteger(L, 2) - 1);
|
||||
int iY = static_cast<int>(luaL_checkinteger(L, 3) - 1);
|
||||
int iPlayer = static_cast<int>(luaL_optinteger(L, 4, 1));
|
||||
|
||||
if (iPlayer < 1 || iPlayer > player_max)
|
||||
return luaL_error(L, "Player index out of range: %i", iPlayer);
|
||||
|
||||
pMap->setPlayerCameraTile(iPlayer - 1, iX, iY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_map_get_player_heliport(lua_State *L)
|
||||
{
|
||||
THMap* pMap = luaT_testuserdata<THMap>(L);
|
||||
@@ -365,12 +407,26 @@ static int l_map_get_player_heliport(lua_State *L)
|
||||
bool bGood = pMap->getPlayerHeliportTile(iPlayer - 1, &iX, &iY);
|
||||
bGood = pMap->getPlayerHeliportTile(iPlayer - 1, &iX, &iY);
|
||||
if(!bGood)
|
||||
return luaL_error(L, "Player index out of range: %i", iPlayer);
|
||||
return luaL_error(L, "Player index out of range: %d", iPlayer);
|
||||
lua_pushinteger(L, iX + 1);
|
||||
lua_pushinteger(L, iY + 2);
|
||||
lua_pushinteger(L, iY + 1);
|
||||
return 2;
|
||||
}
|
||||
|
||||
static int l_map_set_player_heliport(lua_State *L)
|
||||
{
|
||||
THMap* pMap = luaT_testuserdata<THMap>(L);
|
||||
int iX = static_cast<int>(luaL_checkinteger(L, 2) - 1);
|
||||
int iY = static_cast<int>(luaL_checkinteger(L, 3) - 1);
|
||||
int iPlayer = static_cast<int>(luaL_optinteger(L, 4, 1));
|
||||
|
||||
if (iPlayer < 1 || iPlayer > player_max)
|
||||
return luaL_error(L, "Player index out of range: %i", iPlayer);
|
||||
|
||||
pMap->setPlayerHeliportTile(iPlayer - 1, iX, iY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_map_getcell(lua_State *L)
|
||||
{
|
||||
THMap* pMap = luaT_testuserdata<THMap>(L);
|
||||
@@ -855,10 +911,14 @@ void THLuaRegisterMap(const THLuaRegisterState_t *pState)
|
||||
luaT_setmetamethod(l_map_depersist, "depersist", MT_Anim);
|
||||
luaT_setfunction(l_map_load, "load");
|
||||
luaT_setfunction(l_map_loadblank, "loadBlank");
|
||||
luaT_setfunction(l_map_save, "save");
|
||||
luaT_setfunction(l_map_getsize, "size");
|
||||
luaT_setfunction(l_map_get_player_count, "getPlayerCount");
|
||||
luaT_setfunction(l_map_set_player_count, "setPlayerCount");
|
||||
luaT_setfunction(l_map_get_player_camera, "getCameraTile");
|
||||
luaT_setfunction(l_map_set_player_camera, "setCameraTile");
|
||||
luaT_setfunction(l_map_get_player_heliport, "getHeliportTile");
|
||||
luaT_setfunction(l_map_set_player_heliport, "setHeliportTile");
|
||||
luaT_setfunction(l_map_getcell, "getCell");
|
||||
luaT_setfunction(l_map_gettemperature, "getCellTemperature");
|
||||
luaT_setfunction(l_map_getcellflags, "getCellFlags");
|
||||
|
@@ -30,6 +30,9 @@ SOFTWARE.
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <exception>
|
||||
|
||||
th_map_node_flags& th_map_node_flags::operator=(uint32_t raw)
|
||||
{
|
||||
@@ -485,16 +488,16 @@ bool THMap::loadFromTHFile(const uint8_t* pData, size_t iDataLength,
|
||||
return true;
|
||||
}
|
||||
|
||||
void THMap::save(void (*fnWriter)(void*, const uint8_t*, size_t),
|
||||
void* pToken)
|
||||
void THMap::save(std::string filename)
|
||||
{
|
||||
uint8_t aBuffer[256] = {0};
|
||||
int iBufferNext = 0;
|
||||
std::basic_ofstream<uint8_t, std::char_traits<uint8_t>> os(filename, std::ios_base::trunc | std::ios_base::binary);
|
||||
|
||||
// Header
|
||||
aBuffer[0] = static_cast<uint8_t>(m_iPlayerCount);
|
||||
// TODO: Determine correct contents for the next 33 bytes
|
||||
fnWriter(pToken, aBuffer, 34);
|
||||
os.write(aBuffer, 34);
|
||||
|
||||
uint8_t aReverseBlockLUT[256] = {0};
|
||||
for(int i = 0; i < 256; ++i)
|
||||
@@ -540,7 +543,7 @@ void THMap::save(void (*fnWriter)(void*, const uint8_t*, size_t),
|
||||
|
||||
if(iBufferNext == sizeof(aBuffer))
|
||||
{
|
||||
fnWriter(pToken, aBuffer, sizeof(aBuffer));
|
||||
os.write(aBuffer, sizeof(aBuffer));
|
||||
iBufferNext = 0;
|
||||
}
|
||||
}
|
||||
@@ -551,7 +554,7 @@ void THMap::save(void (*fnWriter)(void*, const uint8_t*, size_t),
|
||||
aBuffer[iBufferNext++] = static_cast<uint8_t>(pNode->iParcelId >> 8);
|
||||
if(iBufferNext == sizeof(aBuffer))
|
||||
{
|
||||
fnWriter(pToken, aBuffer, sizeof(aBuffer));
|
||||
os.write(aBuffer, sizeof(aBuffer));
|
||||
iBufferNext = 0;
|
||||
}
|
||||
}
|
||||
@@ -559,7 +562,7 @@ void THMap::save(void (*fnWriter)(void*, const uint8_t*, size_t),
|
||||
// TODO: What are these two bytes?
|
||||
aBuffer[iBufferNext++] = 3;
|
||||
aBuffer[iBufferNext++] = 0;
|
||||
fnWriter(pToken, aBuffer, iBufferNext);
|
||||
os.write(aBuffer, iBufferNext);
|
||||
iBufferNext = 0;
|
||||
|
||||
std::memset(aBuffer, 0, 56);
|
||||
@@ -571,10 +574,11 @@ void THMap::save(void (*fnWriter)(void*, const uint8_t*, size_t),
|
||||
m_aiHeliportX[i], m_aiHeliportY[i]);
|
||||
iBufferNext += 2;
|
||||
}
|
||||
fnWriter(pToken, aBuffer, 16);
|
||||
os.write(aBuffer, 16);
|
||||
std::memset(aBuffer, 0, 16);
|
||||
// TODO: What are these 56 bytes?
|
||||
fnWriter(pToken, aBuffer, 56);
|
||||
os.write(aBuffer, 56);
|
||||
os.close();
|
||||
}
|
||||
|
||||
void THMap::setParcelOwner(int iParcelId, int iOwner)
|
||||
@@ -733,6 +737,14 @@ bool THMap::isParcelPurchasable(int iParcelId, int iPlayer)
|
||||
return false;
|
||||
}
|
||||
|
||||
void THMap::setPlayerCount(int count)
|
||||
{
|
||||
if (count < 1 || count > 4)
|
||||
throw new std::out_of_range("Player count must be between 1 and 4");
|
||||
|
||||
m_iPlayerCount = count;
|
||||
}
|
||||
|
||||
bool THMap::getPlayerCameraTile(int iPlayer, int* pX, int* pY) const
|
||||
{
|
||||
if(iPlayer < 0 || iPlayer >= getPlayerCount())
|
||||
|
@@ -24,6 +24,7 @@ SOFTWARE.
|
||||
#define CORSIX_TH_TH_MAP_H_
|
||||
#include "th_gfx.h"
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
/*
|
||||
Object type enumeration uses same values as original TH does.
|
||||
@@ -241,8 +242,7 @@ public:
|
||||
THMapLoadObjectCallback_t fnObjectCallback,
|
||||
void* pCallbackToken);
|
||||
|
||||
void save(void (*fnWriter)(void*, const uint8_t*, size_t),
|
||||
void* pToken);
|
||||
void save(std::string filename);
|
||||
|
||||
//! Set the sprite sheet to be used for drawing the map
|
||||
/*!
|
||||
@@ -275,6 +275,9 @@ public:
|
||||
inline int getParcelCount() const {return m_iParcelCount - 1;}
|
||||
|
||||
inline int getPlayerCount() const {return m_iPlayerCount;}
|
||||
|
||||
void setPlayerCount(int count);
|
||||
|
||||
bool getPlayerCameraTile(int iPlayer, int* pX, int* pY) const;
|
||||
bool getPlayerHeliportTile(int iPlayer, int* pX, int* pY) const;
|
||||
void setPlayerCameraTile(int iPlayer, int iX, int iY);
|
||||
|
Reference in New Issue
Block a user