196 lines
5.7 KiB
Lua
196 lines
5.7 KiB
Lua
World = class("World")
|
|
|
|
-- The world is deliberately only a layout: rooms continue to own their puzzle
|
|
-- state. Keeping the two separate lets the editor move a room without
|
|
-- rewriting its level file.
|
|
function World:initialize()
|
|
self.width, self.height = 18, 18
|
|
self.rooms = {}
|
|
self.roomByName = {}
|
|
self.previewCache = {}
|
|
self.lastRoom = nil
|
|
self:load()
|
|
end
|
|
|
|
function World:listRoomNames()
|
|
local names = {}
|
|
for _, file in ipairs(love.filesystem.getDirectoryItems("rooms")) do
|
|
local name = file:match("^(.+)%.sav$")
|
|
if name and name ~= "world" then table.insert(names, name) end
|
|
end
|
|
table.sort(names)
|
|
return names
|
|
end
|
|
|
|
function World:load()
|
|
local raw = readFromSource("rooms/world.sav")
|
|
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)
|
|
end
|
|
end
|
|
end
|
|
|
|
function World:save()
|
|
local rooms = {}
|
|
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, lastRoom = self.lastRoom, rooms = rooms }, nil, true))
|
|
end
|
|
|
|
function World:isInBounds(x, y)
|
|
return x and y and x >= 1 and x <= self.width and y >= 1 and y <= self.height
|
|
end
|
|
|
|
function World:roomAt(x, y)
|
|
for _, room in ipairs(self.rooms) do
|
|
if room.x == x and room.y == y then return room.name end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function World:roomLocation(name)
|
|
return self.roomByName[name]
|
|
end
|
|
|
|
function World:_putRoom(name, x, y)
|
|
local existing = self.roomByName[name]
|
|
if existing then
|
|
for i = #self.rooms, 1, -1 do
|
|
if self.rooms[i] == existing then table.remove(self.rooms, i) end
|
|
end
|
|
end
|
|
for i = #self.rooms, 1, -1 do
|
|
if self.rooms[i].x == x and self.rooms[i].y == y then
|
|
self.roomByName[self.rooms[i].name] = nil
|
|
table.remove(self.rooms, i)
|
|
end
|
|
end
|
|
local room = { name = name, x = x, y = y }
|
|
table.insert(self.rooms, room)
|
|
self.roomByName[name] = room
|
|
return room
|
|
end
|
|
|
|
function World:placeRoom(name, x, y)
|
|
if not name or not self:isInBounds(x, y) then return false end
|
|
self:_putRoom(name, x, y)
|
|
return self:save()
|
|
end
|
|
|
|
function World:removeRoom(x, y)
|
|
for i = #self.rooms, 1, -1 do
|
|
local room = self.rooms[i]
|
|
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
|
|
return false
|
|
end
|
|
|
|
function World:renameRoom(oldName, newName)
|
|
local room = self:roomLocation(oldName)
|
|
if not room or oldName == newName then return false end
|
|
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
|
|
local directions = {
|
|
left = { x = -1, y = 0 }, right = { x = 1, y = 0 },
|
|
top = { x = 0, y = -1 }, bottom = { x = 0, y = 1 },
|
|
}
|
|
local direction = directions[exit]
|
|
if not direction then return nil end
|
|
return self:roomAt(room.x + direction.x, room.y + direction.y)
|
|
end
|
|
|
|
function World:getCellFromMousePos(mouseX, mouseY)
|
|
return math.floor(mouseX / (width / self.width)) + 1, math.floor(mouseY / (height / self.height)) + 1
|
|
end
|
|
|
|
function World:getRoomData(name)
|
|
-- The current room is live: its preview reflects unsaved edits immediately.
|
|
if currentRoom and currentRoom.name == name then return currentRoom:serialize() end
|
|
if not self.previewCache[name] then
|
|
local raw = readFromSource("rooms/" .. name .. ".sav")
|
|
if not raw then return nil end
|
|
local ok, room = pcall(TSerial.unpack, raw)
|
|
if not ok then return nil end
|
|
self.previewCache[name] = room
|
|
end
|
|
return self.previewCache[name]
|
|
end
|
|
|
|
function World:invalidatePreview(name)
|
|
self.previewCache[name] = nil
|
|
end
|
|
|
|
function World:drawRoomPreview(name, x, y, size)
|
|
local room = self:getRoomData(name)
|
|
if not room then return end
|
|
local scale = size / (gridWidth * 16)
|
|
love.graphics.setScissor(x, y, size, size)
|
|
for _, object in ipairs(room.objects or {}) do
|
|
if object.class == "npc" then
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.print("?", x + (object.x - 1) * 16 * scale, y + (object.y - 1) * 16 * scale, 0, scale, scale)
|
|
else
|
|
local props = globalAssetProperties[object.class]
|
|
if props and props.sprites then
|
|
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
|
|
love.graphics.setColor(gColor[color]:set())
|
|
love.graphics.draw(loadSprite(path), x + (object.x - 1) * 16 * scale, y + (object.y - 1) * 16 * scale, 0, scale, scale)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
love.graphics.setScissor()
|
|
end
|
|
|
|
function World:draw()
|
|
local cell = width / self.width
|
|
love.graphics.setColor(12 / 255, 12 / 255, 20 / 255, 1)
|
|
love.graphics.rectangle("fill", 0, 0, width, height)
|
|
for y = 1, self.height do
|
|
for x = 1, self.width do
|
|
local screenX, screenY = (x - 1) * cell, (y - 1) * cell
|
|
local name = self:roomAt(x, y)
|
|
if name then self:drawRoomPreview(name, screenX, screenY, cell) end
|
|
love.graphics.setColor(name and 1 or 70 / 255, name and 1 or 70 / 255, name and 1 or 90 / 255, 1)
|
|
love.graphics.rectangle("line", screenX, screenY, cell, cell)
|
|
end
|
|
end
|
|
end
|