This commit is contained in:
Your Name 2026-08-07 03:22:28 -04:00
parent f8e3d78332
commit b9b0726506
6 changed files with 140 additions and 81 deletions

View file

@ -48,8 +48,15 @@ end
-- Runtime saves live in LÖVE's write directory. This deliberately uses only
-- love.filesystem operations: they cannot delete files from the source tree.
function clearRuntimeSaveData()
function clearRuntimeSaveData(roomName)
local removed = 0
if roomName then
-- love.filesystem.remove only affects the writable save directory, never
-- the shipped/source room with the same path.
if love.filesystem.remove("rooms/" .. roomName .. ".sav") then removed = 1 end
return removed
end
local function removeTree(path)
if love.filesystem.isDirectory(path) then
for _, child in ipairs(love.filesystem.getDirectoryItems(path)) do

View file

@ -33,6 +33,7 @@ local editorRoomName = nil
local roomNameField = nil
local editorStatus = nil
local clearSaveConfirmationExpires = 0
local clearSaveConfirmationScope = nil
local worldSeedConfirmationExpires = 0
local worldMode = "place"
local worldRoomButtons = {}
@ -129,7 +130,8 @@ local function saveRoomFromEditor()
return
end
if not currentRoom:saveToSource(name) then
local savePlayerPositions = love.keyboard.isDown("lshift", "rshift")
if not currentRoom:saveToSource(name, savePlayerPositions, oldName) then
editorStatus = "Could not save '" .. name .. "'."
return
end
@ -146,20 +148,28 @@ local function saveRoomFromEditor()
editorRoomName = name
roomNameField:editText(name)
activeField = nil
editorStatus = "Saved source level '" .. name .. ".'"
editorStatus = "Saved source level '" .. name .. "'" .. (savePlayerPositions and " with player positions." or ".")
end
local function clearRuntimeSaves()
local now = love.timer.getTime()
if now > clearSaveConfirmationExpires then
local clearAll = love.keyboard.isDown("lshift", "rshift")
local roomName = currentRoom.name
local scope = clearAll and "all" or roomName
if now > clearSaveConfirmationExpires or clearSaveConfirmationScope ~= scope then
clearSaveConfirmationExpires = now + 3
editorStatus = "Click the clear-save icon again within 3 seconds to erase runtime saves."
clearSaveConfirmationScope = scope
if clearAll then
editorStatus = "Shift-click the clear-save icon again within 3 seconds to erase runtime saves for every room."
else
editorStatus = "Click the clear-save icon again within 3 seconds to erase the runtime save for '" .. (roomName or "this room") .. "'."
end
return
end
local removed = clearRuntimeSaveData()
local removed = clearRuntimeSaveData(clearAll and nil or roomName)
clearSaveConfirmationExpires = 0
local roomName = currentRoom.name
clearSaveConfirmationScope = nil
if roomName then
currentRoom = Room:new{ name = roomName }
setEditorRoomName(currentRoom.name)
@ -521,6 +531,7 @@ local function openWorldRoom(cellX, cellY)
currentRoom = Room:new{ name = name }
editorStatus = "Editing '" .. name .. ".'"
end
gameWorld:setLastRoom(currentRoom.name)
setEditorRoomName(currentRoom.name)
assetsView()
end

View file

@ -33,8 +33,9 @@ function love.load()
inputTimer = Timer.new()
currentRoom = Room:new{
name = "start" -- TEMP: load a migrated level to eyeball it; revert once the room picker lands
name = gameWorld.lastRoom or "start"
}
gameWorld:setLastRoom(currentRoom.name)
setEditorRoomName(currentRoom.name)
response = ""

View file

@ -36,11 +36,6 @@ function Room:initialize(t)
end
self:setActiveColor(color, true)
end
elseif not next(self.player) then
-- fresh load of a room with no saved player: default red spawn so it's
-- playable (the player starts with only red)
self:registerPlayer(Player:new(1, 1, "red"), "red")
self:setActiveColor("red", true)
end
self.name = t.name
@ -702,13 +697,37 @@ end
-- This is intentionally separate from Room:save. It is the editor's explicit
-- "write this level into the project" action; gameplay autosaves must never
-- call it.
function Room:saveToSource(name)
function Room:saveToSource(name, savePlayerPositions, playerSourceName)
name = name or self.name or "test"
local room = TSerial.pack(self:serialize(), nil, true)
local saveTable = self:serialize(savePlayerPositions ~= false)
if savePlayerPositions == false then
-- A normal editor save keeps the authored spawn positions from the
-- existing source level. During a rename that source is still oldName.
local sourceName = playerSourceName or name
local existing = readFromSource("rooms/" .. sourceName .. ".sav")
local keptPlayer = false
if existing then
local previous = TSerial.unpack(existing)
for _, object in ipairs(previous.objects or {}) do
if object.class == "player" then
table.insert(saveTable.objects, object)
keptPlayer = true
end
end
end
-- A newly named blank level has no prior source spawn to retain.
if not keptPlayer then
for color, player in pairs(self.player) do
local pos = player:getGridPos()
table.insert(saveTable.objects, { class = "player", color = color, x = pos.x, y = pos.y })
end
end
end
local room = TSerial.pack(saveTable, nil, true)
return writeToSource("rooms/" .. name .. ".sav", room)
end
function Room:serialize()
function Room:serialize(includePlayers)
local objects = {}
local function emit(class, color, entity)
@ -716,9 +735,11 @@ function Room:serialize()
table.insert(objects, { class = class, color = color, x = pos.x, y = pos.y })
end
if includePlayers ~= false then
for color, player in pairs(self.player) do
emit("player", color, player)
end
end
for color, collideableArray in pairs(self.collideables) do
for _, collideable in pairs(collideableArray) do
@ -769,6 +790,7 @@ function nextRoom(pos)
x = newPos.x, y = newPos.y, activeColors = currentRoom:getActiveColors()
}
}
if gameWorld then gameWorld:setLastRoom(currentRoom.name) end
if currentRoom.name then setEditorRoomName(currentRoom.name) end
end

View file

@ -1,151 +1,152 @@
{
height=18,
lastRoom="2",
rooms={
{
y=11,
name="start",
x=9
x=9,
y=11
},
{
y=10,
name="3",
x=10
x=10,
y=10
},
{
y=10,
name="4",
x=11
x=11,
y=10
},
{
y=10,
name="5",
x=12
x=12,
y=10
},
{
y=9,
name="village_1",
x=12
x=12,
y=9
},
{
y=8,
name="village_2",
x=12
x=12,
y=8
},
{
y=8,
name="village_3",
x=13
x=13,
y=8
},
{
y=8,
name="village_4",
x=11
x=11,
y=8
},
{
y=7,
name="yellow_1",
x=11
x=11,
y=7
},
{
y=6,
name="yellow_3",
x=11
x=11,
y=6
},
{
y=7,
name="yellow_2",
x=12
x=12,
y=7
},
{
y=5,
name="yellow_4",
x=11
x=11,
y=5
},
{
y=4,
name="yellow_5",
x=11
x=11,
y=4
},
{
y=5,
name="yellow_6",
x=10
x=10,
y=5
},
{
y=1,
name="default",
x=1
x=1,
y=1
},
{
y=1,
name="old_yellow_3",
x=3
x=3,
y=1
},
{
y=1,
name="test_patt",
x=5
x=5,
y=1
},
{
y=1,
name="test_patt_update",
x=7
x=7,
y=1
},
{
y=1,
name="white_asymmetrical",
x=9
x=9,
y=1
},
{
y=1,
name="white_ok",
x=11
x=11,
y=1
},
{
y=1,
name="white_primary",
x=13
x=13,
y=1
},
{
y=1,
name="yellow_arches",
x=15
x=15,
y=1
},
{
y=1,
name="yellow_asymmetrical",
x=17
x=17,
y=1
},
{
y=3,
name="yellow_cc_moustache",
x=1
x=1,
y=3
},
{
y=3,
name="yellow_color_changing_levvel_1",
x=3
x=3,
y=3
},
{
y=3,
name="yellow_skull",
x=5
x=5,
y=3
},
{
y=3,
name="yellow_smile",
x=7
x=7,
y=3
},
{
y=10,
name="2",
x=9
x=9,
y=10
},
{
y=9,
name="room_11_09",
x=11
x=11,
y=9
}
},
width=18,
height=18
width=18
}

View file

@ -8,6 +8,7 @@ function World:initialize()
self.rooms = {}
self.roomByName = {}
self.previewCache = {}
self.lastRoom = nil
self:load()
end
@ -26,6 +27,7 @@ function World:load()
if not raw then return end
local ok, save = pcall(TSerial.unpack, raw)
if not ok or type(save) ~= "table" then return end
self.lastRoom = save.lastRoom
for _, room in ipairs(save.rooms or {}) do
if room.name and self:isInBounds(room.x, room.y) then
self:_putRoom(room.name, room.x, room.y)
@ -38,7 +40,7 @@ function World:save()
for _, room in ipairs(self.rooms) do
table.insert(rooms, { name = room.name, x = room.x, y = room.y })
end
return writeToSource("rooms/world.sav", TSerial.pack({ width = self.width, height = self.height, rooms = rooms }, nil, true))
return writeToSource("rooms/world.sav", TSerial.pack({ width = self.width, height = self.height, lastRoom = self.lastRoom, rooms = rooms }, nil, true))
end
function World:isInBounds(x, y)
@ -87,6 +89,7 @@ function World:removeRoom(x, y)
if room.x == x and room.y == y then
self.roomByName[room.name] = nil
table.remove(self.rooms, i)
if self.lastRoom == room.name then self.lastRoom = nil end
return self:save(), room.name
end
end
@ -99,11 +102,20 @@ function World:renameRoom(oldName, newName)
self.roomByName[oldName] = nil
room.name = newName
self.roomByName[newName] = room
if self.lastRoom == oldName then self.lastRoom = newName end
self:invalidatePreview(oldName)
self:invalidatePreview(newName)
return self:save()
end
function World:setLastRoom(name)
if name and self.lastRoom ~= name then
self.lastRoom = name
return self:save()
end
return true
end
function World:getNeighbor(name, exit)
local room = self:roomLocation(name)
if not room then return nil end
@ -149,7 +161,12 @@ function World:drawRoomPreview(name, x, y, size)
else
local props = globalAssetProperties[object.class]
if props and props.sprites then
local colors = object.class == "door" and { "red", "green", "blue" } or { object.color }
local colors
if object.class == "door" then
colors = object.color and { object.color } or { "red", "green", "blue" }
else
colors = { object.color }
end
for _, color in ipairs(colors) do
local path = props.sprites[color]
if path then