Compare commits
10 commits
e065ab83de
...
e73f63b5d4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e73f63b5d4 | ||
|
|
5413823b26 | ||
|
|
c92c0f6ff7 | ||
|
|
ff5abf6bcd | ||
|
|
2e0013ec17 | ||
|
|
1850737cf6 | ||
|
|
12c37b5fa9 | ||
|
|
0f79fe99ae | ||
|
|
b9b0726506 | ||
|
|
f8e3d78332 |
44 changed files with 4530 additions and 3864 deletions
24
_helpers.lua
24
_helpers.lua
|
|
@ -48,10 +48,18 @@ end
|
||||||
|
|
||||||
-- Runtime saves live in LÖVE's write directory. This deliberately uses only
|
-- Runtime saves live in LÖVE's write directory. This deliberately uses only
|
||||||
-- love.filesystem operations: they cannot delete files from the source tree.
|
-- love.filesystem operations: they cannot delete files from the source tree.
|
||||||
function clearRuntimeSaveData()
|
function clearRuntimeSaveData(roomName)
|
||||||
local removed = 0
|
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)
|
local function removeTree(path)
|
||||||
if love.filesystem.isDirectory(path) then
|
local info = love.filesystem.getInfo(path)
|
||||||
|
if info and info.type == "directory" then
|
||||||
for _, child in ipairs(love.filesystem.getDirectoryItems(path)) do
|
for _, child in ipairs(love.filesystem.getDirectoryItems(path)) do
|
||||||
removeTree(path .. "/" .. child)
|
removeTree(path .. "/" .. child)
|
||||||
end
|
end
|
||||||
|
|
@ -60,7 +68,7 @@ function clearRuntimeSaveData()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- These are the only files this game writes through love.filesystem: room
|
-- These are the only files this game writes through love.filesystem: room
|
||||||
-- autosaves and the generated asset cache. Source levels are read-only to
|
-- autosaves and the generated asset cache. Source levels are read-only to
|
||||||
-- this API and cannot be removed here.
|
-- this API and cannot be removed here.
|
||||||
removeTree("rooms")
|
removeTree("rooms")
|
||||||
if love.filesystem.remove("assets.prop") then removed = removed + 1 end
|
if love.filesystem.remove("assets.prop") then removed = removed + 1 end
|
||||||
|
|
@ -102,16 +110,18 @@ function cellShapeFromBox(box)
|
||||||
return cells
|
return cells
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Which 16x16 cells of a sprite are non-empty, for multi-cell collision shapes.
|
-- Which 16x16 cells of an image are non-empty, for multi-cell collision
|
||||||
function getCellsFromSprite(sprite)
|
-- shapes. LÖVE 11 no longer exposes Image:getData(), so inspect ImageData
|
||||||
local w, h = sprite:getWidth(), sprite:getHeight()
|
-- directly instead of creating a GPU Image first.
|
||||||
|
function getCellsFromSprite(spritePath)
|
||||||
|
local img = love.image.newImageData(spritePath)
|
||||||
|
local w, h = img:getWidth(), img:getHeight()
|
||||||
local box = {w = w / 16, h = h / 16}
|
local box = {w = w / 16, h = h / 16}
|
||||||
local boxCells = cellShapeFromBox(box)
|
local boxCells = cellShapeFromBox(box)
|
||||||
if box.w < 1 or box.h < 1 then
|
if box.w < 1 or box.h < 1 then
|
||||||
return boxCells
|
return boxCells
|
||||||
end
|
end
|
||||||
|
|
||||||
local img = sprite:getData()
|
|
||||||
local cellsToRemove = {}
|
local cellsToRemove = {}
|
||||||
|
|
||||||
for _, cell in pairs(boxCells) do
|
for _, cell in pairs(boxCells) do
|
||||||
|
|
|
||||||
3
art/food/apple_scaled.meta
Normal file
3
art/food/apple_scaled.meta
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
class="immoveable"
|
||||||
|
}
|
||||||
3
art/food/banana.meta
Normal file
3
art/food/banana.meta
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
class="immoveable"
|
||||||
|
}
|
||||||
1
art/food/beer_mug.meta
Normal file
1
art/food/beer_mug.meta
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{class="moveable"}
|
||||||
1
art/food/wine.meta
Normal file
1
art/food/wine.meta
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{class="moveable"}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 879 B After Width: | Height: | Size: 338 B |
Binary file not shown.
|
Before Width: | Height: | Size: 890 B After Width: | Height: | Size: 353 B |
Binary file not shown.
|
Before Width: | Height: | Size: 879 B After Width: | Height: | Size: 340 B |
3
art/tech/image.meta
Normal file
3
art/tech/image.meta
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
class="moveable"
|
||||||
|
}
|
||||||
3
art/tech/image2.meta
Normal file
3
art/tech/image2.meta
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
class="immoveable"
|
||||||
|
}
|
||||||
|
|
@ -4,10 +4,11 @@
|
||||||
Color = class("Color")
|
Color = class("Color")
|
||||||
|
|
||||||
Color.static.list = {
|
Color.static.list = {
|
||||||
red = {r = 255, g = 0, b = 0, a = 255},
|
-- LÖVE 11 represents color components as normalized floats, not bytes.
|
||||||
green = {r = 0, g = 255, b = 0, a = 255},
|
red = {r = 1, g = 0, b = 0, a = 1},
|
||||||
blue = {r = 0, g = 0, b = 255, a = 255},
|
green = {r = 0, g = 1, b = 0, a = 1},
|
||||||
white = {r = 255, g = 255, b = 255, a = 255}
|
blue = {r = 0, g = 0, b = 1, a = 1},
|
||||||
|
white = {r = 1, g = 1, b = 1, a = 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Color:initialize(inColor)
|
function Color:initialize(inColor)
|
||||||
|
|
|
||||||
7
conf.lua
7
conf.lua
|
|
@ -1,5 +1,12 @@
|
||||||
function love.conf(t)
|
function love.conf(t)
|
||||||
|
-- This project uses the normalized-color and ImageData APIs introduced in
|
||||||
|
-- LÖVE 11. Pinning the intended runtime also gives a useful warning when a
|
||||||
|
-- player tries to launch it with an older installation.
|
||||||
|
t.version = "11.5"
|
||||||
t.window.width = 900
|
t.window.width = 900
|
||||||
t.window.height = 640
|
t.window.height = 640
|
||||||
t.window.title = "CHROMA SOLSTICE"
|
t.window.title = "CHROMA SOLSTICE"
|
||||||
|
t.window.resizable = true
|
||||||
|
t.window.minwidth = 700
|
||||||
|
t.window.minheight = 500
|
||||||
end
|
end
|
||||||
11
dialogable.lua
Normal file
11
dialogable.lua
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
-- Dialogable: a mixin that makes any Entity subclass talkable. `include` it and
|
||||||
|
-- give the entity a `self.book` (an Ink book); bumping the entity then opens the
|
||||||
|
-- modal Dialogue. npcs, signs, artifacts — anything you can talk to — are just
|
||||||
|
-- Dialogables. For now an entity carries one book; later it can swap books as it
|
||||||
|
-- progresses. All it does is route a bump to the dialogue manager.
|
||||||
|
Dialogable = {}
|
||||||
|
|
||||||
|
function Dialogable:onBump()
|
||||||
|
if self.book then Dialogue.open(self.book) end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
-- Dead-simple modal dialogue overlay driven by a narrator (Ink) story.
|
||||||
|
-- No variables, no saved state: open a book, walk its paragraphs, pick a
|
||||||
|
-- choice, then a keypress dismisses it. `narrator` is the global set up in
|
||||||
|
-- main.lua; Dialogable entities (see npc.lua) call Dialogue.open on bump.
|
||||||
|
Dialogue = {}
|
||||||
|
|
||||||
|
local story -- active narrator story, or nil when the box is closed
|
||||||
|
local paragraphs = {} -- text lines currently shown
|
||||||
|
local choices = {} -- current choices (empty once we reach an ending)
|
||||||
|
local selected = 1
|
||||||
|
|
||||||
|
function Dialogue.isOpen()
|
||||||
|
return story ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Run the story forward to the next choice point (or the end), gathering every
|
||||||
|
-- paragraph printed along the way.
|
||||||
|
local function advance()
|
||||||
|
paragraphs = {}
|
||||||
|
while story:can_continue() do
|
||||||
|
for _, p in ipairs(story:continue()) do
|
||||||
|
table.insert(paragraphs, p.text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
choices = story:can_choose() and story:get_choices() or {}
|
||||||
|
selected = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dialogue.open(book)
|
||||||
|
if story then return end -- already talking: ignore repeat bumps
|
||||||
|
story = narrator.init_story(book)
|
||||||
|
story:begin()
|
||||||
|
advance()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dialogue.close()
|
||||||
|
story, paragraphs, choices, selected = nil, {}, {}, 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dialogue.keypressed(key)
|
||||||
|
if #choices == 0 then
|
||||||
|
-- an ending is on screen: any key dismisses the box
|
||||||
|
Dialogue.close()
|
||||||
|
elseif key == "up" then
|
||||||
|
selected = selected > 1 and selected - 1 or #choices
|
||||||
|
elseif key == "down" then
|
||||||
|
selected = selected < #choices and selected + 1 or 1
|
||||||
|
elseif key == "return" or key == "space" or key == "x" then
|
||||||
|
story:choose(selected)
|
||||||
|
advance()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- LCD font sized to the current play area, rebuilt only when the size changes,
|
||||||
|
-- so dialogue text scales with the game like every sprite does.
|
||||||
|
local dlgFont, dlgFontSize
|
||||||
|
local function dialogueFont()
|
||||||
|
local size = math.max(8, math.floor(height / gridHeight * 0.4))
|
||||||
|
if not dlgFont or size ~= dlgFontSize then
|
||||||
|
dlgFont = love.graphics.newFont("font/LCD_Solid.ttf", size)
|
||||||
|
dlgFontSize = size
|
||||||
|
end
|
||||||
|
return dlgFont
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dialogue.draw()
|
||||||
|
if not story then return end
|
||||||
|
|
||||||
|
local fnt = dialogueFont()
|
||||||
|
local fh = fnt:getHeight()
|
||||||
|
local pad, lineH = fh * 0.8, fh * 1.3
|
||||||
|
local boxH = height * 0.4
|
||||||
|
local y0 = height - boxH
|
||||||
|
local wrap = width - pad * 2
|
||||||
|
|
||||||
|
-- Dialogue obeys the world's colour rule: only the channels the player has
|
||||||
|
-- are rendered. One colour -> that colour; all three -> white (additive), so
|
||||||
|
-- a red-only player literally reads the sign in red.
|
||||||
|
local has = { red = false, green = false, blue = false }
|
||||||
|
for _, c in ipairs(currentRoom.colorsPlayerHas) do has[c] = true end
|
||||||
|
local r = has.red and 1 or 0
|
||||||
|
local g = has.green and 1 or 0
|
||||||
|
local b = has.blue and 1 or 0
|
||||||
|
|
||||||
|
local prevFont = love.graphics.getFont()
|
||||||
|
love.graphics.setFont(fnt)
|
||||||
|
|
||||||
|
love.graphics.setColor(0, 0, 0, 0.82)
|
||||||
|
love.graphics.rectangle("fill", 0, y0, width, boxH)
|
||||||
|
|
||||||
|
local y = y0 + pad
|
||||||
|
love.graphics.setColor(r, g, b, 1)
|
||||||
|
for _, text in ipairs(paragraphs) do
|
||||||
|
love.graphics.printf(text, pad, y, wrap)
|
||||||
|
local _, lines = fnt:getWrap(text, wrap)
|
||||||
|
y = y + math.max(1, #lines) * lineH
|
||||||
|
end
|
||||||
|
|
||||||
|
y = y + lineH * 0.5
|
||||||
|
for i, choice in ipairs(choices) do
|
||||||
|
local dim = (i == selected) and 1 or 0.5 -- selected is brighter + "> "
|
||||||
|
love.graphics.setColor(r * dim, g * dim, b * dim, 1)
|
||||||
|
local line = ((i == selected) and "> " or " ") .. choice.text
|
||||||
|
love.graphics.printf(line, pad, y, wrap)
|
||||||
|
local _, lines = fnt:getWrap(line, wrap)
|
||||||
|
y = y + math.max(1, #lines) * lineH
|
||||||
|
end
|
||||||
|
|
||||||
|
love.graphics.setFont(prevFont)
|
||||||
|
end
|
||||||
84
docs/adding-npcs.md
Normal file
84
docs/adding-npcs.md
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
# Adding a talkable NPC
|
||||||
|
|
||||||
|
An NPC/sign is just an immovable `Entity` that mixes in `Dialogable`. Bumping it
|
||||||
|
(a rejected move) opens the dialogue box. Talking is the same everywhere — you
|
||||||
|
only supply **art** + **a story**.
|
||||||
|
|
||||||
|
## 1. Write the story
|
||||||
|
|
||||||
|
Create `stories/<name>.ink` — a line, a few choices, an ending:
|
||||||
|
|
||||||
|
```ink
|
||||||
|
yo wassup
|
||||||
|
|
||||||
|
* kick the sign
|
||||||
|
it doesnt budge.
|
||||||
|
* [say nothing] you have nothing to say to a sign.
|
||||||
|
- -> END
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Compile it
|
||||||
|
|
||||||
|
The game can't parse `.ink` (LÖVE has no lpeg), so compile to a `.lua` book:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
lua tools/compile_ink.lua <name>
|
||||||
|
```
|
||||||
|
|
||||||
|
Re-run this **every time you edit the `.ink`** — the game loads the `.lua`, not
|
||||||
|
the `.ink`. (Needs `lua` + `lua-lpeg` installed.)
|
||||||
|
|
||||||
|
## 3. Load the book
|
||||||
|
|
||||||
|
In `main.lua`, next to `startSignBook`:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
<name>Book = require "stories.<name>"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Make the entity
|
||||||
|
|
||||||
|
In `npc.lua`, copy `Sign` — pick your art and name your book:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
Oracle = class("Oracle", Entity)
|
||||||
|
Oracle:include(Dialogable)
|
||||||
|
|
||||||
|
function Oracle:initialize(x, y)
|
||||||
|
self.book = oracleBook -- from step 3
|
||||||
|
Entity.initialize(self, {
|
||||||
|
x = x, y = y, color = "red",
|
||||||
|
collision = "unmoveable",
|
||||||
|
sprites = {
|
||||||
|
red = loadSprite("art/npc/oracle_r.png"),
|
||||||
|
green = loadSprite("art/npc/oracle_g.png"),
|
||||||
|
blue = loadSprite("art/npc/oracle_b.png"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Per-channel art lives in `art/npc/` as `<name>_r/_g/_b.png` (16px cells).
|
||||||
|
|
||||||
|
## 5. Place it in a room
|
||||||
|
|
||||||
|
Map a save class to your entity in `Room:createEntity` (`room.lua`), alongside
|
||||||
|
the `npc` → `Sign` branch:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
if class == "oracle" then
|
||||||
|
local entity = Oracle:new(x, y)
|
||||||
|
self:registerNpc(entity)
|
||||||
|
return entity
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add it to a room's `rooms/<room>.sav` `objects` list:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{ class = "oracle", x = 4, y = 8 }
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it — walk into it and it talks. NPCs are stamped into every colour's
|
||||||
|
collision matrix, so they block movement and obey the world's colour rules (a
|
||||||
|
red-only player reads them in red).
|
||||||
50
docs/dialogue-consequences.md
Normal file
50
docs/dialogue-consequences.md
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Dialogue consequences — next steps
|
||||||
|
|
||||||
|
Design for dialogue trees with consequences across conversations ("talk again
|
||||||
|
does something else", NPC A affects NPC B, dialogue opens doors). Builds on the
|
||||||
|
existing narrator (Ink) setup — see `dialogue_manager.lua`, `dialogable.lua`.
|
||||||
|
|
||||||
|
## Core principle
|
||||||
|
|
||||||
|
**Books are disposable views; a global `WorldState` table is the single source
|
||||||
|
of truth.** Each Ink book reads flags in and writes flags out; it holds no
|
||||||
|
authority, so it can be re-`init`ed and thrown away without losing anything.
|
||||||
|
This resolves book-per-character's weakness (book A's visit counts are invisible
|
||||||
|
to book B) — B branches on a `WorldState` flag, not on A's runtime state.
|
||||||
|
|
||||||
|
Mirrors the existing world-vs-room split (`world.lua`: "the world is only a
|
||||||
|
layout; rooms own their puzzle state"). Narrative progress is a third, global
|
||||||
|
category with its own save file.
|
||||||
|
|
||||||
|
Keep book-per-character (`Dialogable.book`), add a stable `self.storyId`.
|
||||||
|
|
||||||
|
## The bug to fix first
|
||||||
|
|
||||||
|
`Dialogue.open` re-`init`s the story every bump (`dialogue_manager.lua:31-33`),
|
||||||
|
discarding all state — so there's no "again" to hang consequences on.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Add a global `WorldState = {}` (flags + counters).
|
||||||
|
2. `saveProgress` / `loadProgress` → `save/progress.sav` via TSerial +
|
||||||
|
`readFromSource` / `writeToSource` (helpers already exist, used in
|
||||||
|
`world.lua` / `world_seed.lua`). Shape: `{ version = 1, flags = WorldState }`.
|
||||||
|
Flat table, no per-book blobs initially.
|
||||||
|
3. `Dialogue.bridge(story)` on open:
|
||||||
|
- `story:bind("flag", fn)` — read a WorldState flag from Ink.
|
||||||
|
- `story:bind("setflag", fn)` — write one back.
|
||||||
|
- gameplay binds, e.g. `give_color` / `open_door` calling into `currentRoom`.
|
||||||
|
4. Cache the live story per `storyId` in the Dialogue module (stop re-init'ing).
|
||||||
|
5. Hook `saveProgress` into the existing autosave points (near `room.lua:814`).
|
||||||
|
|
||||||
|
"Talk again does X" = increment an own `WorldState` counter (e.g.
|
||||||
|
`sage_talk_count`) on open, branch on it in Ink. No reliance on Ink
|
||||||
|
visit-count persistence.
|
||||||
|
|
||||||
|
## Deferred
|
||||||
|
|
||||||
|
Persisting `story:save_state()` (plain table, keyed by `storyId`) only buys
|
||||||
|
in-book niceties — consumed sticky choices, resume mid-conversation across a
|
||||||
|
hard quit. Skipped initially: it couples the save file to book structure and
|
||||||
|
forces narrator's `migrate` hook on every `.ink` edit. `WorldState` flags don't
|
||||||
|
— a renamed flag just reads `nil`/false.
|
||||||
19
entity.lua
19
entity.lua
|
|
@ -16,13 +16,18 @@ function Entity:initialize(t)
|
||||||
self.collision = t.collision --"none", "moveable", "unmoveable"
|
self.collision = t.collision --"none", "moveable", "unmoveable"
|
||||||
self.sprite = repSprite
|
self.sprite = repSprite
|
||||||
self.shaAmount = t.sha or 1
|
self.shaAmount = t.sha or 1
|
||||||
self.onBump = t.onBump or function(self) return true end
|
-- onBump is a method (see Entity:onBump) so mixins like Dialogable can
|
||||||
|
-- override it; a per-instance t.onBump still wins when supplied.
|
||||||
|
if t.onBump then self.onBump = t.onBump end
|
||||||
self.tick = t.tick or function(self) return true end
|
self.tick = t.tick or function(self) return true end
|
||||||
|
|
||||||
self.drawOffset = {x = 0, y = 0}
|
self.drawOffset = {x = 0, y = 0}
|
||||||
if self.size.w < 1 or self.size.h < 1 then
|
if self.size.w < 1 or self.size.h < 1 then
|
||||||
self.drawOffset.x = (drawScale * 16 * self.size.w) / 2
|
-- Small sprites (notably 10px switches) are drawn inside a 16px cell.
|
||||||
self.drawOffset.y = (drawScale * 16 * self.size.h) / 2
|
-- Offset by the leftover space in native sprite-pixels (e.g. (16-10)/2 = 3);
|
||||||
|
-- drawScale is applied at draw time so this stays correct across resizes.
|
||||||
|
self.drawOffset.x = (16 - self.spriteSize.w) / 2
|
||||||
|
self.drawOffset.y = (16 - self.spriteSize.h) / 2
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -119,7 +124,7 @@ function Entity:draw(channel)
|
||||||
if not sprite then return end
|
if not sprite then return end
|
||||||
love.graphics.setColor(gColor[channel]:set())
|
love.graphics.setColor(gColor[channel]:set())
|
||||||
local p = self:getDrawPos()
|
local p = self:getDrawPos()
|
||||||
love.graphics.draw(sprite, p.x + self.drawOffset.x, p.y + self.drawOffset.y, 0, drawScale, drawScale)
|
love.graphics.draw(sprite, p.x + self.drawOffset.x * drawScale, p.y + self.drawOffset.y * drawScale, 0, drawScale, drawScale)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entity:canMove()
|
function Entity:canMove()
|
||||||
|
|
@ -157,7 +162,11 @@ function Entity:setSprite(sprite)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entity:sha()
|
function Entity:sha()
|
||||||
return math.random(-worldSha, worldSha) * .5
|
return math.random(-worldSha, worldSha) * .125 * drawScale
|
||||||
|
end
|
||||||
|
|
||||||
|
function Entity:onBump()
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entity:bump()
|
function Entity:bump()
|
||||||
|
|
|
||||||
40
input.lua
40
input.lua
|
|
@ -3,8 +3,48 @@ Input = {}
|
||||||
Input.playermovekeys = { up = "up", down = "down", left = "left", right = "right" }
|
Input.playermovekeys = { up = "up", down = "down", left = "left", right = "right" }
|
||||||
Input.playerswitchkeys = { ["1"] = "1", ["2"] = "2", ["3"] = "3"}
|
Input.playerswitchkeys = { ["1"] = "1", ["2"] = "2", ["3"] = "3"}
|
||||||
Input.colorfromkey = {"red", "green", "blue"}
|
Input.colorfromkey = {"red", "green", "blue"}
|
||||||
|
Input.moveRepeatDelay = .28
|
||||||
|
Input.moveRepeatInterval = .09
|
||||||
|
Input.heldMoveKey = nil
|
||||||
|
Input.moveRepeatElapsed = 0
|
||||||
|
Input.moveRepeatStarted = false
|
||||||
|
|
||||||
function Input:handler(dt)
|
function Input:handler(dt)
|
||||||
|
local key = self.heldMoveKey
|
||||||
|
if key and not love.keyboard.isDown(key) then
|
||||||
|
key = nil
|
||||||
|
for _, candidate in ipairs({"up", "down", "left", "right"}) do
|
||||||
|
if love.keyboard.isDown(candidate) then key = candidate; break end
|
||||||
|
end
|
||||||
|
self.heldMoveKey = key
|
||||||
|
self.moveRepeatElapsed = 0
|
||||||
|
self.moveRepeatStarted = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if not key then return nil end
|
||||||
|
self.moveRepeatElapsed = self.moveRepeatElapsed + dt
|
||||||
|
if not self.moveRepeatStarted then
|
||||||
|
if self.moveRepeatElapsed >= self.moveRepeatDelay then
|
||||||
|
self.moveRepeatElapsed = 0
|
||||||
|
self.moveRepeatStarted = true
|
||||||
|
return key
|
||||||
|
end
|
||||||
|
elseif self.moveRepeatElapsed >= self.moveRepeatInterval then
|
||||||
|
self.moveRepeatElapsed = 0
|
||||||
|
return key
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input:beginMove(key)
|
||||||
|
self.heldMoveKey = key
|
||||||
|
self.moveRepeatElapsed = 0
|
||||||
|
self.moveRepeatStarted = false
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input:stopMoveRepeat()
|
||||||
|
self.heldMoveKey = nil
|
||||||
|
self.moveRepeatElapsed = 0
|
||||||
|
self.moveRepeatStarted = false
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,59 @@ local editorRoomName = nil
|
||||||
local roomNameField = nil
|
local roomNameField = nil
|
||||||
local editorStatus = nil
|
local editorStatus = nil
|
||||||
local clearSaveConfirmationExpires = 0
|
local clearSaveConfirmationExpires = 0
|
||||||
|
local clearSaveConfirmationScope = nil
|
||||||
local worldSeedConfirmationExpires = 0
|
local worldSeedConfirmationExpires = 0
|
||||||
local worldMode = "place"
|
local worldMode = "place"
|
||||||
local worldRoomButtons = {}
|
local worldRoomButtons = {}
|
||||||
local worldControls = {}
|
local worldControls = {}
|
||||||
local worldPage = 1
|
local worldPage = 1
|
||||||
local worldPageSize = 5
|
local worldPageSize = 5
|
||||||
|
local assetSearchText = ""
|
||||||
|
local assetSearchButtons = {}
|
||||||
|
local worldSearchText = ""
|
||||||
|
local worldSearchBox = { x = 10, y = 34, w = 0, h = 28 }
|
||||||
|
|
||||||
|
local function fuzzyMatches(value, query)
|
||||||
|
query = (query or ""):lower():gsub("%s+", "")
|
||||||
|
if query == "" then return true end
|
||||||
|
local index = 1
|
||||||
|
for character in value:lower():gmatch(".") do
|
||||||
|
if character == query:sub(index, index) then
|
||||||
|
index = index + 1
|
||||||
|
if index > #query then return true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function beginSearch(field)
|
||||||
|
activeField = field
|
||||||
|
inputText = field == "assetSearch" and assetSearchText or worldSearchText
|
||||||
|
response = inputText
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rebuildAssetSearchButtons()
|
||||||
|
assetSearchButtons = {}
|
||||||
|
if assetSearchText == "" then return end
|
||||||
|
local names = {}
|
||||||
|
for asset in pairs(gameAssets) do
|
||||||
|
if fuzzyMatches(asset, assetSearchText) then table.insert(names, asset) end
|
||||||
|
end
|
||||||
|
table.sort(names)
|
||||||
|
for index, asset in ipairs(names) do
|
||||||
|
if index > 8 then break end
|
||||||
|
local sprites = {}
|
||||||
|
for color, location in pairs(gameAssets[asset].sprites) do sprites[color] = love.graphics.newImage(location) end
|
||||||
|
table.insert(assetSearchButtons, createUIElement{
|
||||||
|
sprites = sprites,
|
||||||
|
name = "asset",
|
||||||
|
text = asset,
|
||||||
|
x = 30,
|
||||||
|
y = 45 + (index - 1) * 62,
|
||||||
|
onClic = { left = function() selectAsset(asset) end }
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function buttonContains(button, x, y)
|
local function buttonContains(button, x, y)
|
||||||
return x > button.x and x < button.x + button.w and y > button.y and y < button.y + button.h
|
return x > button.x and x < button.x + button.w and y > button.y and y < button.y + button.h
|
||||||
|
|
@ -46,14 +93,17 @@ end
|
||||||
|
|
||||||
local function rebuildWorldRoomButtons()
|
local function rebuildWorldRoomButtons()
|
||||||
worldRoomButtons = {}
|
worldRoomButtons = {}
|
||||||
local names = gameWorld:listRoomNames()
|
local names = {}
|
||||||
|
for _, name in ipairs(gameWorld:listRoomNames()) do
|
||||||
|
if fuzzyMatches(name, worldSearchText) then table.insert(names, name) end
|
||||||
|
end
|
||||||
local pages = math.max(1, math.ceil(#names / worldPageSize))
|
local pages = math.max(1, math.ceil(#names / worldPageSize))
|
||||||
worldPage = constrain(worldPage, 1, pages)
|
worldPage = constrain(worldPage, 1, pages)
|
||||||
local first = (worldPage - 1) * worldPageSize + 1
|
local first = (worldPage - 1) * worldPageSize + 1
|
||||||
for index = first, math.min(first + worldPageSize - 1, #names) do
|
for index = first, math.min(first + worldPageSize - 1, #names) do
|
||||||
local roomName = names[index]
|
local roomName = names[index]
|
||||||
local row = index - first
|
local row = index - first
|
||||||
table.insert(worldRoomButtons, { name = roomName, x = 10, y = 36 + row * 68, w = sideBarBox.w - 20, h = 60 })
|
table.insert(worldRoomButtons, { name = roomName, x = 10, y = 70 + row * 68, w = sideBarBox.w - 20, h = 60 })
|
||||||
end
|
end
|
||||||
worldControls = {
|
worldControls = {
|
||||||
{ text = worldMode == "place" and "MODE: PLACE ROOMS" or "MODE: OPEN LEVELS", x = 10, y = sideBarBox.h - 194, w = sideBarBox.w - 20, h = 34,
|
{ text = worldMode == "place" and "MODE: PLACE ROOMS" or "MODE: OPEN LEVELS", x = 10, y = sideBarBox.h - 194, w = sideBarBox.w - 20, h = 34,
|
||||||
|
|
@ -81,16 +131,19 @@ end
|
||||||
|
|
||||||
local function drawWorldSidebar()
|
local function drawWorldSidebar()
|
||||||
love.graphics.setColor(gColor.white:set())
|
love.graphics.setColor(gColor.white:set())
|
||||||
love.graphics.printf("ROOMS — " .. (worldMode == "place" and "select to place; re-click to clear" or "click a map room to open"), 8, 10, sideBarBox.w - 16, "center")
|
love.graphics.printf("ROOMS — " .. (worldMode == "place" and "select to place; re-click to clear" or "click a map room to open"), 8, 6, sideBarBox.w - 16, "center")
|
||||||
|
worldSearchBox.w = sideBarBox.w - 20
|
||||||
|
love.graphics.rectangle("line", worldSearchBox.x, worldSearchBox.y, worldSearchBox.w, worldSearchBox.h)
|
||||||
|
love.graphics.printf("SEARCH: " .. worldSearchText, worldSearchBox.x + 6, worldSearchBox.y + 7, worldSearchBox.w - 12, "left")
|
||||||
for _, button in ipairs(worldRoomButtons) do
|
for _, button in ipairs(worldRoomButtons) do
|
||||||
love.graphics.setColor(button.name == selectedRoom and 255 or 100, button.name == selectedRoom and 255 or 100, button.name == selectedRoom and 255 or 100, 255)
|
love.graphics.setColor(button.name == selectedRoom and 1 or 100 / 255, button.name == selectedRoom and 1 or 100 / 255, button.name == selectedRoom and 1 or 100 / 255, 1)
|
||||||
love.graphics.rectangle("line", button.x, button.y, button.w, button.h)
|
love.graphics.rectangle("line", button.x, button.y, button.w, button.h)
|
||||||
gameWorld:drawRoomPreview(button.name, button.x + 4, button.y + 4, button.h - 8)
|
gameWorld:drawRoomPreview(button.name, button.x + 4, button.y + 4, button.h - 8)
|
||||||
love.graphics.setColor(gColor.white:set())
|
love.graphics.setColor(gColor.white:set())
|
||||||
love.graphics.printf(button.name, button.x + button.h + 4, button.y + 21, button.w - button.h - 8, "left")
|
love.graphics.printf(button.name, button.x + button.h + 4, button.y + 21, button.w - button.h - 8, "left")
|
||||||
end
|
end
|
||||||
for _, control in ipairs(worldControls) do
|
for _, control in ipairs(worldControls) do
|
||||||
love.graphics.setColor(255, 255, 255, 255)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
love.graphics.rectangle("line", control.x, control.y, control.w, control.h)
|
love.graphics.rectangle("line", control.x, control.y, control.w, control.h)
|
||||||
love.graphics.printf(control.text, control.x, control.y + 9, control.w, "center")
|
love.graphics.printf(control.text, control.x, control.y + 9, control.w, "center")
|
||||||
end
|
end
|
||||||
|
|
@ -129,7 +182,8 @@ local function saveRoomFromEditor()
|
||||||
return
|
return
|
||||||
end
|
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 .. "'."
|
editorStatus = "Could not save '" .. name .. "'."
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -146,20 +200,28 @@ local function saveRoomFromEditor()
|
||||||
editorRoomName = name
|
editorRoomName = name
|
||||||
roomNameField:editText(name)
|
roomNameField:editText(name)
|
||||||
activeField = nil
|
activeField = nil
|
||||||
editorStatus = "Saved source level '" .. name .. ".'"
|
editorStatus = "Saved source level '" .. name .. "'" .. (savePlayerPositions and " with player positions." or ".")
|
||||||
end
|
end
|
||||||
|
|
||||||
local function clearRuntimeSaves()
|
local function clearRuntimeSaves()
|
||||||
local now = love.timer.getTime()
|
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
|
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
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local removed = clearRuntimeSaveData()
|
local removed = clearRuntimeSaveData(clearAll and nil or roomName)
|
||||||
clearSaveConfirmationExpires = 0
|
clearSaveConfirmationExpires = 0
|
||||||
local roomName = currentRoom.name
|
clearSaveConfirmationScope = nil
|
||||||
if roomName then
|
if roomName then
|
||||||
currentRoom = Room:new{ name = roomName }
|
currentRoom = Room:new{ name = roomName }
|
||||||
setEditorRoomName(currentRoom.name)
|
setEditorRoomName(currentRoom.name)
|
||||||
|
|
@ -184,7 +246,10 @@ function _initLevelEditor()
|
||||||
table.insert(hierarchy[gameAssets[asset].directory], asset)
|
table.insert(hierarchy[gameAssets[asset].directory], asset)
|
||||||
end
|
end
|
||||||
|
|
||||||
for folder, object in pairs(hierarchy) do
|
local folders = {}
|
||||||
|
for folder in pairs(hierarchy) do table.insert(folders, folder) end
|
||||||
|
table.sort(folders)
|
||||||
|
for _, folder in ipairs(folders) do
|
||||||
local button = createUIElement{
|
local button = createUIElement{
|
||||||
name = "folder",
|
name = "folder",
|
||||||
text = folder,
|
text = folder,
|
||||||
|
|
@ -310,7 +375,8 @@ function selectFolder(folder)
|
||||||
local counter = 1
|
local counter = 1
|
||||||
local limit = 10
|
local limit = 10
|
||||||
|
|
||||||
for _, asset in pairs(hierarchy[selectedFolder]) do
|
table.sort(hierarchy[selectedFolder])
|
||||||
|
for _, asset in ipairs(hierarchy[selectedFolder]) do
|
||||||
if counter % limit == 0 then
|
if counter % limit == 0 then
|
||||||
counter = 1
|
counter = 1
|
||||||
|
|
||||||
|
|
@ -328,7 +394,7 @@ function selectFolder(folder)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
table.insert(assetButtons[row], forwardbutton)
|
table.insert(assetButtons[row], forwardButton)
|
||||||
|
|
||||||
row = row + 1
|
row = row + 1
|
||||||
table.insert(assetButtons, {})
|
table.insert(assetButtons, {})
|
||||||
|
|
@ -521,6 +587,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:setLastRoom(currentRoom.name)
|
||||||
setEditorRoomName(currentRoom.name)
|
setEditorRoomName(currentRoom.name)
|
||||||
assetsView()
|
assetsView()
|
||||||
end
|
end
|
||||||
|
|
@ -531,7 +598,17 @@ function editorMouseHandler(x, y, button)
|
||||||
x = x - width
|
x = x - width
|
||||||
|
|
||||||
if editorView == "room" then
|
if editorView == "room" then
|
||||||
if not selectedFolder then
|
if buttonContains({ x = 10, y = 5, w = sideBarBox.w - 20, h = 28 }, x, y) then
|
||||||
|
beginSearch("assetSearch")
|
||||||
|
return
|
||||||
|
elseif assetSearchText ~= "" then
|
||||||
|
for _, uiElement in ipairs(assetSearchButtons) do
|
||||||
|
if uiElement:clicIsInBox(x, y) then
|
||||||
|
uiElement:onClic(button)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif not selectedFolder then
|
||||||
for _, uiElement in pairs(folderButtons) do
|
for _, uiElement in pairs(folderButtons) do
|
||||||
if uiElement:clicIsInBox(x, y) then
|
if uiElement:clicIsInBox(x, y) then
|
||||||
uiElement:onClic(button)
|
uiElement:onClic(button)
|
||||||
|
|
@ -559,6 +636,10 @@ function editorMouseHandler(x, y, button)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif editorView == "world" then
|
elseif editorView == "world" then
|
||||||
|
if buttonContains(worldSearchBox, x, y) then
|
||||||
|
beginSearch("worldSearch")
|
||||||
|
return
|
||||||
|
end
|
||||||
for _, roomButton in ipairs(worldRoomButtons) do
|
for _, roomButton in ipairs(worldRoomButtons) do
|
||||||
if buttonContains(roomButton, x, y) then
|
if buttonContains(roomButton, x, y) then
|
||||||
if selectedRoom == roomButton.name then
|
if selectedRoom == roomButton.name then
|
||||||
|
|
@ -573,7 +654,7 @@ function editorMouseHandler(x, y, button)
|
||||||
end
|
end
|
||||||
for _, control in ipairs(worldControls) do
|
for _, control in ipairs(worldControls) do
|
||||||
if buttonContains(control, x, y) then
|
if buttonContains(control, x, y) then
|
||||||
control:onClic(x)
|
control.onClic(x)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -606,6 +687,15 @@ function editorTextHandler(text)
|
||||||
elseif editorView == "room" and activeField == "roomName" then
|
elseif editorView == "room" and activeField == "roomName" then
|
||||||
inputText = text
|
inputText = text
|
||||||
roomNameField:editText(text)
|
roomNameField:editText(text)
|
||||||
|
elseif editorView == "room" and activeField == "assetSearch" then
|
||||||
|
inputText = text
|
||||||
|
assetSearchText = text
|
||||||
|
rebuildAssetSearchButtons()
|
||||||
|
elseif editorView == "world" and activeField == "worldSearch" then
|
||||||
|
inputText = text
|
||||||
|
worldSearchText = text
|
||||||
|
worldPage = 1
|
||||||
|
rebuildWorldRoomButtons()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -614,6 +704,10 @@ function editorCompleteResponse()
|
||||||
assetProperties[activeField] = inputText
|
assetProperties[activeField] = inputText
|
||||||
elseif editorView == "room" and activeField == "roomName" then
|
elseif editorView == "room" and activeField == "roomName" then
|
||||||
editorRoomName = inputText
|
editorRoomName = inputText
|
||||||
|
elseif editorView == "room" and activeField == "assetSearch" then
|
||||||
|
assetSearchText = inputText
|
||||||
|
elseif editorView == "world" and activeField == "worldSearch" then
|
||||||
|
worldSearchText = inputText
|
||||||
end
|
end
|
||||||
activeField = nil
|
activeField = nil
|
||||||
end
|
end
|
||||||
|
|
@ -628,12 +722,17 @@ end
|
||||||
|
|
||||||
function editorDraw()
|
function editorDraw()
|
||||||
if editorView == "room" then
|
if editorView == "room" then
|
||||||
|
love.graphics.setColor(gColor.white:set())
|
||||||
|
love.graphics.rectangle("line", 10, 5, sideBarBox.w - 20, 28)
|
||||||
|
love.graphics.printf("ASSET SEARCH: " .. assetSearchText, 16, 12, sideBarBox.w - 32, "left")
|
||||||
love.graphics.setColor(gColor.white:set())
|
love.graphics.setColor(gColor.white:set())
|
||||||
love.graphics.printf("LEVEL NAME", 0, sideBarBox.h * (7 / 10) - 24, sideBarBox.w, "center")
|
love.graphics.printf("LEVEL NAME", 0, sideBarBox.h * (7 / 10) - 24, sideBarBox.w, "center")
|
||||||
for _, button in pairs(uiButtons) do
|
for _, button in pairs(uiButtons) do
|
||||||
button:draw()
|
button:draw()
|
||||||
end
|
end
|
||||||
if not selectedFolder then
|
if assetSearchText ~= "" then
|
||||||
|
for _, asset in ipairs(assetSearchButtons) do asset:draw() end
|
||||||
|
elseif not selectedFolder then
|
||||||
for _, folder in pairs(folderButtons) do
|
for _, folder in pairs(folderButtons) do
|
||||||
folder:draw()
|
folder:draw()
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
GUI = class("level_editor/gui")
|
GUI = class("level_editor/gui")
|
||||||
|
|
||||||
GUI.static.pressSound = love.audio.newSource("sounds/ui/button.wav")
|
GUI.static.pressSound = love.audio.newSource("sounds/ui/button.wav", "static")
|
||||||
|
|
||||||
GUI.static.defaultCallbac = function() print("this button has not been configured") end
|
GUI.static.defaultCallbac = function() print("this button has not been configured") end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ function getAllAssets()
|
||||||
-- class comes from a <name>.meta sitting next to the art
|
-- class comes from a <name>.meta sitting next to the art
|
||||||
-- (authoritative); default to immoveable if none exists
|
-- (authoritative); default to immoveable if none exists
|
||||||
local metaPath = topFolder .. "/" .. folder .. "/" .. name .. ".meta"
|
local metaPath = topFolder .. "/" .. folder .. "/" .. name .. ".meta"
|
||||||
if love.filesystem.exists(metaPath) then
|
if love.filesystem.getInfo(metaPath, "file") then
|
||||||
globalAssetProperties[name].class = TSerial.unpack(love.filesystem.read(metaPath)).class
|
globalAssetProperties[name].class = TSerial.unpack(love.filesystem.read(metaPath)).class
|
||||||
elseif not globalAssetProperties[name].class then
|
elseif not globalAssetProperties[name].class then
|
||||||
globalAssetProperties[name].class = "immoveable"
|
globalAssetProperties[name].class = "immoveable"
|
||||||
|
|
@ -45,7 +45,7 @@ function getAllAssets()
|
||||||
|
|
||||||
if not globalAssetProperties[name].cells or reanalyzeCells then
|
if not globalAssetProperties[name].cells or reanalyzeCells then
|
||||||
globalAssetProperties[name].cells = {}
|
globalAssetProperties[name].cells = {}
|
||||||
globalAssetProperties[name].cells[color] = getCellsFromSprite(love.graphics.newImage(sprite))
|
globalAssetProperties[name].cells[color] = getCellsFromSprite(sprite)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
1
libs/narrator
Submodule
1
libs/narrator
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 45a8b03553f2385716c24ec762757e418b2b73c1
|
||||||
85
main.lua
85
main.lua
|
|
@ -15,6 +15,12 @@ require "world_seed"
|
||||||
require "shaders"
|
require "shaders"
|
||||||
require "level_editor/editor"
|
require "level_editor/editor"
|
||||||
|
|
||||||
|
-- Ink dialogue: ship the pre-parsed book (LÖVE has no lpeg to parse .ink at
|
||||||
|
-- runtime; the narrator parser degrades to false without it, runtime is fine).
|
||||||
|
package.path = package.path .. ";libs/narrator/?.lua"
|
||||||
|
narrator = require "narrator.narrator"
|
||||||
|
startSignBook = require "stories.start_sign"
|
||||||
|
|
||||||
function love.load()
|
function love.load()
|
||||||
globalAssetProperties = {} -- rebuilt from the filesystem (art/ + .meta) by getAllAssets
|
globalAssetProperties = {} -- rebuilt from the filesystem (art/ + .meta) by getAllAssets
|
||||||
|
|
||||||
|
|
@ -25,23 +31,39 @@ function love.load()
|
||||||
gameWorld = World:new()
|
gameWorld = World:new()
|
||||||
_initLevelEditor()
|
_initLevelEditor()
|
||||||
|
|
||||||
gameCanvas = love.graphics.newCanvas()
|
_initCanvases()
|
||||||
sideBarCanvas = love.graphics.newCanvas(sideBarBox.w, sideBarBox.h)
|
|
||||||
finalCanvas = love.graphics.newCanvas()
|
|
||||||
|
|
||||||
|
|
||||||
inputTimer = Timer.new()
|
inputTimer = Timer.new()
|
||||||
|
|
||||||
|
-- Resume the game: prefer the runtime autosave so the last saved position is
|
||||||
|
-- restored, falling back to the authored source level. Editor room-opens go
|
||||||
|
-- through their own (source-only) load path, so this only affects resume.
|
||||||
currentRoom = Room: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",
|
||||||
|
runtime = true
|
||||||
}
|
}
|
||||||
|
gameWorld:setLastRoom(currentRoom.name)
|
||||||
setEditorRoomName(currentRoom.name)
|
setEditorRoomName(currentRoom.name)
|
||||||
|
|
||||||
response = ""
|
response = ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Autosave on close so a position reached by moving within a room (short of a
|
||||||
|
-- room-to-room transition, which already autosaves in nextRoom) survives.
|
||||||
|
function love.quit()
|
||||||
|
if currentRoom then
|
||||||
|
currentRoom:save()
|
||||||
|
if gameWorld then gameWorld:setLastRoom(currentRoom.name) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function love.update(dt)
|
function love.update(dt)
|
||||||
Input:handler(dt)
|
if editorView == "world" then
|
||||||
|
Input:stopMoveRepeat()
|
||||||
|
else
|
||||||
|
local repeatedMove = Input:handler(dt)
|
||||||
|
if repeatedMove and not Dialogue.isOpen() then currentRoom:movePlayer(repeatedMove) end
|
||||||
|
end
|
||||||
currentRoom:update(dt)
|
currentRoom:update(dt)
|
||||||
|
|
||||||
-- if love.keyboard.isDown("w") then worldSha = worldSha + 1 end
|
-- if love.keyboard.isDown("w") then worldSha = worldSha + 1 end
|
||||||
|
|
@ -67,7 +89,7 @@ function love.draw()
|
||||||
love.graphics.push()
|
love.graphics.push()
|
||||||
|
|
||||||
for i = 1, samples do
|
for i = 1, samples do
|
||||||
local c = 65 / (i * 1.75)
|
local c = 65 / 255 / (i * 1.75)
|
||||||
love.graphics.setColor(c, c, c)
|
love.graphics.setColor(c, c, c)
|
||||||
love.graphics.scale(scaleCoefficient, scaleCoefficient)
|
love.graphics.scale(scaleCoefficient, scaleCoefficient)
|
||||||
love.graphics.translate((width / 2 - (width * scaleCoefficient) / 2) + 2, (width / 2 - (height * scaleCoefficient) / 2) + 2)
|
love.graphics.translate((width / 2 - (width * scaleCoefficient) / 2) + 2, (width / 2 - (height * scaleCoefficient) / 2) + 2)
|
||||||
|
|
@ -75,7 +97,7 @@ function love.draw()
|
||||||
end
|
end
|
||||||
|
|
||||||
love.graphics.pop()
|
love.graphics.pop()
|
||||||
love.graphics.setColor(255, 255, 255)
|
love.graphics.setColor(1, 1, 1)
|
||||||
love.graphics.draw(gameCanvas, 0, 0)
|
love.graphics.draw(gameCanvas, 0, 0)
|
||||||
love.graphics.setCanvas()
|
love.graphics.setCanvas()
|
||||||
|
|
||||||
|
|
@ -85,15 +107,23 @@ function love.draw()
|
||||||
love.graphics.setCanvas()
|
love.graphics.setCanvas()
|
||||||
|
|
||||||
love.graphics.setBlendMode("alpha")
|
love.graphics.setBlendMode("alpha")
|
||||||
love.graphics.setColor(255, 255, 255, 255)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
love.graphics.draw(finalCanvas, 0, 0)
|
love.graphics.draw(finalCanvas, 0, 0)
|
||||||
love.graphics.draw(sideBarCanvas, sideBarBox.x, sideBarBox.y)
|
love.graphics.draw(sideBarCanvas, sideBarBox.x, sideBarBox.y)
|
||||||
love.graphics.print("Current FPS: "..tostring(love.timer.getFPS( )), 10, 10)
|
love.graphics.print("Current FPS: "..tostring(love.timer.getFPS( )), 10, 10)
|
||||||
|
|
||||||
|
Dialogue.draw()
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.keypressed(key)
|
function love.keypressed(key)
|
||||||
|
-- Talking to a sign captures input: navigate/choose, don't move the player.
|
||||||
|
if Dialogue.isOpen() then
|
||||||
|
Dialogue.keypressed(key)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if editorView ~= "world" and Input.isPlayerMove(key) then
|
if editorView ~= "world" and Input.isPlayerMove(key) then
|
||||||
|
Input:beginMove(key)
|
||||||
currentRoom:movePlayer(key)
|
currentRoom:movePlayer(key)
|
||||||
elseif editorView ~= "world" and Input.isPlayerSwitch(key) then
|
elseif editorView ~= "world" and Input.isPlayerSwitch(key) then
|
||||||
local c = Input.playerColor(key)
|
local c = Input.playerColor(key)
|
||||||
|
|
@ -134,12 +164,32 @@ function love.textinput(t)
|
||||||
editorTextHandler(response)
|
editorTextHandler(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function love.resize(w, h)
|
||||||
|
_computeDimensions()
|
||||||
|
drawScale = width / gridWidth / 16
|
||||||
|
_initCanvases()
|
||||||
|
end
|
||||||
|
|
||||||
function _initDefaults()
|
function _initDefaults()
|
||||||
--set filter
|
--set filter
|
||||||
love.graphics.setDefaultFilter("linear", "nearest")
|
love.graphics.setDefaultFilter("linear", "nearest")
|
||||||
love.filesystem.createDirectory("rooms")
|
love.filesystem.createDirectory("rooms")
|
||||||
|
|
||||||
--set global width / height variables
|
--set global width / height / sidebar variables
|
||||||
|
_computeDimensions()
|
||||||
|
|
||||||
|
--set randomseed
|
||||||
|
math.randomseed(os.time())
|
||||||
|
|
||||||
|
--font
|
||||||
|
font = love.graphics.newFont("font/LCD_Solid.ttf", 16)
|
||||||
|
love.graphics.setFont(font)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Derive the square play area (side = smaller window dimension) and the
|
||||||
|
-- sidebar that fills the remaining horizontal space. Safe to call any time
|
||||||
|
-- the window size changes.
|
||||||
|
function _computeDimensions()
|
||||||
width = love.graphics.getWidth()
|
width = love.graphics.getWidth()
|
||||||
height = love.graphics.getHeight()
|
height = love.graphics.getHeight()
|
||||||
if width > height then
|
if width > height then
|
||||||
|
|
@ -148,17 +198,16 @@ function _initDefaults()
|
||||||
height = width
|
height = width
|
||||||
end
|
end
|
||||||
|
|
||||||
--set randomseed
|
|
||||||
math.randomseed(os.time())
|
|
||||||
|
|
||||||
--font
|
|
||||||
font = love.graphics.newFont("font/LCD_Solid.ttf", 16)
|
|
||||||
love.graphics.setFont(font)
|
|
||||||
|
|
||||||
--sidebar
|
|
||||||
sideBarBox = {x = width, y = 0, w = love.graphics.getWidth() - width, h = height}
|
sideBarBox = {x = width, y = 0, w = love.graphics.getWidth() - width, h = height}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- (Re)create the render targets at the current window size.
|
||||||
|
function _initCanvases()
|
||||||
|
gameCanvas = love.graphics.newCanvas()
|
||||||
|
finalCanvas = love.graphics.newCanvas()
|
||||||
|
sideBarCanvas = love.graphics.newCanvas(math.max(1, sideBarBox.w), math.max(1, sideBarBox.h))
|
||||||
|
end
|
||||||
|
|
||||||
function _initGridVars()
|
function _initGridVars()
|
||||||
gridWidth, gridHeight = 11, 11
|
gridWidth, gridHeight = 11, 11
|
||||||
drawScale = width / gridWidth / 16
|
drawScale = width / gridWidth / 16
|
||||||
|
|
|
||||||
26
npc.lua
26
npc.lua
|
|
@ -1,14 +1,22 @@
|
||||||
npc = class("npc", Entity)
|
require "entity"
|
||||||
|
require "dialogable"
|
||||||
|
|
||||||
function npc:initialize(x, y, color, name)
|
-- A dialogable placed in the world. Signs, npcs and artifacts will each be a
|
||||||
|
-- small Entity subclass that includes Dialogable and names its own book; Sign is
|
||||||
|
-- the first. It just picks art + a story — the talking lives in the mixin.
|
||||||
|
Sign = class("Sign", Entity)
|
||||||
|
Sign:include(Dialogable)
|
||||||
|
|
||||||
|
function Sign:initialize(x, y)
|
||||||
|
self.book = startSignBook
|
||||||
Entity.initialize(self, {
|
Entity.initialize(self, {
|
||||||
x = x,
|
x = x, y = y,
|
||||||
y = y,
|
color = "red", -- representative only; sprites are per-channel
|
||||||
color = color,
|
|
||||||
collision = "unmoveable",
|
collision = "unmoveable",
|
||||||
sprite = sprites.npc[name][color],
|
sprites = {
|
||||||
onBump = function(self)
|
red = loadSprite("art/npc/start_sign_r.png"),
|
||||||
|
green = loadSprite("art/npc/start_sign_g.png"),
|
||||||
end,
|
blue = loadSprite("art/npc/start_sign_b.png"),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
@ -3,8 +3,8 @@ require "entity"
|
||||||
Player = class("Player", Entity)
|
Player = class("Player", Entity)
|
||||||
|
|
||||||
Player.static.sounds = {
|
Player.static.sounds = {
|
||||||
switchFail = love.audio.newSource("sounds/switch_fail.wav"),
|
switchFail = love.audio.newSource("sounds/switch_fail.wav", "static"),
|
||||||
switchSuccess = love.audio.newSource("sounds/switch_success.wav")
|
switchSuccess = love.audio.newSource("sounds/switch_success.wav", "static")
|
||||||
}
|
}
|
||||||
|
|
||||||
function Player:initialize(x, y, color)
|
function Player:initialize(x, y, color)
|
||||||
|
|
|
||||||
107
room.lua
107
room.lua
|
|
@ -27,6 +27,10 @@ function Room:initialize(t)
|
||||||
print(t.name)
|
print(t.name)
|
||||||
|
|
||||||
if t.player then
|
if t.player then
|
||||||
|
-- The carried colors are the sole source of truth for what's active.
|
||||||
|
-- Copies that live in this room (loaded from the sav) start INACTIVE —
|
||||||
|
-- you must walk onto one to merge it; it never joins you automatically.
|
||||||
|
self.activeColors = {red = false, green = false, blue = false}
|
||||||
-- entering from another room: place the carried colors at the entry point
|
-- entering from another room: place the carried colors at the entry point
|
||||||
for _, color in ipairs(t.player.activeColors) do
|
for _, color in ipairs(t.player.activeColors) do
|
||||||
if self.player[color] then
|
if self.player[color] then
|
||||||
|
|
@ -36,11 +40,6 @@ function Room:initialize(t)
|
||||||
end
|
end
|
||||||
self:setActiveColor(color, true)
|
self:setActiveColor(color, true)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
self.name = t.name
|
self.name = t.name
|
||||||
|
|
@ -102,6 +101,11 @@ function Room:initialize(t)
|
||||||
self.collidableMatrices = {} --{red = {{...}{...}{...}}, ...
|
self.collidableMatrices = {} --{red = {{...}{...}{...}}, ...
|
||||||
self:createSwitchMatrix()
|
self:createSwitchMatrix()
|
||||||
self:createCollideablesMatrix()
|
self:createCollideablesMatrix()
|
||||||
|
|
||||||
|
-- Resolve switches/doors up front so the very first frame renders correct
|
||||||
|
-- lock state: a block already sitting on a switch presses it before anything
|
||||||
|
-- is drawn, instead of waiting for the first move's tick.
|
||||||
|
self:resolveSwitchesAndDoors()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -116,19 +120,11 @@ function Room:draw()
|
||||||
-- draw only the channels the player has. Each object just exposes sprites per
|
-- draw only the channels the player has. Each object just exposes sprites per
|
||||||
-- channel via Entity:draw(channel); a door registered on all three channels
|
-- channel via Entity:draw(channel); a door registered on all three channels
|
||||||
-- naturally draws its red sprite on the red pass, etc. No per-object logic.
|
-- naturally draws its red sprite on the red pass, etc. No per-object logic.
|
||||||
local has = {}
|
|
||||||
for _, channel in ipairs(self.colorsPlayerHas) do
|
for _, channel in ipairs(self.colorsPlayerHas) do
|
||||||
has[channel] = true
|
|
||||||
if self.player[channel] then self.player[channel]:draw(channel) end
|
if self.player[channel] then self.player[channel]:draw(channel) end
|
||||||
for _, entity in pairs(self.collideables[channel]) do entity:draw(channel) end
|
for _, entity in pairs(self.collideables[channel]) do entity:draw(channel) end
|
||||||
for _, switch in pairs(self.switches[channel]) do switch:draw(channel) end
|
for _, switch in pairs(self.switches[channel]) do switch:draw(channel) end
|
||||||
end
|
for _, npc in ipairs(self.npcs) do npc:draw(channel) end
|
||||||
|
|
||||||
-- npcs (old "sages") are markers for now: a "?" tinted to the player's channels
|
|
||||||
local cellW, cellH = width / gridWidth, height / gridHeight
|
|
||||||
love.graphics.setColor(has.red and 255 or 0, has.green and 255 or 0, has.blue and 255 or 0)
|
|
||||||
for _, npc in ipairs(self.npcs) do
|
|
||||||
love.graphics.print("?", (npc.x - 1) * cellW + cellW * 0.3, (npc.y - 1) * cellH + cellH * 0.1, 0, 2, 2)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -136,6 +132,14 @@ function Room:createCollideablesMatrix()
|
||||||
for color, entities in pairs(self.collideables) do
|
for color, entities in pairs(self.collideables) do
|
||||||
self.collidableMatrices[color] = self:createEntityMatrix(entities)
|
self.collidableMatrices[color] = self:createEntityMatrix(entities)
|
||||||
end
|
end
|
||||||
|
-- Dialogables (npcs) block every channel, so stamp them into each matrix.
|
||||||
|
for _, npc in ipairs(self.npcs) do
|
||||||
|
for _, cell in ipairs(npc:getOccupiedCells()) do
|
||||||
|
for color in pairs(self.collidableMatrices) do
|
||||||
|
self.collidableMatrices[color][cell.y][cell.x] = npc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Room:createSwitchMatrix()
|
function Room:createSwitchMatrix()
|
||||||
|
|
@ -419,11 +423,17 @@ function Room:tick()
|
||||||
end
|
end
|
||||||
self:createCollideablesMatrix()
|
self:createCollideablesMatrix()
|
||||||
self:createSwitchMatrix()
|
self:createSwitchMatrix()
|
||||||
|
self:resolveSwitchesAndDoors()
|
||||||
|
self:nextRoomCheck()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Run the switch press check and push the result to every door. Assumes the
|
||||||
|
-- collidable/switch matrices are already current.
|
||||||
|
function Room:resolveSwitchesAndDoors()
|
||||||
local setDoorColors = self:switchCheck()
|
local setDoorColors = self:switchCheck()
|
||||||
for _, door in pairs(self.doors) do
|
for _, door in pairs(self.doors) do
|
||||||
door:setColors(setDoorColors)
|
door:setColors(setDoorColors)
|
||||||
end
|
end
|
||||||
self:nextRoomCheck()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Room:playerJoinCheck(previousPositions, newPositions)
|
function Room:playerJoinCheck(previousPositions, newPositions)
|
||||||
|
|
@ -489,7 +499,10 @@ function Room:switchCheck()
|
||||||
local onSwitches = {}
|
local onSwitches = {}
|
||||||
for _, switch in pairs(switchArray) do
|
for _, switch in pairs(switchArray) do
|
||||||
local targetCell = self:getCellInMatrix(self.collidableMatrices[color], switch:getGridPos())
|
local targetCell = self:getCellInMatrix(self.collidableMatrices[color], switch:getGridPos())
|
||||||
if targetCell then
|
local player = self.player[color]
|
||||||
|
-- Players are not stored in collidableMatrices, but a colour copy
|
||||||
|
-- standing on its matching switch presses it just like a block does.
|
||||||
|
if targetCell or (player and player:occupiesCell(switch:getGridPos())) then
|
||||||
--switch is on
|
--switch is on
|
||||||
switch:activate()
|
switch:activate()
|
||||||
table.insert(onSwitches, switch) --doesn't rly matter what we put here as long as it's true-y
|
table.insert(onSwitches, switch) --doesn't rly matter what we put here as long as it's true-y
|
||||||
|
|
@ -528,8 +541,9 @@ end
|
||||||
-- keyword handled directly since it has no art.
|
-- keyword handled directly since it has no art.
|
||||||
function Room:createEntity(class, color, x, y)
|
function Room:createEntity(class, color, x, y)
|
||||||
if class == "npc" then
|
if class == "npc" then
|
||||||
self:registerNpc(x, y)
|
local entity = Sign:new(x, y)
|
||||||
return
|
self:registerNpc(entity)
|
||||||
|
return entity
|
||||||
end
|
end
|
||||||
|
|
||||||
local props = globalAssetProperties and globalAssetProperties[class]
|
local props = globalAssetProperties and globalAssetProperties[class]
|
||||||
|
|
@ -589,8 +603,11 @@ function Room:registerPlayer(entityPointer, color)
|
||||||
self.player[color] = entityPointer
|
self.player[color] = entityPointer
|
||||||
end
|
end
|
||||||
|
|
||||||
function Room:registerNpc(x, y)
|
function Room:registerNpc(entity)
|
||||||
table.insert(self.npcs, { x = x, y = y })
|
-- npcs are dialogable entities: they carry .x/.y like any Entity, so
|
||||||
|
-- serialize's npc loop still round-trips them, and they get stamped into the
|
||||||
|
-- collision matrix (see createCollideablesMatrix) so a bump is a rejected move.
|
||||||
|
table.insert(self.npcs, entity)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Room:getCellFromMousePos(mouseX, mouseY)
|
function Room:getCellFromMousePos(mouseX, mouseY)
|
||||||
|
|
@ -685,6 +702,12 @@ end
|
||||||
function Room:deserialize(saveTable)
|
function Room:deserialize(saveTable)
|
||||||
for _, obj in ipairs(saveTable.objects or {}) do
|
for _, obj in ipairs(saveTable.objects or {}) do
|
||||||
self:createEntity(obj.class, obj.color, obj.x, obj.y)
|
self:createEntity(obj.class, obj.color, obj.x, obj.y)
|
||||||
|
-- createEntity turns every loaded player on; honour a saved active flag so
|
||||||
|
-- copies that were off at save time stay off (older savs lack the field and
|
||||||
|
-- keep the old all-on behaviour).
|
||||||
|
if obj.class == "player" and obj.active ~= nil then
|
||||||
|
self:setActiveColor(obj.color, obj.active)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -699,13 +722,37 @@ end
|
||||||
-- This is intentionally separate from Room:save. It is the editor's explicit
|
-- This is intentionally separate from Room:save. It is the editor's explicit
|
||||||
-- "write this level into the project" action; gameplay autosaves must never
|
-- "write this level into the project" action; gameplay autosaves must never
|
||||||
-- call it.
|
-- call it.
|
||||||
function Room:saveToSource(name)
|
function Room:saveToSource(name, savePlayerPositions, playerSourceName)
|
||||||
name = name or self.name or "test"
|
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)
|
return writeToSource("rooms/" .. name .. ".sav", room)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Room:serialize()
|
function Room:serialize(includePlayers)
|
||||||
local objects = {}
|
local objects = {}
|
||||||
|
|
||||||
local function emit(class, color, entity)
|
local function emit(class, color, entity)
|
||||||
|
|
@ -713,8 +760,17 @@ function Room:serialize()
|
||||||
table.insert(objects, { class = class, color = color, x = pos.x, y = pos.y })
|
table.insert(objects, { class = class, color = color, x = pos.x, y = pos.y })
|
||||||
end
|
end
|
||||||
|
|
||||||
for color, player in pairs(self.player) do
|
if includePlayers ~= false then
|
||||||
emit("player", color, player)
|
for color, player in pairs(self.player) do
|
||||||
|
local pos = player:getGridPos()
|
||||||
|
-- Persist which copies were active so a resume restores exactly the
|
||||||
|
-- channels that were on at save time. A colour deactivated during play
|
||||||
|
-- (see playerJoinCheck) must come back as an inactive copy, not turn on.
|
||||||
|
table.insert(objects, {
|
||||||
|
class = "player", color = color, x = pos.x, y = pos.y,
|
||||||
|
active = self.activeColors[color] and true or false,
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for color, collideableArray in pairs(self.collideables) do
|
for color, collideableArray in pairs(self.collideables) do
|
||||||
|
|
@ -766,6 +822,7 @@ function nextRoom(pos)
|
||||||
x = newPos.x, y = newPos.y, activeColors = currentRoom:getActiveColors()
|
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
|
if currentRoom.name then setEditorRoomName(currentRoom.name) end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
76
rooms/jorge_room.sav
Normal file
76
rooms/jorge_room.sav
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
objects={
|
||||||
|
{
|
||||||
|
color="red",
|
||||||
|
y=4,
|
||||||
|
x=8,
|
||||||
|
class="toilet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color="red",
|
||||||
|
y=4,
|
||||||
|
x=6,
|
||||||
|
class="toilet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color="red",
|
||||||
|
y=4,
|
||||||
|
x=4,
|
||||||
|
class="toilet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color="green",
|
||||||
|
y=4,
|
||||||
|
x=8,
|
||||||
|
class="toilet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color="green",
|
||||||
|
y=4,
|
||||||
|
x=6,
|
||||||
|
class="toilet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color="green",
|
||||||
|
y=4,
|
||||||
|
x=4,
|
||||||
|
class="toilet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color="blue",
|
||||||
|
y=4,
|
||||||
|
x=8,
|
||||||
|
class="toilet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color="blue",
|
||||||
|
y=4,
|
||||||
|
x=6,
|
||||||
|
class="toilet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color="blue",
|
||||||
|
y=4,
|
||||||
|
x=4,
|
||||||
|
class="toilet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color="red",
|
||||||
|
y=8,
|
||||||
|
x=6,
|
||||||
|
class="player"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color="green",
|
||||||
|
y=8,
|
||||||
|
x=6,
|
||||||
|
class="player"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color="blue",
|
||||||
|
y=8,
|
||||||
|
x=6,
|
||||||
|
class="player"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
rooms/room_11_09.sav
Normal file
4
rooms/room_11_09.sav
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
objects={
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,538 +1,538 @@
|
||||||
{
|
{
|
||||||
objects={
|
objects={
|
||||||
{
|
{
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=1,
|
|
||||||
y=1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=1,
|
||||||
x=1,
|
x=1
|
||||||
y=1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=1,
|
|
||||||
y=1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=1,
|
||||||
x=11,
|
x=11
|
||||||
y=1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=11,
|
|
||||||
y=1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=2,
|
||||||
x=1,
|
x=1
|
||||||
y=2
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=1,
|
|
||||||
y=2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=1,
|
|
||||||
y=2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=2,
|
||||||
x=11,
|
x=11
|
||||||
y=2
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
class="static_wall",
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=3,
|
||||||
x=1,
|
x=1
|
||||||
y=3
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=1,
|
|
||||||
y=3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=1,
|
|
||||||
y=3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=3,
|
||||||
x=11,
|
x=11
|
||||||
y=3
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=11,
|
|
||||||
y=3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=1,
|
|
||||||
y=4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=4,
|
||||||
x=1,
|
x=1
|
||||||
y=4
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=1,
|
|
||||||
y=4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=4,
|
||||||
x=11,
|
x=11
|
||||||
y=4
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=11,
|
|
||||||
y=4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=5,
|
||||||
x=1,
|
x=1
|
||||||
y=5
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=1,
|
|
||||||
y=5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=1,
|
|
||||||
y=5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=1,
|
|
||||||
y=6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=1,
|
|
||||||
y=6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=6,
|
||||||
x=1,
|
x=1
|
||||||
y=6
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=11,
|
|
||||||
y=6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=6,
|
||||||
x=11,
|
x=11
|
||||||
y=6
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=1,
|
|
||||||
y=7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=1,
|
|
||||||
y=7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=7,
|
||||||
x=1,
|
x=1
|
||||||
y=7
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=11,
|
|
||||||
y=7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=7,
|
||||||
x=11,
|
x=11
|
||||||
y=7
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=1,
|
|
||||||
y=8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=8,
|
||||||
x=1,
|
x=1
|
||||||
y=8
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=1,
|
|
||||||
y=8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=8,
|
||||||
x=11,
|
x=11
|
||||||
y=8
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=1,
|
|
||||||
y=9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=9,
|
||||||
x=1,
|
x=1
|
||||||
y=9
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=1,
|
|
||||||
y=9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=9,
|
||||||
x=11,
|
x=11
|
||||||
y=9
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=11,
|
|
||||||
y=9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=1,
|
|
||||||
y=10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=10,
|
||||||
x=1,
|
x=1
|
||||||
y=10
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=1,
|
|
||||||
y=10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=11,
|
|
||||||
y=10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=10,
|
||||||
x=11,
|
x=11
|
||||||
y=10
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=1,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=1,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=11,
|
||||||
x=1,
|
x=1
|
||||||
y=11
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=2,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=11,
|
||||||
x=2,
|
x=2
|
||||||
y=11
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=2,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=11,
|
||||||
x=3,
|
x=3
|
||||||
y=11
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=3,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=3,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=4,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=4,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=11,
|
||||||
x=4,
|
x=4
|
||||||
y=11
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
class="static_wall",
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=11,
|
||||||
x=5,
|
x=5
|
||||||
y=11
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=5,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=5,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=7,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=11,
|
||||||
x=7,
|
x=7
|
||||||
y=11
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=7,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=11,
|
||||||
x=8,
|
x=8
|
||||||
y=11
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=8,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=8,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=11,
|
||||||
x=9,
|
x=9
|
||||||
y=11
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=9,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=9,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
|
||||||
class="static_wall",
|
|
||||||
x=10,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
|
||||||
x=10,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=11,
|
||||||
x=10,
|
x=10
|
||||||
y=11
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
class="static_wall",
|
||||||
color="blue",
|
color="blue",
|
||||||
class="static_wall",
|
y=11,
|
||||||
x=11,
|
x=11
|
||||||
y=11
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color="green",
|
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=11,
|
|
||||||
y=11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color="red",
|
color="red",
|
||||||
|
y=1,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
x=11,
|
color="red",
|
||||||
y=11
|
y=1,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=2,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=2,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=3,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=3,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=4,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=4,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=5,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=5,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=6,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=6,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=7,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=7,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=8,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=8,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=9,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=9,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=10,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=10,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=11,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=11,
|
||||||
|
x=2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=11,
|
||||||
|
x=3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=11,
|
||||||
|
x=4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=11,
|
||||||
|
x=5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=11,
|
||||||
|
x=7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=11,
|
||||||
|
x=8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=11,
|
||||||
|
x=9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=11,
|
||||||
|
x=10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="red",
|
||||||
|
y=11,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=1,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=1,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=2,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=2,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=3,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=3,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=4,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=4,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=5,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=5,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=6,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=6,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=7,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=7,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=8,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=8,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=9,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=9,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=10,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=10,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=11,
|
||||||
|
x=1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=11,
|
||||||
|
x=2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=11,
|
||||||
|
x=3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=11,
|
||||||
|
x=4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=11,
|
||||||
|
x=5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=11,
|
||||||
|
x=7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=11,
|
||||||
|
x=8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=11,
|
||||||
|
x=9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=11,
|
||||||
|
x=10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class="static_wall",
|
||||||
|
color="green",
|
||||||
|
y=11,
|
||||||
|
x=11
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1044
rooms/village_4.sav
1044
rooms/village_4.sav
File diff suppressed because it is too large
Load diff
|
|
@ -18,24 +18,6 @@ objects={
|
||||||
x=6,
|
x=6,
|
||||||
color="green"
|
color="green"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
y=11,
|
|
||||||
class="player",
|
|
||||||
x=6,
|
|
||||||
color="blue"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
y=11,
|
|
||||||
class="player",
|
|
||||||
x=6,
|
|
||||||
color="red"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
y=11,
|
|
||||||
class="player",
|
|
||||||
x=6,
|
|
||||||
color="green"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
y=1,
|
y=1,
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,5 @@
|
||||||
{
|
{
|
||||||
objects={
|
objects={
|
||||||
{
|
|
||||||
y=2,
|
|
||||||
class="player",
|
|
||||||
x=6,
|
|
||||||
color="blue"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
y=3,
|
|
||||||
class="player",
|
|
||||||
x=2,
|
|
||||||
color="red"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
y=3,
|
|
||||||
class="player",
|
|
||||||
x=7,
|
|
||||||
color="green"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
y=1,
|
y=1,
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
|
|
|
||||||
141
rooms/world.sav
141
rooms/world.sav
|
|
@ -1,146 +1,157 @@
|
||||||
{
|
{
|
||||||
width=18,
|
|
||||||
height=18,
|
|
||||||
rooms={
|
rooms={
|
||||||
{
|
{
|
||||||
|
name="start",
|
||||||
y=11,
|
y=11,
|
||||||
x=9,
|
x=9
|
||||||
name="start"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="3",
|
||||||
y=10,
|
y=10,
|
||||||
x=10,
|
x=10
|
||||||
name="3"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="5",
|
||||||
y=10,
|
y=10,
|
||||||
x=11,
|
x=12
|
||||||
name="4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
y=10,
|
|
||||||
x=12,
|
|
||||||
name="5"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="village_1",
|
||||||
y=9,
|
y=9,
|
||||||
x=12,
|
x=12
|
||||||
name="village_1"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="village_2",
|
||||||
y=8,
|
y=8,
|
||||||
x=12,
|
x=12
|
||||||
name="village_2"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="village_3",
|
||||||
y=8,
|
y=8,
|
||||||
x=13,
|
x=13
|
||||||
name="village_3"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="village_4",
|
||||||
y=8,
|
y=8,
|
||||||
x=11,
|
x=11
|
||||||
name="village_4"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="yellow_1",
|
||||||
y=7,
|
y=7,
|
||||||
x=11,
|
x=11
|
||||||
name="yellow_1"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="yellow_3",
|
||||||
y=6,
|
y=6,
|
||||||
x=11,
|
x=11
|
||||||
name="yellow_3"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="yellow_2",
|
||||||
y=7,
|
y=7,
|
||||||
x=12,
|
x=12
|
||||||
name="yellow_2"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="yellow_4",
|
||||||
y=5,
|
y=5,
|
||||||
x=11,
|
x=11
|
||||||
name="yellow_4"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="yellow_5",
|
||||||
y=4,
|
y=4,
|
||||||
x=11,
|
x=11
|
||||||
name="yellow_5"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="yellow_6",
|
||||||
y=5,
|
y=5,
|
||||||
x=10,
|
x=10
|
||||||
name="yellow_6"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="default",
|
||||||
y=1,
|
y=1,
|
||||||
x=1,
|
x=1
|
||||||
name="default"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="old_yellow_3",
|
||||||
y=1,
|
y=1,
|
||||||
x=3,
|
x=3
|
||||||
name="old_yellow_3"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="test_patt",
|
||||||
y=1,
|
y=1,
|
||||||
x=5,
|
x=5
|
||||||
name="test_patt"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="test_patt_update",
|
||||||
y=1,
|
y=1,
|
||||||
x=7,
|
x=7
|
||||||
name="test_patt_update"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="white_asymmetrical",
|
||||||
y=1,
|
y=1,
|
||||||
x=9,
|
x=9
|
||||||
name="white_asymmetrical"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="white_ok",
|
||||||
y=1,
|
y=1,
|
||||||
x=11,
|
x=11
|
||||||
name="white_ok"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="white_primary",
|
||||||
y=1,
|
y=1,
|
||||||
x=13,
|
x=13
|
||||||
name="white_primary"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="yellow_arches",
|
||||||
y=1,
|
y=1,
|
||||||
x=15,
|
x=15
|
||||||
name="yellow_arches"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="yellow_asymmetrical",
|
||||||
y=1,
|
y=1,
|
||||||
x=17,
|
x=17
|
||||||
name="yellow_asymmetrical"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="yellow_cc_moustache",
|
||||||
y=3,
|
y=3,
|
||||||
x=1,
|
x=1
|
||||||
name="yellow_cc_moustache"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="yellow_color_changing_levvel_1",
|
||||||
y=3,
|
y=3,
|
||||||
x=3,
|
x=3
|
||||||
name="yellow_color_changing_levvel_1"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="yellow_smile",
|
||||||
y=3,
|
y=3,
|
||||||
x=5,
|
x=7
|
||||||
name="yellow_skull"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
y=3,
|
|
||||||
x=7,
|
|
||||||
name="yellow_smile"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name="2",
|
||||||
y=10,
|
y=10,
|
||||||
x=9,
|
x=9
|
||||||
name="2"
|
},
|
||||||
|
{
|
||||||
|
name="room_11_09",
|
||||||
|
y=9,
|
||||||
|
x=11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name="yellow_skull",
|
||||||
|
y=3,
|
||||||
|
x=9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name="jorge_room",
|
||||||
|
y=5,
|
||||||
|
x=5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name="4",
|
||||||
|
y=10,
|
||||||
|
x=11
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
lastRoom="yellow_4",
|
||||||
|
width=18,
|
||||||
|
height=18
|
||||||
}
|
}
|
||||||
1286
rooms/yellow_1.sav
1286
rooms/yellow_1.sav
File diff suppressed because it is too large
Load diff
1427
rooms/yellow_2.sav
1427
rooms/yellow_2.sav
File diff suppressed because it is too large
Load diff
1290
rooms/yellow_3.sav
1290
rooms/yellow_3.sav
File diff suppressed because it is too large
Load diff
1394
rooms/yellow_4.sav
1394
rooms/yellow_4.sav
File diff suppressed because it is too large
Load diff
|
|
@ -30,18 +30,6 @@ objects={
|
||||||
x=13,
|
x=13,
|
||||||
color="blue"
|
color="blue"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
y=11,
|
|
||||||
class="player",
|
|
||||||
x=6,
|
|
||||||
color="red"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
y=11,
|
|
||||||
class="player",
|
|
||||||
x=6,
|
|
||||||
color="green"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
y=1,
|
y=1,
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,6 @@ objects={
|
||||||
x=6,
|
x=6,
|
||||||
color="red"
|
color="red"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
y=11,
|
|
||||||
class="player",
|
|
||||||
x=6,
|
|
||||||
color="green"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
y=1,
|
y=1,
|
||||||
class="static_wall",
|
class="static_wall",
|
||||||
|
|
|
||||||
8
stories/start_sign.ink
Normal file
8
stories/start_sign.ink
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
yo wassup
|
||||||
|
|
||||||
|
* kick the sign
|
||||||
|
it doesnt budge.
|
||||||
|
* kiss the sign
|
||||||
|
You step away, a little embarrassed.
|
||||||
|
* [say nothing] you have nothing to say to a sign.
|
||||||
|
- -> END
|
||||||
1
stories/start_sign.lua
Normal file
1
stories/start_sign.lua
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
return {["version"]={["engine"]=2,["tree"]=1},["inclusions"]={},["lists"]={},["tree"]={["_"]={["_"]={"yo wassup",{["node"]={"it doesnt budge."},["text"]="kick the sign",["choice"]="kick the sign"},{["node"]={"You step away, a little embarrassed."},["text"]="kiss the sign",["choice"]="kiss the sign"},{["text"]="you have nothing to say to a sign.",["choice"]="say nothing"},{["divert"]={["path"]="END",["tunnel"]=false}}}}},["constants"]={},["params"]={},["variables"]={}}
|
||||||
31
tools/compile_ink.lua
Normal file
31
tools/compile_ink.lua
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
-- Compile Ink source (stories/*.ink) into the pre-parsed .lua books the game
|
||||||
|
-- loads. LÖVE ships no lpeg, so parsing happens here, once, at author time;
|
||||||
|
-- the game only ever requires the compiled .lua. Needs a standalone `lua` with
|
||||||
|
-- lpeg installed (Arch: pacman -S lua-lpeg).
|
||||||
|
--
|
||||||
|
-- Run from the project root:
|
||||||
|
-- lua tools/compile_ink.lua # compile every stories/*.ink
|
||||||
|
-- lua tools/compile_ink.lua start_sign # compile stories/start_sign.ink
|
||||||
|
-- lua tools/compile_ink.lua stories/foo.ink # explicit path
|
||||||
|
|
||||||
|
package.path = package.path .. ";libs/narrator/?.lua"
|
||||||
|
local narrator = require("narrator.narrator")
|
||||||
|
|
||||||
|
local function compile(path)
|
||||||
|
narrator.parse_file(path, { save = true })
|
||||||
|
print("compiled " .. path .. " -> " .. (path:gsub("%.ink$", "") .. ".lua"))
|
||||||
|
end
|
||||||
|
|
||||||
|
local targets = {}
|
||||||
|
if #arg == 0 then
|
||||||
|
local ls = io.popen("ls stories/*.ink 2>/dev/null")
|
||||||
|
for line in ls:lines() do table.insert(targets, line) end
|
||||||
|
ls:close()
|
||||||
|
else
|
||||||
|
for _, a in ipairs(arg) do
|
||||||
|
table.insert(targets, a:match("%.ink$") and a or ("stories/" .. a .. ".ink"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(#targets > 0, "no .ink files to compile")
|
||||||
|
for _, t in ipairs(targets) do compile(t) end
|
||||||
29
tools/nuke_love_saves.sh
Executable file
29
tools/nuke_love_saves.sh
Executable file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
# Delete Chroma Solstice's *runtime* LÖVE save data. Project levels in
|
||||||
|
# ./rooms are not affected.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
DATA_HOME=${XDG_DATA_HOME:-"$HOME/.local/share"}
|
||||||
|
SAVE_DIR="$DATA_HOME/love/chroma-solstice"
|
||||||
|
|
||||||
|
if [ "${1:-}" != "--yes" ]; then
|
||||||
|
printf '%s\n' "This deletes only the local LÖVE runtime saves at:"
|
||||||
|
printf '%s\n' " $SAVE_DIR"
|
||||||
|
printf '%s\n' "Repository files, including ./rooms/*.sav, are not touched."
|
||||||
|
printf '%s\n' "Run: $0 --yes"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -e "$SAVE_DIR" ]; then
|
||||||
|
printf '%s\n' "No local LÖVE saves found at: $SAVE_DIR"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Keep the destructive target exact even if this script is changed later.
|
||||||
|
case "$SAVE_DIR" in
|
||||||
|
"$DATA_HOME/love/chroma-solstice") ;;
|
||||||
|
*) printf '%s\n' "Refusing unexpected save path: $SAVE_DIR" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
rm -rf -- "$SAVE_DIR"
|
||||||
|
printf '%s\n' "Deleted local LÖVE saves: $SAVE_DIR"
|
||||||
27
world.lua
27
world.lua
|
|
@ -8,6 +8,7 @@ function World:initialize()
|
||||||
self.rooms = {}
|
self.rooms = {}
|
||||||
self.roomByName = {}
|
self.roomByName = {}
|
||||||
self.previewCache = {}
|
self.previewCache = {}
|
||||||
|
self.lastRoom = nil
|
||||||
self:load()
|
self:load()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -26,6 +27,7 @@ function World:load()
|
||||||
if not raw then return end
|
if not raw then return end
|
||||||
local ok, save = pcall(TSerial.unpack, raw)
|
local ok, save = pcall(TSerial.unpack, raw)
|
||||||
if not ok or type(save) ~= "table" then return end
|
if not ok or type(save) ~= "table" then return end
|
||||||
|
self.lastRoom = save.lastRoom
|
||||||
for _, room in ipairs(save.rooms or {}) do
|
for _, room in ipairs(save.rooms or {}) do
|
||||||
if room.name and self:isInBounds(room.x, room.y) then
|
if room.name and self:isInBounds(room.x, room.y) then
|
||||||
self:_putRoom(room.name, room.x, room.y)
|
self:_putRoom(room.name, room.x, room.y)
|
||||||
|
|
@ -38,7 +40,7 @@ function World:save()
|
||||||
for _, room in ipairs(self.rooms) do
|
for _, room in ipairs(self.rooms) do
|
||||||
table.insert(rooms, { name = room.name, x = room.x, y = room.y })
|
table.insert(rooms, { name = room.name, x = room.x, y = room.y })
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
function World:isInBounds(x, y)
|
function World:isInBounds(x, y)
|
||||||
|
|
@ -87,6 +89,7 @@ function World:removeRoom(x, y)
|
||||||
if room.x == x and room.y == y then
|
if room.x == x and room.y == y then
|
||||||
self.roomByName[room.name] = nil
|
self.roomByName[room.name] = nil
|
||||||
table.remove(self.rooms, i)
|
table.remove(self.rooms, i)
|
||||||
|
if self.lastRoom == room.name then self.lastRoom = nil end
|
||||||
return self:save(), room.name
|
return self:save(), room.name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -99,11 +102,20 @@ function World:renameRoom(oldName, newName)
|
||||||
self.roomByName[oldName] = nil
|
self.roomByName[oldName] = nil
|
||||||
room.name = newName
|
room.name = newName
|
||||||
self.roomByName[newName] = room
|
self.roomByName[newName] = room
|
||||||
|
if self.lastRoom == oldName then self.lastRoom = newName end
|
||||||
self:invalidatePreview(oldName)
|
self:invalidatePreview(oldName)
|
||||||
self:invalidatePreview(newName)
|
self:invalidatePreview(newName)
|
||||||
return self:save()
|
return self:save()
|
||||||
end
|
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)
|
function World:getNeighbor(name, exit)
|
||||||
local room = self:roomLocation(name)
|
local room = self:roomLocation(name)
|
||||||
if not room then return nil end
|
if not room then return nil end
|
||||||
|
|
@ -144,12 +156,17 @@ function World:drawRoomPreview(name, x, y, size)
|
||||||
love.graphics.setScissor(x, y, size, size)
|
love.graphics.setScissor(x, y, size, size)
|
||||||
for _, object in ipairs(room.objects or {}) do
|
for _, object in ipairs(room.objects or {}) do
|
||||||
if object.class == "npc" then
|
if object.class == "npc" then
|
||||||
love.graphics.setColor(255, 255, 255, 255)
|
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)
|
love.graphics.print("?", x + (object.x - 1) * 16 * scale, y + (object.y - 1) * 16 * scale, 0, scale, scale)
|
||||||
else
|
else
|
||||||
local props = globalAssetProperties[object.class]
|
local props = globalAssetProperties[object.class]
|
||||||
if props and props.sprites then
|
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
|
for _, color in ipairs(colors) do
|
||||||
local path = props.sprites[color]
|
local path = props.sprites[color]
|
||||||
if path then
|
if path then
|
||||||
|
|
@ -165,14 +182,14 @@ end
|
||||||
|
|
||||||
function World:draw()
|
function World:draw()
|
||||||
local cell = width / self.width
|
local cell = width / self.width
|
||||||
love.graphics.setColor(12, 12, 20, 255)
|
love.graphics.setColor(12 / 255, 12 / 255, 20 / 255, 1)
|
||||||
love.graphics.rectangle("fill", 0, 0, width, height)
|
love.graphics.rectangle("fill", 0, 0, width, height)
|
||||||
for y = 1, self.height do
|
for y = 1, self.height do
|
||||||
for x = 1, self.width do
|
for x = 1, self.width do
|
||||||
local screenX, screenY = (x - 1) * cell, (y - 1) * cell
|
local screenX, screenY = (x - 1) * cell, (y - 1) * cell
|
||||||
local name = self:roomAt(x, y)
|
local name = self:roomAt(x, y)
|
||||||
if name then self:drawRoomPreview(name, screenX, screenY, cell) end
|
if name then self:drawRoomPreview(name, screenX, screenY, cell) end
|
||||||
love.graphics.setColor(name and 255 or 70, name and 255 or 70, name and 255 or 90, 255)
|
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)
|
love.graphics.rectangle("line", screenX, screenY, cell, cell)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue