note blocks and sticky test

This commit is contained in:
Your Name 2026-08-13 12:45:41 -04:00
parent 1bc1cdb11f
commit d829b4972f
11 changed files with 3389 additions and 118 deletions

BIN
art/_debug/notepad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

View file

@ -1,6 +1,7 @@
require "level_editor/object_attributes" require "level_editor/object_attributes"
require "level_editor/gui" require "level_editor/gui"
require "level_editor/layout" require "level_editor/layout"
require "level_editor/note_editor"
editorView = "room" --can be "off", "room", "world", "asset" editorView = "room" --can be "off", "room", "world", "asset"
selectedColor = "white" selectedColor = "white"
@ -10,6 +11,7 @@ local folderSprite = love.graphics.newImage("art/_ui/folder.png")
local cancelSprite = love.graphics.newImage("art/_ui/cancel.png") local cancelSprite = love.graphics.newImage("art/_ui/cancel.png")
local confirmSprite = love.graphics.newImage("art/_ui/confirm.png") local confirmSprite = love.graphics.newImage("art/_ui/confirm.png")
local clearSavesSprite = love.graphics.newImage("art/_ui/clear_saves.png") local clearSavesSprite = love.graphics.newImage("art/_ui/clear_saves.png")
local notepadSprite = love.graphics.newImage("art/_debug/notepad.png")
local selectedFolder = nil local selectedFolder = nil
local selectedAsset = nil local selectedAsset = nil
@ -53,6 +55,12 @@ local boxPaletteOpen = false
local boxPaletteScroll = { scroll = 0 } local boxPaletteScroll = { scroll = 0 }
local boxPaletteSections = {} local boxPaletteSections = {}
-- Note authoring state. `noteMode` is nil normally, "create" while placing a new
-- note (click a cell to drop it and finish), or "edit" while retyping an
-- existing note (with Save / Discard). `editingNote` is the note being edited.
local noteMode = nil
local editingNote = nil
-- Convert a pointer position into a valid room cell. Keeping brush state in -- Convert a pointer position into a valid room cell. Keeping brush state in
-- cell coordinates means a click that jitters within a tile cannot place or -- cell coordinates means a click that jitters within a tile cannot place or
-- erase that tile more than once. -- erase that tile more than once.
@ -241,6 +249,19 @@ local function roomSections()
return { search = search, listRect = list, status = status, controls = controls } return { search = search, listRect = list, status = status, controls = controls }
end end
-- Save / Discard buttons for note edit mode, laid out in the status strip. Runs
-- for both drawing and hit-testing so the two never disagree.
local function noteEditButtonRects(s)
local gap = 8
local h = math.min(28, s.status.h)
local w = (s.status.w - gap) / 2
local y = s.status.y + (s.status.h - h) / 2
return {
save = { x = s.status.x, y = y, w = w, h = h },
discard = { x = s.status.x + w + gap, y = y, w = w, h = h },
}
end
-- Position the bottom control cluster inside its section: level-name field, a -- Position the bottom control cluster inside its section: level-name field, a
-- row of colour buttons, then a row of action buttons. Runs for both drawing -- row of colour buttons, then a row of action buttons. Runs for both drawing
-- and hit-testing so the two never disagree. -- and hit-testing so the two never disagree.
@ -297,7 +318,7 @@ local function layoutRoomControls(c, s)
placeRow({ "white", "red", "green", "blue" }, 22) placeRow({ "white", "red", "green", "blue" }, 22)
placeRow({ "quick_player", "quick_static_wall", "quick_switch", "quick_door", "quick_wired_switch", "quick_wired_door", "quick_box" }, 22) placeRow({ "quick_player", "quick_static_wall", "quick_switch", "quick_door", "quick_wired_switch", "quick_wired_door", "quick_box" }, 22)
placeRow({ "save", "clearSaves", "world", "wire" }, 28) placeRow({ "save", "clearSaves", "world", "wire", "note" }, 28)
end end
-- Build the full room-view layout (sections + a scroll list + a back/search -- Build the full room-view layout (sections + a scroll list + a back/search
@ -723,6 +744,16 @@ function _initLevelEditor()
y = 0, y = 0,
}, "wire") }, "wire")
_addUIButton(createUIElement{
name = "note",
sprite = notepadSprite,
onClic = { left = function()
if editorView == "room" then beginNoteCreate() end
end },
x = 0,
y = 0,
}, "note")
roomNameField = createUIElement{ roomNameField = createUIElement{
name = "roomName", name = "roomName",
text = editorRoomName, text = editorRoomName,
@ -783,9 +814,50 @@ end
function selectAsset(asset) function selectAsset(asset)
print("selecting", asset) print("selecting", asset)
selectedAsset = asset selectedAsset = asset
exitNoteMode() -- picking any real asset closes the note text field
print(selectedAsset) print(selectedAsset)
end end
-- Close the note text box and return to whatever the sidebar was showing before.
function exitNoteMode()
noteMode = nil
editingNote = nil
noteEditorBlur()
end
-- Start authoring a new note: focus a blank text field. The author types, then
-- clicks a grid cell to drop the note there (that placement is the "done").
function beginNoteCreate()
if editorView ~= "room" then return end
boxPaletteOpen = false
noteMode = "create"
editingNote = nil
noteEditorSetText("")
noteEditorFocus()
end
-- Left-clicking an existing note reopens it for editing, with Save / Discard.
function beginNoteEdit(note)
noteMode = "edit"
editingNote = note
noteEditorSetText(note.text)
noteEditorFocus()
end
function saveNoteEdit()
if editingNote then editingNote.text = noteEditorText() end
exitNoteMode()
end
function discardNoteEdit()
exitNoteMode()
end
-- Keyboard Escape while the field is focused: discard the edit / cancel placing.
function cancelNoteMode()
exitNoteMode()
end
function selectQuickAsset(asset) function selectQuickAsset(asset)
selectAsset(asset) selectAsset(asset)
boxPaletteOpen = asset == "box" boxPaletteOpen = asset == "box"
@ -922,6 +994,7 @@ function wireView()
editorStatus = "Place this room in the world before drawing cross-room wires." editorStatus = "Place this room in the world before drawing cross-room wires."
return return
end end
exitNoteMode()
editorView = "wire" editorView = "wire"
activeField = nil activeField = nil
roomBrush = nil roomBrush = nil
@ -935,6 +1008,7 @@ function wireView()
end end
function worldView() function worldView()
exitNoteMode()
editorView = "world" editorView = "world"
wireBrush = nil wireBrush = nil
activeField = nil activeField = nil
@ -1169,6 +1243,22 @@ function editorMouseHandler(x, y, button)
return return
end end
-- While authoring a note, the field takes over the list area: a click
-- there positions the caret / starts a selection (its rect is set each
-- frame by noteEditorDraw). In edit mode, Save / Discard live in the
-- status strip.
if noteMode then
if noteMode == "edit" and button == 1 then
local nb = noteEditButtonRects(s)
if Layout.hit(nb.save, x, y) then saveNoteEdit(); return end
if Layout.hit(nb.discard, x, y) then discardNoteEdit(); return end
end
if noteEditorMousepressed(x, y) then return end
-- The field covers the list area; swallow other clicks there so the
-- hidden asset list underneath can't be selected by accident.
if Layout.hit(s.listRect, x, y) then return end
end
if s.boxPicker then if s.boxPicker then
local asset = s.boxPicker.at(x, y) local asset = s.boxPicker.at(x, y)
if asset then if asset then
@ -1185,7 +1275,7 @@ function editorMouseHandler(x, y, button)
end end
-- control cluster (only the buttons that live there) -- control cluster (only the buttons that live there)
for _, name in ipairs({ "roomName", "white", "red", "green", "blue", "quick_player", "quick_static_wall", "quick_switch", "quick_door", "quick_wired_switch", "quick_wired_door", "quick_box", "save", "clearSaves", "world", "wire" }) do for _, name in ipairs({ "roomName", "white", "red", "green", "blue", "quick_player", "quick_static_wall", "quick_switch", "quick_door", "quick_wired_switch", "quick_wired_door", "quick_box", "save", "clearSaves", "world", "wire", "note" }) do
local b = uiButtons[name] local b = uiButtons[name]
if b and b:clicIsInBox(x, y) then if b and b:clicIsInBox(x, y) then
b:onClic(button) b:onClic(button)
@ -1232,6 +1322,20 @@ function editorMouseHandler(x, y, button)
else else
-- click landed on the grid (game area): place the selected asset -- click landed on the grid (game area): place the selected asset
if editorView == "room" then if editorView == "room" then
local cell = button == 1 and roomCellAt(x, y)
-- Placing a new note: drop it here carrying the typed text, then finish
-- (the drop is the "done"), returning the sidebar to its prior view.
if cell and noteMode == "create" then
currentRoom:attemptPlaceAtCell("note", nil, cell, selectedColor, noteEditorText())
exitNoteMode()
return
end
-- Left-clicking an existing note opens it for editing instead of
-- painting over it.
if cell then
local note = currentRoom:noteAtCell(cell)
if note then beginNoteEdit(note); return end
end
beginRoomBrush(x, y, button) beginRoomBrush(x, y, button)
elseif editorView == "wire" then elseif editorView == "wire" then
beginWireBrush(x, y, button) beginWireBrush(x, y, button)
@ -1257,6 +1361,11 @@ function editorMouseHandler(x, y, button)
end end
function editorMouseMoved(x, y) function editorMouseMoved(x, y)
if noteEditorActive() and x >= sideBarBox.x then
local vx = (x - sideBarBox.x) * sb.w / sideBarBox.w
local vy = y * sb.h / sideBarBox.h
if noteEditorMousemoved(vx, vy) then return end
end
if wireBrush then if wireBrush then
if editorView ~= "wire" then wireBrush = nil; return end if editorView ~= "wire" then wireBrush = nil; return end
local cell = wireCellAt(x, y) local cell = wireCellAt(x, y)
@ -1286,6 +1395,7 @@ function editorMouseMoved(x, y)
end end
function editorMouseReleased(_, _, button) function editorMouseReleased(_, _, button)
noteEditorMousereleased()
if roomBrush and roomBrush.button == button then roomBrush = nil end if roomBrush and roomBrush.button == button then roomBrush = nil end
if wireBrush and wireBrush.button == button then wireBrush = nil end if wireBrush and wireBrush.button == button then wireBrush = nil end
end end
@ -1355,6 +1465,36 @@ function createUIElement(t)
return GUI:new(t) return GUI:new(t)
end end
-- Hover readout for note blocks, drawn in screen space over the play area (not
-- the sidebar). Mirrors World:drawHoveredRoomName's pointer-anchored tooltip so
-- it matches the overworld hover style. Only meaningful while notes are shown.
function editorDrawNoteHover()
if not (DEBUG and NOTES_VISIBLE) then return end
if editorView ~= "room" then return end
local mouseX, mouseY = love.mouse.getPosition()
if mouseX < 0 or mouseX >= width or mouseY < 0 or mouseY >= height then return end
local note = currentRoom:noteAtCell(currentRoom:getCellFromMousePos(mouseX, mouseY))
if not note or note.text == "" then return end
local pad = 6
local maxW = math.min(280, width - 8)
local _, wrapped = font:getWrap(note.text, maxW - pad * 2)
local textW = 0
for _, line in ipairs(wrapped) do textW = math.max(textW, font:getWidth(line)) end
local tooltipW = math.min(textW + pad * 2, maxW)
local tooltipH = #wrapped * font:getHeight() + pad * 2
local tooltipX = math.max(4, math.min(mouseX + 14, width - tooltipW - 4))
local tooltipY = math.max(4, math.min(mouseY + 14, height - tooltipH - 4))
love.graphics.setColor(0, 0, 0, 0.9)
love.graphics.rectangle("fill", tooltipX, tooltipY, tooltipW, tooltipH)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.rectangle("line", tooltipX, tooltipY, tooltipW, tooltipH)
love.graphics.printf(note.text, tooltipX + pad, tooltipY + pad, tooltipW - pad * 2, "left")
end
function editorDraw() function editorDraw()
if editorView == "room" then if editorView == "room" then
local s = buildRoomLayout() local s = buildRoomLayout()
@ -1373,7 +1513,17 @@ function editorDraw()
love.graphics.rectangle("line", s.searchBox.x, s.searchBox.y, s.searchBox.w, s.searchBox.h) love.graphics.rectangle("line", s.searchBox.x, s.searchBox.y, s.searchBox.w, s.searchBox.h)
love.graphics.printf(boxPaletteOpen and "QUICK PALETTE" or "SEARCH: " .. assetSearchText, s.searchBox.x + 6, s.searchBox.y + 9, s.searchBox.w - 12, "left") love.graphics.printf(boxPaletteOpen and "QUICK PALETTE" or "SEARCH: " .. assetSearchText, s.searchBox.x + 6, s.searchBox.y + 9, s.searchBox.w - 12, "left")
if s.boxPicker then if noteMode then
-- Authoring a note: the text field replaces the asset list. In create
-- mode you click a grid cell to drop it; in edit mode Save / Discard sit
-- in the status strip below.
local labelH = 18
local hint = noteMode == "edit" and "EDIT NOTE" or "NEW NOTE (click grid to place)"
love.graphics.setColor(gColor.white:set())
love.graphics.printf(hint, s.listRect.x, s.listRect.y, s.listRect.w, "left")
noteEditorSetRect(s.listRect.x, s.listRect.y + labelH, s.listRect.w, s.listRect.h - labelH)
noteEditorDraw()
elseif s.boxPicker then
drawBoxPicker(s.boxPicker) drawBoxPicker(s.boxPicker)
else else
-- Scrolling list, clipped to its section so nothing bleeds into others. -- Scrolling list, clipped to its section so nothing bleeds into others.
@ -1397,7 +1547,7 @@ function editorDraw()
-- control cluster -- control cluster
love.graphics.setColor(gColor.white:set()) love.graphics.setColor(gColor.white:set())
love.graphics.printf("LEVEL NAME", s.controls.x, s.labelY, s.controls.w, "center") love.graphics.printf("LEVEL NAME", s.controls.x, s.labelY, s.controls.w, "center")
for _, name in ipairs({ "roomName", "white", "red", "green", "blue", "quick_player", "quick_static_wall", "quick_switch", "quick_door", "quick_wired_switch", "quick_wired_door", "quick_box", "save", "clearSaves", "world", "wire" }) do for _, name in ipairs({ "roomName", "white", "red", "green", "blue", "quick_player", "quick_static_wall", "quick_switch", "quick_door", "quick_wired_switch", "quick_wired_door", "quick_box", "save", "clearSaves", "world", "wire", "note" }) do
local button = uiButtons[name] local button = uiButtons[name]
if button then if button then
button:draw() button:draw()
@ -1406,7 +1556,7 @@ function editorDraw()
love.graphics.setColor(1, 1, 1, 1) love.graphics.setColor(1, 1, 1, 1)
love.graphics.print("W", button.x + button.w - 7, button.y - 4) love.graphics.print("W", button.x + button.w - 7, button.y - 4)
end end
if asset and selectedAsset == asset then if (asset and selectedAsset == asset) or (name == "note" and noteMode ~= nil) then
love.graphics.setColor(1, 0.85, 0.2, 1) love.graphics.setColor(1, 0.85, 0.2, 1)
love.graphics.setLineWidth(2) love.graphics.setLineWidth(2)
love.graphics.rectangle("line", button.x - 1, button.y - 1, button.w + 2, button.h + 2) love.graphics.rectangle("line", button.x - 1, button.y - 1, button.w + 2, button.h + 2)
@ -1414,8 +1564,17 @@ function editorDraw()
end end
end end
-- status strip between the list and the controls -- status strip between the list and the controls: Save / Discard while
if editorStatus then -- editing an existing note, otherwise the ordinary status line.
if noteMode == "edit" then
local nb = noteEditButtonRects(s)
love.graphics.setColor(gColor.white:set())
for label, r in pairs({ SAVE = nb.save, DISCARD = nb.discard }) do
love.graphics.setLineWidth(2)
love.graphics.rectangle("line", r.x, r.y, r.w, r.h)
love.graphics.printf(label, r.x, r.y + (r.h - font:getHeight()) / 2, r.w, "center")
end
elseif editorStatus then
Layout.clip(s.status) Layout.clip(s.status)
love.graphics.setColor(gColor.white:set()) love.graphics.setColor(gColor.white:set())
love.graphics.printf(editorStatus, s.status.x, s.status.y + 4, s.status.w, "center") love.graphics.printf(editorStatus, s.status.x, s.status.y + 4, s.status.w, "center")

View file

@ -0,0 +1,299 @@
-- A small, self-contained multi-line text field for the level editor sidebar.
-- LÖVE ships no GUI widgets, so this rolls the minimum needed to feel like a
-- normal text box: a caret, a selection, mouse click/drag positioning, and the
-- usual keyboard shortcuts (arrows, word-jump, home/end, select-all, clipboard).
--
-- Text is kept as an array of one-character strings (utf8-aware) with the caret
-- and selection anchor expressed as caret indices (0..#chars, a position between
-- characters). Everything else is derived from that.
local utf8 = require("utf8")
local pad = 6
local note = {
active = false,
chars = {}, -- array of single utf8 characters
caret = 0, -- 0..#chars
anchor = nil, -- selection start (caret index) or nil
rect = { x = 0, y = 0, w = 0, h = 0 },
dragging = false,
}
-- ── helpers ─────────────────────────────────────────────────────────────────
local function toChars(str)
local t = {}
local i, len = 1, #str
while i <= len do
local nexti = utf8.offset(str, 2, i) or (len + 1)
t[#t + 1] = str:sub(i, nexti - 1)
i = nexti
end
return t
end
local function clamp(v, lo, hi)
if v < lo then return lo elseif v > hi then return hi else return v end
end
local function isWordChar(c)
return c ~= nil and c:match("[%w_]") ~= nil
end
-- Line ranges as caret-index pairs: line covers caret indices s..e, and its text
-- is chars[s+1 .. e]. A blank final line after a trailing newline is included.
local function buildLines()
local lines, start = {}, 0
for k = 1, #note.chars do
if note.chars[k] == "\n" then
lines[#lines + 1] = { s = start, e = k - 1 }
start = k
end
end
lines[#lines + 1] = { s = start, e = #note.chars }
return lines
end
local function lineIndexOf(caret, lines)
for i, line in ipairs(lines) do
if caret >= line.s and caret <= line.e then return i end
end
return #lines
end
-- Pixel width from a line's start up to `col` characters into it.
local function colWidth(line, col)
local s = table.concat(note.chars, "", line.s + 1, line.s + col)
return font:getWidth(s)
end
-- ── selection / mutation ─────────────────────────────────────────────────────
local function selectionRange()
if not note.anchor or note.anchor == note.caret then return nil end
return math.min(note.anchor, note.caret), math.max(note.anchor, note.caret)
end
local function deleteRange(lo, hi)
for k = hi, lo + 1, -1 do table.remove(note.chars, k) end
note.caret = lo
note.anchor = nil
end
local function deleteSelection()
local lo, hi = selectionRange()
if not lo then return false end
deleteRange(lo, hi)
return true
end
local function insertText(str)
deleteSelection()
local ins = toChars(str)
for offset, c in ipairs(ins) do
table.insert(note.chars, note.caret + offset, c)
end
note.caret = note.caret + #ins
note.anchor = nil
end
-- Move the caret, extending or clearing the selection depending on shift.
local function moveCaret(pos, keepSelection)
if keepSelection then
if not note.anchor then note.anchor = note.caret end
else
note.anchor = nil
end
note.caret = clamp(pos, 0, #note.chars)
end
local function wordLeft(i)
local j = i
while j > 0 and not isWordChar(note.chars[j]) do j = j - 1 end
while j > 0 and isWordChar(note.chars[j]) do j = j - 1 end
return j
end
local function wordRight(i)
local n = #note.chars
local j = i
while j < n and not isWordChar(note.chars[j + 1]) do j = j + 1 end
while j < n and isWordChar(note.chars[j + 1]) do j = j + 1 end
return j
end
-- Map a point (sidebar-virtual coords) to the nearest caret index.
local function caretFromPoint(px, py)
local lines = buildLines()
local lineH = font:getHeight()
local row = clamp(math.floor((py - (note.rect.y + pad)) / lineH) + 1, 1, #lines)
local line = lines[row]
local target = px - (note.rect.x + pad)
local best, bestDist = 0, math.huge
for col = 0, line.e - line.s do
local dist = math.abs(colWidth(line, col) - target)
if dist < bestDist then best, bestDist = col, dist end
end
return line.s + best
end
-- ── public API (globals, matching the editor's module style) ─────────────────
function noteEditorActive()
return note.active and editorView == "room"
end
function noteEditorFocus()
note.active = true
end
function noteEditorBlur()
note.active = false
note.dragging = false
end
function noteEditorText()
return table.concat(note.chars)
end
function noteEditorSetText(str)
note.chars = toChars(str or "")
note.caret = #note.chars
note.anchor = nil
end
function noteEditorSetRect(x, y, w, h)
note.rect.x, note.rect.y, note.rect.w, note.rect.h = x, y, w, h
end
-- Click landed somewhere in the sidebar (virtual coords). Returns true if it hit
-- the field (so the caller stops looking for another control).
function noteEditorMousepressed(x, y)
local r = note.rect
if x < r.x or x > r.x + r.w or y < r.y or y > r.y + r.h then return false end
note.active = true
note.caret = caretFromPoint(x, y)
note.anchor = note.caret
note.dragging = true
return true
end
function noteEditorMousemoved(x, y)
if not note.dragging then return false end
note.caret = caretFromPoint(x, y)
return true
end
function noteEditorMousereleased()
note.dragging = false
if note.anchor == note.caret then note.anchor = nil end
end
function noteEditorTextinput(t)
insertText(t)
end
function noteEditorKeypressed(key)
local ctrl = love.keyboard.isDown("lctrl", "rctrl", "lgui", "rgui")
local shift = love.keyboard.isDown("lshift", "rshift")
if key == "escape" then
noteEditorBlur()
elseif key == "return" or key == "kpenter" then
insertText("\n")
elseif key == "backspace" then
if not deleteSelection() then
local to = ctrl and wordLeft(note.caret) or note.caret - 1
if to >= 0 and note.caret > 0 then deleteRange(math.max(0, to), note.caret) end
end
elseif key == "delete" then
if not deleteSelection() then
local to = ctrl and wordRight(note.caret) or note.caret + 1
if note.caret < #note.chars then deleteRange(note.caret, math.min(#note.chars, to)) end
end
elseif key == "left" then
moveCaret(ctrl and wordLeft(note.caret) or note.caret - 1, shift)
elseif key == "right" then
moveCaret(ctrl and wordRight(note.caret) or note.caret + 1, shift)
elseif key == "up" or key == "down" then
local lines = buildLines()
local row = lineIndexOf(note.caret, lines)
local col = note.caret - lines[row].s
local target = clamp(row + (key == "up" and -1 or 1), 1, #lines)
local line = lines[target]
moveCaret(line.s + math.min(col, line.e - line.s), shift)
elseif key == "home" then
if ctrl then
moveCaret(0, shift)
else
local lines = buildLines()
moveCaret(lines[lineIndexOf(note.caret, lines)].s, shift)
end
elseif key == "end" then
if ctrl then
moveCaret(#note.chars, shift)
else
local lines = buildLines()
moveCaret(lines[lineIndexOf(note.caret, lines)].e, shift)
end
elseif ctrl and key == "a" then
note.anchor = 0
note.caret = #note.chars
elseif ctrl and (key == "c" or key == "x") then
local lo, hi = selectionRange()
if lo then
love.system.setClipboardText(table.concat(note.chars, "", lo + 1, hi))
if key == "x" then deleteRange(lo, hi) end
end
elseif ctrl and key == "v" then
insertText(love.system.getClipboardText() or "")
end
end
-- Rendered inside the room-view sidebar (virtual space), clipped to its rect.
function noteEditorDraw()
local r = note.rect
local lines = buildLines()
local lineH = font:getHeight()
local ox, oy = r.x + pad, r.y + pad
love.graphics.setColor(0, 0, 0, 0.85)
love.graphics.rectangle("fill", r.x, r.y, r.w, r.h)
love.graphics.setColor(gColor.white:set())
love.graphics.setLineWidth(noteEditorActive() and 2 or 1)
love.graphics.rectangle("line", r.x, r.y, r.w, r.h)
Layout.clip({ x = r.x, y = r.y, w = r.w, h = r.h })
-- selection highlight
local lo, hi = selectionRange()
if lo then
love.graphics.setColor(0.25, 0.45, 0.9, 0.6)
for i, line in ipairs(lines) do
local segStart, segEnd = math.max(lo, line.s), math.min(hi, line.e)
if segEnd > segStart then
local x1 = ox + colWidth(line, segStart - line.s)
local x2 = ox + colWidth(line, segEnd - line.s)
love.graphics.rectangle("fill", x1, oy + (i - 1) * lineH, x2 - x1, lineH)
end
end
end
love.graphics.setColor(gColor.white:set())
for i, line in ipairs(lines) do
local text = table.concat(note.chars, "", line.s + 1, line.e)
love.graphics.print(text, ox, oy + (i - 1) * lineH)
end
-- caret (blinking) — only while focused
if noteEditorActive() and (love.timer.getTime() % 1) < 0.5 then
local row = lineIndexOf(note.caret, lines)
local cx = ox + colWidth(lines[row], note.caret - lines[row].s)
local cy = oy + (row - 1) * lineH
love.graphics.setLineWidth(1)
love.graphics.line(cx, cy, cx, cy + lineH)
end
Layout.unclip()
end

View file

@ -35,6 +35,10 @@ jorgeBook = require "stories.jorge"
-- the DEBUG env var (DEBUG=0 to force off, DEBUG=1 to force on). -- the DEBUG env var (DEBUG=0 to force off, DEBUG=1 to force on).
DEBUG = os.getenv("DEBUG") ~= "0" DEBUG = os.getenv("DEBUG") ~= "0"
-- Whether editor "note" annotations render over the room. Toggled with Tab (see
-- love.keypressed); default on. Only ever consulted under DEBUG (see Room:draw).
NOTES_VISIBLE = true
function love.load() function love.load()
globalAssetProperties = {} -- rebuilt from the filesystem (art/ + .meta) by getAllAssets globalAssetProperties = {} -- rebuilt from the filesystem (art/ + .meta) by getAllAssets
@ -165,6 +169,7 @@ function love.draw()
love.graphics.setBlendMode("alpha") love.graphics.setBlendMode("alpha")
love.graphics.setColor(1, 1, 1, 1) love.graphics.setColor(1, 1, 1, 1)
if DEBUG then love.graphics.draw(sideBarCanvas, sideBarBox.x, sideBarBox.y) end if DEBUG then love.graphics.draw(sideBarCanvas, sideBarBox.x, sideBarBox.y) end
if DEBUG then editorDrawNoteHover() end
love.graphics.print("Current FPS: "..tostring(love.timer.getFPS( )), 10, 10) love.graphics.print("Current FPS: "..tostring(love.timer.getFPS( )), 10, 10)
PerfProfiler:draw() PerfProfiler:draw()
Dialogue.draw() Dialogue.draw()
@ -206,6 +211,19 @@ function love.keypressed(key)
return return
end end
-- The focused note text field owns the keyboard entirely: arrows navigate the
-- text instead of moving the player, Return inserts a newline, etc.
if DEBUG and noteEditorActive() then
if key == "escape" then cancelNoteMode() else noteEditorKeypressed(key) end
return
end
-- Tab toggles the editor's note overlay (DEBUG-only; default on).
if DEBUG and key == "tab" then
NOTES_VISIBLE = not NOTES_VISIBLE
return
end
if editorView == "wire" and editorWireKeypressed(key) then return end if editorView == "wire" and editorWireKeypressed(key) then return end
if editorView ~= "world" and editorView ~= "wire" and Input.isPlayerMove(key) then if editorView ~= "world" and editorView ~= "wire" and Input.isPlayerMove(key) then
@ -264,6 +282,10 @@ end
function love.textinput(t) function love.textinput(t)
if not DEBUG then return end if not DEBUG then return end
if noteEditorActive() then
noteEditorTextinput(t)
return
end
response = response .. t response = response .. t
editorTextHandler(response) editorTextHandler(response)
end end

30
note.lua Normal file
View file

@ -0,0 +1,30 @@
require "entity"
-- An editor annotation dropped onto the grid. A Note is a normal Entity (so it
-- serializes, places and hit-tests like any other object) that carries a bit of
-- author text. It is deliberately non-collidable — the player passes straight
-- through it — and only ever renders as a translucent icon while the editor is
-- available (DEBUG) and notes are toggled on (see NOTES_VISIBLE / Room:draw).
Note = class("Note", Entity)
local noteSprite = loadSprite("art/_debug/notepad.png")
function Note:initialize(x, y, text)
self.text = text or ""
self.saveClass = "note"
Entity.initialize(self, {
x = x, y = y,
color = "white", -- representative only; Note:draw ignores channels
collision = "none",
sprite = noteSprite,
})
end
-- Drawn once (not per-channel) as a faint overlay. Called directly from the
-- room's note pass, so it takes no channel argument.
function Note:draw()
love.graphics.setColor(1, 1, 1, 0.85)
local p = self:getDrawPos()
love.graphics.draw(self.sprite, p.x, p.y, 0, drawScale, drawScale)
love.graphics.setColor(1, 1, 1, 1)
end

View file

@ -17,6 +17,7 @@ require "generic_skewer"
require "generic_spiky" require "generic_spiky"
require "generic_unmoveable" require "generic_unmoveable"
require "generic_player_blocker" require "generic_player_blocker"
require "note"
local Movement = require "movement" local Movement = require "movement"
local Wire = require "wire" local Wire = require "wire"
@ -31,6 +32,7 @@ function Room:initialize(t)
self.collideables = { red = {}, green = {}, blue = {} } self.collideables = { red = {}, green = {}, blue = {} }
self.playerBlockers = { red = {}, green = {}, blue = {} } self.playerBlockers = { red = {}, green = {}, blue = {} }
self.npcs = {} self.npcs = {}
self.notes = {}
self.activeColors = {red = false, green = false, blue = false} self.activeColors = {red = false, green = false, blue = false}
self.colorsPlayerHas = {} self.colorsPlayerHas = {}
@ -172,6 +174,16 @@ function Room:draw(drawChannels)
for _, switch in ipairs(self.wiredSwitches) do switch:draw(channel) end for _, switch in ipairs(self.wiredSwitches) do switch:draw(channel) end
for _, npc in ipairs(self.npcs) do npc:draw(channel) end for _, npc in ipairs(self.npcs) do npc:draw(channel) end
end end
-- Notes are an editor-only overlay: shown only while the editor build is
-- running (DEBUG) and the NOTES_VISIBLE toggle is on. Drawn once (not per
-- channel) in alpha so the faint icon reads as itself instead of screen-adding.
if DEBUG and NOTES_VISIBLE and #self.notes > 0 then
local mode, alphaMode = love.graphics.getBlendMode()
love.graphics.setBlendMode("alpha")
for _, note in ipairs(self.notes) do note:draw() end
love.graphics.setBlendMode(mode, alphaMode)
end
end end
function Room:wireTracksAt(cell, create) function Room:wireTracksAt(cell, create)
@ -739,6 +751,11 @@ function Room:createEntity(class, color, x, y, data)
self:registerNpc(entity) self:registerNpc(entity)
return entity return entity
end end
if class == "note" then
local entity = Note:new(x, y, data.text)
self:registerNote(entity)
return entity
end
local props = globalAssetProperties and globalAssetProperties[class] local props = globalAssetProperties and globalAssetProperties[class]
local behavior = props and props.class or "immoveable" local behavior = props and props.class or "immoveable"
@ -857,6 +874,22 @@ function Room:registerNpc(entity)
table.insert(self.npcs, entity) table.insert(self.npcs, entity)
end end
function Room:registerNote(entity)
-- Notes are like switches: a passable object kept in their own registry, so
-- serialize's note loop round-trips them and movement never sees them (their
-- "none" collision means canPassOver, so they are not stamped into a matrix).
table.insert(self.notes, entity)
end
-- The note (if any) under a given grid cell — used by the editor's hover/click.
function Room:noteAtCell(cell)
if not cell then return nil end
for _, note in ipairs(self.notes) do
if note:occupiesCell(cell) then return note end
end
return nil
end
function Room:getCellFromMousePos(mouseX, mouseY) function Room:getCellFromMousePos(mouseX, mouseY)
if gameWorld and gameWorld.levels and gameWorld.levels[self.name] == self and if gameWorld and gameWorld.levels and gameWorld.levels[self.name] == self and
not gameWorld.drawingRoomNeighborhood then not gameWorld.drawingRoomNeighborhood then
@ -933,6 +966,11 @@ function Room:attemptDeleteAtCell(cell, color)
for index = #self.npcs, 1, -1 do for index = #self.npcs, 1, -1 do
if self.npcs[index]:occupiesCell(cell) then table.remove(self.npcs, index) end if self.npcs[index]:occupiesCell(cell) then table.remove(self.npcs, index) end
end end
-- Notes are non-collidable and colourless, so a right-click removes them
-- regardless of the currently selected editor colour.
for index = #self.notes, 1, -1 do
if self.notes[index]:occupiesCell(cell) then table.remove(self.notes, index) end
end
self:tick() self:tick()
end end
@ -940,7 +978,19 @@ function Room:attemptDelete(mouseX, mouseY, color)
return self:attemptDeleteAtCell(self:getCellFromMousePos(mouseX, mouseY), color) return self:attemptDeleteAtCell(self:getCellFromMousePos(mouseX, mouseY), color)
end end
function Room:attemptPlaceAtCell(name, props, cell, color) function Room:attemptPlaceAtCell(name, props, cell, color, text)
-- Notes are colourless, single-instance annotations (no per-channel sprites),
-- so they place once carrying their text rather than one copy per colour. One
-- note per cell: drop any note already here before adding the new one.
if name == "note" then
if not cell then return end
for index = #self.notes, 1, -1 do
if self.notes[index]:occupiesCell(cell) then table.remove(self.notes, index) end
end
self:createEntity("note", nil, cell.x, cell.y, { text = text })
self:tick()
return
end
if not props or not props.sprites then return end if not props or not props.sprites then return end
if not cell then return end if not cell then return end
-- Jorge is one dialogable NPC with three display channels, not three -- Jorge is one dialogable NPC with three display channels, not three
@ -1101,6 +1151,10 @@ function Room:serialize(includePlayers)
table.insert(objects, { class = npc.saveClass or "npc", x = npc.x, y = npc.y }) table.insert(objects, { class = npc.saveClass or "npc", x = npc.x, y = npc.y })
end end
for _, note in ipairs(self.notes) do
table.insert(objects, { class = "note", x = note.x, y = note.y, text = note.text })
end
return { objects = objects, wires = self:serializeWires() } return { objects = objects, wires = self:serializeWires() }
end end

6
rooms/room_05_15.sav Normal file
View file

@ -0,0 +1,6 @@
{
objects={
},
wires={
}
}

711
rooms/sticky_test_5.sav Normal file
View file

@ -0,0 +1,711 @@
{
objects={
{
class="player",
x=4,
color="green",
active=true,
y=9
},
{
class="player",
x=4,
color="blue",
active=true,
y=9
},
{
class="player",
x=4,
color="red",
active=true,
y=9
},
{
color="green",
y=11,
class="static_wall",
x=1
},
{
color="green",
y=11,
class="static_wall",
x=2
},
{
color="green",
y=11,
class="static_wall",
x=3
},
{
color="green",
y=11,
class="static_wall",
x=4
},
{
color="green",
y=11,
class="static_wall",
x=5
},
{
color="green",
y=11,
class="static_wall",
x=6
},
{
color="green",
y=11,
class="static_wall",
x=7
},
{
color="green",
y=11,
class="static_wall",
x=8
},
{
color="green",
y=11,
class="static_wall",
x=9
},
{
color="green",
y=11,
class="static_wall",
x=10
},
{
color="green",
y=11,
class="static_wall",
x=11
},
{
color="green",
y=11,
class="static_wall",
x=11
},
{
color="green",
y=10,
class="static_wall",
x=11
},
{
color="green",
y=9,
class="static_wall",
x=11
},
{
color="green",
y=8,
class="static_wall",
x=11
},
{
color="green",
y=6,
class="static_wall",
x=11
},
{
color="green",
y=5,
class="static_wall",
x=11
},
{
color="green",
y=6,
class="static_wall",
x=11
},
{
color="green",
y=7,
class="static_wall",
x=11
},
{
color="green",
y=1,
class="static_wall",
x=11
},
{
color="green",
y=5,
class="static_wall",
x=11
},
{
color="green",
y=4,
class="static_wall",
x=11
},
{
color="green",
y=3,
class="static_wall",
x=11
},
{
color="green",
y=2,
class="static_wall",
x=11
},
{
color="green",
y=1,
class="static_wall",
x=11
},
{
color="green",
y=1,
class="static_wall",
x=11
},
{
color="green",
y=1,
class="static_wall",
x=10
},
{
color="green",
y=1,
class="static_wall",
x=9
},
{
color="green",
y=1,
class="static_wall",
x=8
},
{
color="green",
y=1,
class="static_wall",
x=7
},
{
color="green",
y=1,
class="static_wall",
x=6
},
{
color="green",
y=1,
class="static_wall",
x=5
},
{
color="green",
y=1,
class="static_wall",
x=4
},
{
color="green",
y=1,
class="static_wall",
x=3
},
{
color="green",
y=1,
class="static_wall",
x=2
},
{
color="green",
y=1,
class="static_wall",
x=1
},
{
color="green",
y=3,
class="david_small",
x=7
},
{
color="green",
y=3,
class="the_scream",
x=2
},
{
color="blue",
y=11,
class="static_wall",
x=1
},
{
color="blue",
y=11,
class="static_wall",
x=2
},
{
color="blue",
y=11,
class="static_wall",
x=3
},
{
color="blue",
y=11,
class="static_wall",
x=4
},
{
color="blue",
y=11,
class="static_wall",
x=5
},
{
color="blue",
y=11,
class="static_wall",
x=6
},
{
color="blue",
y=11,
class="static_wall",
x=7
},
{
color="blue",
y=11,
class="static_wall",
x=8
},
{
color="blue",
y=11,
class="static_wall",
x=9
},
{
color="blue",
y=11,
class="static_wall",
x=10
},
{
color="blue",
y=11,
class="static_wall",
x=11
},
{
color="blue",
y=11,
class="static_wall",
x=11
},
{
color="blue",
y=10,
class="static_wall",
x=11
},
{
color="blue",
y=9,
class="static_wall",
x=11
},
{
color="blue",
y=8,
class="static_wall",
x=11
},
{
color="blue",
y=6,
class="static_wall",
x=11
},
{
color="blue",
y=5,
class="static_wall",
x=11
},
{
color="blue",
y=6,
class="static_wall",
x=11
},
{
color="blue",
y=7,
class="static_wall",
x=11
},
{
color="blue",
y=1,
class="static_wall",
x=11
},
{
color="blue",
y=5,
class="static_wall",
x=11
},
{
color="blue",
y=4,
class="static_wall",
x=11
},
{
color="blue",
y=3,
class="static_wall",
x=11
},
{
color="blue",
y=2,
class="static_wall",
x=11
},
{
color="blue",
y=1,
class="static_wall",
x=11
},
{
color="blue",
y=1,
class="static_wall",
x=11
},
{
color="blue",
y=1,
class="static_wall",
x=10
},
{
color="blue",
y=1,
class="static_wall",
x=9
},
{
color="blue",
y=1,
class="static_wall",
x=8
},
{
color="blue",
y=1,
class="static_wall",
x=7
},
{
color="blue",
y=1,
class="static_wall",
x=6
},
{
color="blue",
y=1,
class="static_wall",
x=5
},
{
color="blue",
y=1,
class="static_wall",
x=4
},
{
color="blue",
y=1,
class="static_wall",
x=3
},
{
color="blue",
y=1,
class="static_wall",
x=2
},
{
color="blue",
y=1,
class="static_wall",
x=1
},
{
color="blue",
y=3,
class="david_small",
x=7
},
{
color="blue",
y=3,
class="the_scream",
x=2
},
{
color="red",
y=11,
class="static_wall",
x=1
},
{
color="red",
y=11,
class="static_wall",
x=2
},
{
color="red",
y=11,
class="static_wall",
x=3
},
{
color="red",
y=11,
class="static_wall",
x=4
},
{
color="red",
y=11,
class="static_wall",
x=5
},
{
color="red",
y=11,
class="static_wall",
x=6
},
{
color="red",
y=11,
class="static_wall",
x=7
},
{
color="red",
y=11,
class="static_wall",
x=8
},
{
color="red",
y=11,
class="static_wall",
x=9
},
{
color="red",
y=11,
class="static_wall",
x=10
},
{
color="red",
y=11,
class="static_wall",
x=11
},
{
color="red",
y=11,
class="static_wall",
x=11
},
{
color="red",
y=10,
class="static_wall",
x=11
},
{
color="red",
y=9,
class="static_wall",
x=11
},
{
color="red",
y=8,
class="static_wall",
x=11
},
{
color="red",
y=6,
class="static_wall",
x=11
},
{
color="red",
y=5,
class="static_wall",
x=11
},
{
color="red",
y=6,
class="static_wall",
x=11
},
{
color="red",
y=7,
class="static_wall",
x=11
},
{
color="red",
y=1,
class="static_wall",
x=11
},
{
color="red",
y=5,
class="static_wall",
x=11
},
{
color="red",
y=4,
class="static_wall",
x=11
},
{
color="red",
y=3,
class="static_wall",
x=11
},
{
color="red",
y=2,
class="static_wall",
x=11
},
{
color="red",
y=1,
class="static_wall",
x=11
},
{
color="red",
y=1,
class="static_wall",
x=11
},
{
color="red",
y=1,
class="static_wall",
x=10
},
{
color="red",
y=1,
class="static_wall",
x=9
},
{
color="red",
y=1,
class="static_wall",
x=8
},
{
color="red",
y=1,
class="static_wall",
x=7
},
{
color="red",
y=1,
class="static_wall",
x=6
},
{
color="red",
y=1,
class="static_wall",
x=5
},
{
color="red",
y=1,
class="static_wall",
x=4
},
{
color="red",
y=1,
class="static_wall",
x=3
},
{
color="red",
y=1,
class="static_wall",
x=2
},
{
color="red",
y=1,
class="static_wall",
x=1
},
{
color="red",
y=3,
class="david_small",
x=7
},
{
color="red",
y=3,
class="the_scream",
x=2
}
},
wires={
}
}

1139
rooms/testing_sticky_3.sav Normal file

File diff suppressed because it is too large Load diff

831
rooms/testing_sticky_4.sav Normal file
View file

@ -0,0 +1,831 @@
{
objects={
{
color="red",
x=1,
class="static_wall",
y=11
},
{
color="red",
x=2,
class="static_wall",
y=11
},
{
color="red",
x=3,
class="static_wall",
y=11
},
{
color="red",
x=4,
class="static_wall",
y=11
},
{
color="red",
x=5,
class="static_wall",
y=11
},
{
color="red",
x=6,
class="static_wall",
y=11
},
{
color="red",
x=7,
class="static_wall",
y=11
},
{
color="red",
x=8,
class="static_wall",
y=11
},
{
color="red",
x=9,
class="static_wall",
y=11
},
{
color="red",
x=10,
class="static_wall",
y=11
},
{
color="red",
x=11,
class="static_wall",
y=11
},
{
color="red",
x=11,
class="static_wall",
y=10
},
{
color="red",
x=11,
class="static_wall",
y=8
},
{
color="red",
x=11,
class="static_wall",
y=7
},
{
color="red",
x=11,
class="static_wall",
y=6
},
{
color="red",
x=11,
class="static_wall",
y=3
},
{
color="red",
x=11,
class="static_wall",
y=2
},
{
color="red",
x=11,
class="static_wall",
y=3
},
{
color="red",
x=11,
class="static_wall",
y=4
},
{
color="red",
x=11,
class="static_wall",
y=5
},
{
color="red",
x=11,
class="static_wall",
y=4
},
{
color="red",
x=11,
class="static_wall",
y=3
},
{
color="red",
x=11,
class="static_wall",
y=2
},
{
color="red",
x=11,
class="static_wall",
y=1
},
{
color="red",
x=10,
class="static_wall",
y=1
},
{
color="red",
x=9,
class="static_wall",
y=1
},
{
color="red",
x=8,
class="static_wall",
y=1
},
{
color="red",
x=7,
class="static_wall",
y=1
},
{
color="red",
x=5,
class="static_wall",
y=1
},
{
color="red",
x=4,
class="static_wall",
y=1
},
{
color="red",
x=3,
class="static_wall",
y=1
},
{
color="red",
x=2,
class="static_wall",
y=1
},
{
color="red",
x=1,
class="static_wall",
y=1
},
{
color="red",
x=11,
class="door",
y=9
},
{
color="red",
x=2,
class="static_wall",
y=5
},
{
color="red",
x=2,
class="static_wall",
y=9
},
{
color="red",
x=6,
class="static_wall",
y=1
},
{
color="green",
x=1,
class="static_wall",
y=11
},
{
color="green",
x=2,
class="static_wall",
y=11
},
{
color="green",
x=3,
class="static_wall",
y=11
},
{
color="green",
x=4,
class="static_wall",
y=11
},
{
color="green",
x=5,
class="static_wall",
y=11
},
{
color="green",
x=6,
class="static_wall",
y=11
},
{
color="green",
x=7,
class="static_wall",
y=11
},
{
color="green",
x=8,
class="static_wall",
y=11
},
{
color="green",
x=9,
class="static_wall",
y=11
},
{
color="green",
x=10,
class="static_wall",
y=11
},
{
color="green",
x=11,
class="static_wall",
y=11
},
{
color="green",
x=11,
class="static_wall",
y=10
},
{
color="green",
x=11,
class="static_wall",
y=8
},
{
color="green",
x=11,
class="static_wall",
y=7
},
{
color="green",
x=11,
class="static_wall",
y=6
},
{
color="green",
x=11,
class="static_wall",
y=3
},
{
color="green",
x=11,
class="static_wall",
y=2
},
{
color="green",
x=11,
class="static_wall",
y=3
},
{
color="green",
x=11,
class="static_wall",
y=4
},
{
color="green",
x=11,
class="static_wall",
y=5
},
{
color="green",
x=11,
class="static_wall",
y=4
},
{
color="green",
x=11,
class="static_wall",
y=3
},
{
color="green",
x=11,
class="static_wall",
y=2
},
{
color="green",
x=11,
class="static_wall",
y=1
},
{
color="green",
x=10,
class="static_wall",
y=1
},
{
color="green",
x=9,
class="static_wall",
y=1
},
{
color="green",
x=8,
class="static_wall",
y=1
},
{
color="green",
x=7,
class="static_wall",
y=1
},
{
color="green",
x=5,
class="static_wall",
y=1
},
{
color="green",
x=4,
class="static_wall",
y=1
},
{
color="green",
x=3,
class="static_wall",
y=1
},
{
color="green",
x=2,
class="static_wall",
y=1
},
{
color="green",
x=1,
class="static_wall",
y=1
},
{
color="green",
x=11,
class="door",
y=9
},
{
color="green",
x=2,
class="static_wall",
y=5
},
{
color="green",
x=2,
class="static_wall",
y=9
},
{
color="green",
x=6,
class="static_wall",
y=1
},
{
color="blue",
x=1,
class="static_wall",
y=11
},
{
color="blue",
x=2,
class="static_wall",
y=11
},
{
color="blue",
x=3,
class="static_wall",
y=11
},
{
color="blue",
x=4,
class="static_wall",
y=11
},
{
color="blue",
x=5,
class="static_wall",
y=11
},
{
color="blue",
x=6,
class="static_wall",
y=11
},
{
color="blue",
x=7,
class="static_wall",
y=11
},
{
color="blue",
x=8,
class="static_wall",
y=11
},
{
color="blue",
x=9,
class="static_wall",
y=11
},
{
color="blue",
x=10,
class="static_wall",
y=11
},
{
color="blue",
x=11,
class="static_wall",
y=11
},
{
color="blue",
x=11,
class="static_wall",
y=10
},
{
color="blue",
x=11,
class="static_wall",
y=8
},
{
color="blue",
x=11,
class="static_wall",
y=7
},
{
color="blue",
x=11,
class="static_wall",
y=6
},
{
color="blue",
x=11,
class="static_wall",
y=3
},
{
color="blue",
x=11,
class="static_wall",
y=2
},
{
color="blue",
x=11,
class="static_wall",
y=3
},
{
color="blue",
x=11,
class="static_wall",
y=4
},
{
color="blue",
x=11,
class="static_wall",
y=5
},
{
color="blue",
x=11,
class="static_wall",
y=4
},
{
color="blue",
x=11,
class="static_wall",
y=3
},
{
color="blue",
x=11,
class="static_wall",
y=2
},
{
color="blue",
x=11,
class="static_wall",
y=1
},
{
color="blue",
x=10,
class="static_wall",
y=1
},
{
color="blue",
x=9,
class="static_wall",
y=1
},
{
color="blue",
x=8,
class="static_wall",
y=1
},
{
color="blue",
x=7,
class="static_wall",
y=1
},
{
color="blue",
x=5,
class="static_wall",
y=1
},
{
color="blue",
x=4,
class="static_wall",
y=1
},
{
color="blue",
x=3,
class="static_wall",
y=1
},
{
color="blue",
x=2,
class="static_wall",
y=1
},
{
color="blue",
x=1,
class="static_wall",
y=1
},
{
color="blue",
x=11,
class="door",
y=9
},
{
color="blue",
x=2,
class="static_wall",
y=5
},
{
color="blue",
x=2,
class="static_wall",
y=9
},
{
color="blue",
x=6,
class="static_wall",
y=1
},
{
color="red",
x=7,
class="player_blocker",
y=5
},
{
color="red",
x=8,
class="player_blocker",
y=5
},
{
color="red",
x=9,
class="player_blocker",
y=5
},
{
color="red",
x=10,
class="player_blocker",
y=5
},
{
color="red",
x=7,
class="player_blocker",
y=2
},
{
color="red",
x=7,
class="player_blocker",
y=3
},
{
color="red",
x=7,
class="player_blocker",
y=4
},
{
color="green",
x=7,
class="player_blocker",
y=5
},
{
color="green",
x=8,
class="player_blocker",
y=5
},
{
color="green",
x=9,
class="player_blocker",
y=5
},
{
color="green",
x=10,
class="player_blocker",
y=5
},
{
color="green",
x=7,
class="player_blocker",
y=2
},
{
color="green",
x=7,
class="player_blocker",
y=3
},
{
color="green",
x=7,
class="player_blocker",
y=4
},
{
color="blue",
x=7,
class="player_blocker",
y=5
},
{
color="blue",
x=8,
class="player_blocker",
y=5
},
{
color="blue",
x=9,
class="player_blocker",
y=5
},
{
color="blue",
x=10,
class="player_blocker",
y=5
},
{
color="blue",
x=7,
class="player_blocker",
y=2
},
{
color="blue",
x=7,
class="player_blocker",
y=3
},
{
color="blue",
x=7,
class="player_blocker",
y=4
},
{
color="red",
x=10,
class="switch",
y=2
},
{
color="green",
x=10,
class="switch",
y=2
},
{
color="blue",
x=10,
class="switch",
y=2
},
{
text="right now it's possible to like...strand the sticky block\
here if it's at the end of one of the Ls and you push it with the other l.\
\
\
is this something we can address? it would require the reset\
block but idk. the only other thing is making it so\
there's always a sticky and one non sticky. if you have\
mopre than one nonsticky it will always be possible to strand \
at certain depths of playerblockers.\
",
x=5,
class="note",
y=3
}
},
wires={
}
}

View file

@ -1,272 +1,292 @@
{ {
width=18,
height=18,
lastRoom="testing_sticky",
rooms={ rooms={
{ {
y=10, y=10,
x=10, name="3",
name="3" x=10
}, },
{ {
y=10, y=10,
x=12, name="5",
name="5" x=12
}, },
{ {
y=9, y=9,
x=12, name="village_1",
name="village_1" x=12
}, },
{ {
y=8, y=8,
x=11, name="village_4",
name="village_4" x=11
}, },
{ {
y=7, y=7,
x=11, name="yellow_1",
name="yellow_1" x=11
}, },
{ {
y=6, y=6,
x=11, name="yellow_3",
name="yellow_3" x=11
}, },
{ {
y=7, y=7,
x=12, name="yellow_2",
name="yellow_2" x=12
}, },
{ {
y=5, y=5,
x=11, name="yellow_4",
name="yellow_4" x=11
}, },
{ {
y=4, y=4,
x=11, name="yellow_5",
name="yellow_5" x=11
}, },
{ {
y=5, y=5,
x=10, name="yellow_6",
name="yellow_6" x=10
}, },
{ {
y=1, y=1,
x=1, name="default",
name="default" x=1
}, },
{ {
y=1, y=1,
x=3, name="old_yellow_3",
name="old_yellow_3" x=3
}, },
{ {
y=1, y=1,
x=5, name="test_patt",
name="test_patt" x=5
}, },
{ {
y=1, y=1,
x=7, name="test_patt_update",
name="test_patt_update" x=7
}, },
{ {
y=1, y=1,
x=9, name="white_asymmetrical",
name="white_asymmetrical" x=9
}, },
{ {
y=1, y=1,
x=11, name="white_ok",
name="white_ok" x=11
}, },
{ {
y=1, y=1,
x=13, name="white_primary",
name="white_primary" x=13
}, },
{ {
y=1, y=1,
x=15, name="yellow_arches",
name="yellow_arches" x=15
}, },
{ {
y=1, y=1,
x=17, name="yellow_asymmetrical",
name="yellow_asymmetrical" x=17
}, },
{ {
y=3, y=3,
x=1, name="yellow_cc_moustache",
name="yellow_cc_moustache" x=1
}, },
{ {
y=3, y=3,
x=3, name="yellow_color_changing_levvel_1",
name="yellow_color_changing_levvel_1" x=3
}, },
{ {
y=3, y=3,
x=7, name="yellow_smile",
name="yellow_smile" x=7
}, },
{ {
y=10, y=10,
x=9, name="2",
name="2" x=9
}, },
{ {
y=9, y=9,
x=11, name="room_11_09",
name="room_11_09" x=11
}, },
{ {
y=3, y=3,
x=9, name="yellow_skull",
name="yellow_skull" x=9
}, },
{ {
y=5, y=5,
x=5, name="jorge_room",
name="jorge_room" x=5
}, },
{ {
y=10, y=10,
x=11, name="4",
name="4" x=11
}, },
{ {
y=5, y=5,
x=1, name="room_01_05",
name="room_01_05" x=1
}, },
{ {
y=8, y=8,
x=2, name="ok",
name="ok" x=2
}, },
{ {
y=18, y=18,
x=5, name="room_05_18",
name="room_05_18" x=5
}, },
{ {
y=11, y=11,
x=9, name="village_start",
name="village_start" x=9
}, },
{ {
y=8, y=8,
x=4, name="spiky_test",
name="spiky_test" x=4
}, },
{ {
y=7, y=7,
x=5, name="corner_trimino_test",
name="corner_trimino_test" x=5
}, },
{ {
y=7, y=7,
x=6, name="tetromino_loop_t",
name="tetromino_loop_t" x=6
}, },
{ {
y=6, y=6,
x=5, name="corner_trimino_test_copy",
name="corner_trimino_test_copy" x=5
}, },
{ {
y=7, y=7,
x=7, name="blocker_test",
name="blocker_test" x=7
}, },
{ {
y=8, y=8,
x=5, name="spiky_test_2",
name="spiky_test_2" x=5
}, },
{ {
y=9, y=9,
x=5, name="spiky_test_3",
name="spiky_test_3" x=5
}, },
{ {
y=14, y=14,
x=3, name="jorge2",
name="jorge2" x=3
}, },
{ {
y=7, y=7,
x=8, name="room_08_07",
name="room_08_07" x=8
}, },
{ {
y=8, y=8,
x=13, name="village_3",
name="village_3" x=13
}, },
{ {
y=8, y=8,
x=12, name="village_2",
name="village_2" x=12
}, },
{ {
y=7, y=7,
x=14, name="room_14_07",
name="room_14_07" x=14
}, },
{ {
y=13, y=13,
x=9, name="room_09_13",
name="room_09_13" x=9
}, },
{ {
y=13, y=13,
x=8, name="room_08_13",
name="room_08_13" x=8
}, },
{ {
y=15, y=15,
x=9, name="room_10_15",
name="room_10_15" x=9
}, },
{ {
y=14, y=14,
x=14, name="room_14_14",
name="room_14_14" x=14
}, },
{ {
y=13, y=13,
x=13, name="room_13_13",
name="room_13_13" x=13
}, },
{ {
y=14, y=14,
x=12, name="room_12_14",
name="room_12_14" x=12
}, },
{ {
y=14, y=14,
x=13, name="room_13_14",
name="room_13_14" x=13
}, },
{ {
y=13, y=13,
x=5, name="testing_sticky",
name="testing_sticky" x=5
}, },
{ {
y=13, y=13,
x=6, name="testing_sticky_two",
name="testing_sticky_two" x=6
}, },
{ {
y=14, y=14,
x=5, name="room_05_14",
name="room_05_14" x=5
},
{
y=16,
name="testing_sticky_3",
x=5
},
{
y=16,
name="testing_sticky_4",
x=6
},
{
y=16,
name="sticky_test_5",
x=7
},
{
y=15,
name="room_05_15",
x=5
} }
} },
width=18,
height=18,
lastRoom="testing_sticky_3"
} }