diff --git a/level_editor/editor.lua b/level_editor/editor.lua index ce1e8af..90a177b 100644 --- a/level_editor/editor.lua +++ b/level_editor/editor.lua @@ -492,6 +492,7 @@ local function saveRoomFromEditor() gameWorld:invalidatePreview(name) if editorView == "world" then rebuildWorldRoomButtons() end currentRoom.name = name + gameWorld:setLevel(currentRoom) editorRoomName = name roomNameField:editText(name) activeField = nil @@ -520,6 +521,7 @@ local function clearRuntimeSaves() clearSaveConfirmationScope = nil if roomName then currentRoom = Room:new{ name = roomName } + gameWorld:setLevel(currentRoom) setEditorRoomName(currentRoom.name) editorStatus = "Cleared " .. removed .. " runtime save item(s) and reloaded '" .. roomName .. ".'" else @@ -905,6 +907,7 @@ local function openWorldRoom(cellX, cellY) currentRoom = Room:new{ name = name } editorStatus = "Editing '" .. name .. ".'" end + gameWorld:setLevel(currentRoom) gameWorld:setLastRoom(currentRoom.name) setEditorRoomName(currentRoom.name) assetsView() diff --git a/main.lua b/main.lua index 245a2e9..a9566d1 100644 --- a/main.lua +++ b/main.lua @@ -66,8 +66,12 @@ function love.quit() -- An explicit editor save/clear removes the runtime copy intentionally; -- don't immediately recreate it while shutting down. if currentRoom and not suppressRuntimeAutosave then - currentRoom:save() - if gameWorld then gameWorld:setLastRoom(currentRoom.name) end + if gameWorld then + gameWorld:markDirty(currentRoom) + gameWorld:flushRuntime() + else + currentRoom:save() + end end end diff --git a/world.lua b/world.lua index 8c8aeaf..2ab6ab3 100644 --- a/world.lua +++ b/world.lua @@ -12,15 +12,54 @@ function World:initialize() self.previewCache = {} self.lastRoom = nil self:load() + self:loadRuntimeState() self.levels = {} end 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() 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() local names = {} for _, file in ipairs(love.filesystem.getDirectoryItems("rooms")) do @@ -120,7 +159,6 @@ end function World:setLastRoom(name) if name and self.lastRoom ~= name then self.lastRoom = name - return self:save() end return true end @@ -310,8 +348,10 @@ function World:movePlayer(room, key) current[color] = player:getGridPos() end if moved then + self:markDirty(room) self:playerJoinCheck(room, previous, current) self:tick(room) + self:flushRuntime() end end @@ -326,6 +366,7 @@ function World:switchPlayerColor(room, color) end end player:playSwitchSound(canSwitch) + if canSwitch then self:markDirty(room); self:flushRuntime() end return canSwitch end @@ -353,19 +394,33 @@ function World:nextRoom(room, pos) elseif pos.y < 1 then exit, entry.y = "top", gridHeight end local nextName = self:getNeighbor(room.name, exit) or room.name local activeColors = room:getActiveColors() - room:save() - room.activeColors = {red = false, green = false, blue = false} - currentRoom = self.levels[nextName] + self:markDirty(room) + local destination = self.levels[nextName] for _, color in ipairs(activeColors) do - if currentRoom.player[color] then - currentRoom.player[color].x, currentRoom.player[color].y = entry.x, entry.y - else - currentRoom:registerPlayer(Player:new(entry.x, entry.y, color), color) + local player = room.player[color] + -- Active copies travel with the player. Inactive copies are deliberately + -- left in their room so World:playerJoinCheck can merge them later. + room.player[color] = nil + for i = #room.colorsPlayerHas, 1, -1 do + if room.colorsPlayerHas[i] == color then table.remove(room.colorsPlayerHas, i) 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) end self:playerJoinCheck(currentRoom) - self:setLastRoom(currentRoom.name) + self.lastRoom = currentRoom.name setEditorRoomName(currentRoom.name) end @@ -397,7 +452,7 @@ function World:moveGroup(group, direction) self:rebuildCollision() for room in pairs(changed) do room:resolveSwitchesAndDoors() end 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 function World:drawOverlaps(room, color)