Synchronize world state across editor and room saves
This commit is contained in:
parent
1965e548b4
commit
ee7d36e72b
3 changed files with 75 additions and 13 deletions
|
|
@ -492,6 +492,7 @@ local function saveRoomFromEditor()
|
||||||
gameWorld:invalidatePreview(name)
|
gameWorld:invalidatePreview(name)
|
||||||
if editorView == "world" then rebuildWorldRoomButtons() end
|
if editorView == "world" then rebuildWorldRoomButtons() end
|
||||||
currentRoom.name = name
|
currentRoom.name = name
|
||||||
|
gameWorld:setLevel(currentRoom)
|
||||||
editorRoomName = name
|
editorRoomName = name
|
||||||
roomNameField:editText(name)
|
roomNameField:editText(name)
|
||||||
activeField = nil
|
activeField = nil
|
||||||
|
|
@ -520,6 +521,7 @@ local function clearRuntimeSaves()
|
||||||
clearSaveConfirmationScope = nil
|
clearSaveConfirmationScope = nil
|
||||||
if roomName then
|
if roomName then
|
||||||
currentRoom = Room:new{ name = roomName }
|
currentRoom = Room:new{ name = roomName }
|
||||||
|
gameWorld:setLevel(currentRoom)
|
||||||
setEditorRoomName(currentRoom.name)
|
setEditorRoomName(currentRoom.name)
|
||||||
editorStatus = "Cleared " .. removed .. " runtime save item(s) and reloaded '" .. roomName .. ".'"
|
editorStatus = "Cleared " .. removed .. " runtime save item(s) and reloaded '" .. roomName .. ".'"
|
||||||
else
|
else
|
||||||
|
|
@ -905,6 +907,7 @@ local function openWorldRoom(cellX, cellY)
|
||||||
currentRoom = Room:new{ name = name }
|
currentRoom = Room:new{ name = name }
|
||||||
editorStatus = "Editing '" .. name .. ".'"
|
editorStatus = "Editing '" .. name .. ".'"
|
||||||
end
|
end
|
||||||
|
gameWorld:setLevel(currentRoom)
|
||||||
gameWorld:setLastRoom(currentRoom.name)
|
gameWorld:setLastRoom(currentRoom.name)
|
||||||
setEditorRoomName(currentRoom.name)
|
setEditorRoomName(currentRoom.name)
|
||||||
assetsView()
|
assetsView()
|
||||||
|
|
|
||||||
6
main.lua
6
main.lua
|
|
@ -66,8 +66,12 @@ function love.quit()
|
||||||
-- An explicit editor save/clear removes the runtime copy intentionally;
|
-- An explicit editor save/clear removes the runtime copy intentionally;
|
||||||
-- don't immediately recreate it while shutting down.
|
-- don't immediately recreate it while shutting down.
|
||||||
if currentRoom and not suppressRuntimeAutosave then
|
if currentRoom and not suppressRuntimeAutosave then
|
||||||
|
if gameWorld then
|
||||||
|
gameWorld:markDirty(currentRoom)
|
||||||
|
gameWorld:flushRuntime()
|
||||||
|
else
|
||||||
currentRoom:save()
|
currentRoom:save()
|
||||||
if gameWorld then gameWorld:setLastRoom(currentRoom.name) end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
77
world.lua
77
world.lua
|
|
@ -12,15 +12,54 @@ function World:initialize()
|
||||||
self.previewCache = {}
|
self.previewCache = {}
|
||||||
self.lastRoom = nil
|
self.lastRoom = nil
|
||||||
self:load()
|
self:load()
|
||||||
|
self:loadRuntimeState()
|
||||||
self.levels = {}
|
self.levels = {}
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function World:loadLevels()
|
function World:loadLevels()
|
||||||
for _, placed in ipairs(self.rooms) do self.levels[placed.name] = Room:new{name = placed.name} end
|
-- Gameplay saves go to LÖVE's runtime directory. Seed every world slice
|
||||||
|
-- through the same runtime-first loader, not just the initially visible one.
|
||||||
|
for _, placed in ipairs(self.rooms) do self.levels[placed.name] = Room:new{name = placed.name, runtime = true} end
|
||||||
self:rebuildCollision()
|
self:rebuildCollision()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- The editor can replace a level with a freshly loaded authored Room. Keep the
|
||||||
|
-- global simulation pointing at that exact instance, not a stale preload.
|
||||||
|
function World:setLevel(room)
|
||||||
|
if not room or not room.name or not self:roomLocation(room.name) then return false end
|
||||||
|
self.levels[room.name] = room
|
||||||
|
self:rebuildCollision()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Runtime state is one logical world snapshot: each level slice is written to
|
||||||
|
-- LÖVE's save directory, and this companion file says which slice was active.
|
||||||
|
-- The authored world layout remains source-only for the editor.
|
||||||
|
function World:loadRuntimeState()
|
||||||
|
local raw = love.filesystem.read("rooms/world.sav")
|
||||||
|
if not raw then return end
|
||||||
|
local ok, state = pcall(TSerial.unpack, raw)
|
||||||
|
if ok and type(state) == "table" and state.lastRoom and self:roomLocation(state.lastRoom) then
|
||||||
|
self.lastRoom = state.lastRoom
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function World:saveRuntime(dirtyRooms)
|
||||||
|
for room in pairs(dirtyRooms or {}) do room:save() end
|
||||||
|
return love.filesystem.write("rooms/world.sav", TSerial.pack({lastRoom = currentRoom and currentRoom.name}, nil, true))
|
||||||
|
end
|
||||||
|
|
||||||
|
function World:markDirty(room)
|
||||||
|
self.dirtyRooms = self.dirtyRooms or {}
|
||||||
|
self.dirtyRooms[room] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function World:flushRuntime()
|
||||||
|
self:saveRuntime(self.dirtyRooms)
|
||||||
|
self.dirtyRooms = {}
|
||||||
|
end
|
||||||
|
|
||||||
function World:listRoomNames()
|
function World:listRoomNames()
|
||||||
local names = {}
|
local names = {}
|
||||||
for _, file in ipairs(love.filesystem.getDirectoryItems("rooms")) do
|
for _, file in ipairs(love.filesystem.getDirectoryItems("rooms")) do
|
||||||
|
|
@ -120,7 +159,6 @@ end
|
||||||
function World:setLastRoom(name)
|
function World:setLastRoom(name)
|
||||||
if name and self.lastRoom ~= name then
|
if name and self.lastRoom ~= name then
|
||||||
self.lastRoom = name
|
self.lastRoom = name
|
||||||
return self:save()
|
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
@ -310,8 +348,10 @@ function World:movePlayer(room, key)
|
||||||
current[color] = player:getGridPos()
|
current[color] = player:getGridPos()
|
||||||
end
|
end
|
||||||
if moved then
|
if moved then
|
||||||
|
self:markDirty(room)
|
||||||
self:playerJoinCheck(room, previous, current)
|
self:playerJoinCheck(room, previous, current)
|
||||||
self:tick(room)
|
self:tick(room)
|
||||||
|
self:flushRuntime()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -326,6 +366,7 @@ function World:switchPlayerColor(room, color)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
player:playSwitchSound(canSwitch)
|
player:playSwitchSound(canSwitch)
|
||||||
|
if canSwitch then self:markDirty(room); self:flushRuntime() end
|
||||||
return canSwitch
|
return canSwitch
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -353,19 +394,33 @@ function World:nextRoom(room, pos)
|
||||||
elseif pos.y < 1 then exit, entry.y = "top", gridHeight end
|
elseif pos.y < 1 then exit, entry.y = "top", gridHeight end
|
||||||
local nextName = self:getNeighbor(room.name, exit) or room.name
|
local nextName = self:getNeighbor(room.name, exit) or room.name
|
||||||
local activeColors = room:getActiveColors()
|
local activeColors = room:getActiveColors()
|
||||||
room:save()
|
self:markDirty(room)
|
||||||
room.activeColors = {red = false, green = false, blue = false}
|
local destination = self.levels[nextName]
|
||||||
currentRoom = self.levels[nextName]
|
|
||||||
for _, color in ipairs(activeColors) do
|
for _, color in ipairs(activeColors) do
|
||||||
if currentRoom.player[color] then
|
local player = room.player[color]
|
||||||
currentRoom.player[color].x, currentRoom.player[color].y = entry.x, entry.y
|
-- Active copies travel with the player. Inactive copies are deliberately
|
||||||
else
|
-- left in their room so World:playerJoinCheck can merge them later.
|
||||||
currentRoom:registerPlayer(Player:new(entry.x, entry.y, color), color)
|
room.player[color] = nil
|
||||||
|
for i = #room.colorsPlayerHas, 1, -1 do
|
||||||
|
if room.colorsPlayerHas[i] == color then table.remove(room.colorsPlayerHas, i) end
|
||||||
end
|
end
|
||||||
|
if destination.player[color] then
|
||||||
|
for i = #destination.colorsPlayerHas, 1, -1 do
|
||||||
|
if destination.colorsPlayerHas[i] == color then table.remove(destination.colorsPlayerHas, i) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
destination.player[color] = player
|
||||||
|
table.insert(destination.colorsPlayerHas, color)
|
||||||
|
end
|
||||||
|
room.activeColors = {red = false, green = false, blue = false}
|
||||||
|
currentRoom = destination
|
||||||
|
self:markDirty(currentRoom)
|
||||||
|
for _, color in ipairs(activeColors) do
|
||||||
|
currentRoom.player[color].x, currentRoom.player[color].y = entry.x, entry.y
|
||||||
currentRoom:setActiveColor(color, true)
|
currentRoom:setActiveColor(color, true)
|
||||||
end
|
end
|
||||||
self:playerJoinCheck(currentRoom)
|
self:playerJoinCheck(currentRoom)
|
||||||
self:setLastRoom(currentRoom.name)
|
self.lastRoom = currentRoom.name
|
||||||
setEditorRoomName(currentRoom.name)
|
setEditorRoomName(currentRoom.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -397,7 +452,7 @@ function World:moveGroup(group, direction)
|
||||||
self:rebuildCollision()
|
self:rebuildCollision()
|
||||||
for room in pairs(changed) do room:resolveSwitchesAndDoors() end
|
for room in pairs(changed) do room:resolveSwitchesAndDoors() end
|
||||||
self:rebuildCollision()
|
self:rebuildCollision()
|
||||||
for room in pairs(changed) do if room ~= currentRoom then room:save() end end
|
for room in pairs(changed) do self:markDirty(room) end
|
||||||
end
|
end
|
||||||
|
|
||||||
function World:drawOverlaps(room, color)
|
function World:drawOverlaps(room, color)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue