chroma_solstice/mark.lua

79 lines
3.1 KiB
Lua
Raw Permalink Normal View History

require "entity"
-- A named destination on the grid. NPCs are sent to a Mark by name (see
-- Npc:sendTo / World:markByName), so the name is the whole point of the object.
--
-- A Mark is a *debug non-collidable*: an editor-only annotation that everything
-- passes through, shown only while the debug overlay is on. It is NOT a Note and
-- shares no code with one — the two just happen to be siblings in that category.
-- Its icon is a target, drawn procedurally so it needs no art asset.
Mark = class("Mark", Entity)
-- Entity only ever calls :getWidth/:getHeight on the sizing sprite (Mark:draw
-- paints a target instead of blitting it), so a lightweight stub is enough and
-- keeps Mark free of any borrowed artwork.
local sizeStub = { getWidth = function() return 16 end, getHeight = function() return 16 end }
-- The target is pixel art: a 16x16 bullseye (two rings + a centre dot) baked once
-- into a list of on-pixels, drawn as blocky rectangles so it stays crisp at the
-- game's nearest-neighbour scale instead of a smooth vector circle.
local GRID = 16
local targetPixels = {}
do
local c = (GRID - 1) / 2 -- 7.5, the sub-pixel centre
for row = 0, GRID - 1 do
for col = 0, GRID - 1 do
local dist = math.sqrt((col - c) ^ 2 + (row - c) ^ 2)
local on = math.abs(dist - 6.5) < 0.7 -- outer ring
or math.abs(dist - 3.5) < 0.7 -- inner ring
or dist < 1.3 -- centre dot
if on then targetPixels[#targetPixels + 1] = { col, row } end
end
end
end
function Mark:initialize(x, y, name)
self.markName = name or ""
self.saveClass = "mark"
-- programmatic marks are created from code on every room load, so they are
-- skipped by Room:serialize (see there) to avoid duplicating on disk.
self.programmatic = false
Entity.initialize(self, {
x = x, y = y,
color = "white", -- representative only; Mark:draw is channel-agnostic
collision = "none",
sprite = sizeStub,
})
end
-- Drawn once (not per-channel) from the room's debug overlay pass, so it takes no
-- channel argument. A faint pixel-art red target with its name beside it, kept at
-- the same low opacity as a Note so it reads as an annotation, not a game object.
function Mark:draw()
local p = self:getDrawPos()
love.graphics.setColor(1, 0.3, 0.25, 0.85)
for _, px in ipairs(targetPixels) do
love.graphics.rectangle("fill", p.x + px[1] * drawScale, p.y + px[2] * drawScale, drawScale, drawScale)
end
if self.markName ~= "" then
love.graphics.setColor(1, 0.5, 0.42, 0.85)
love.graphics.print(self.markName, p.x, p.y - 12)
end
love.graphics.setColor(1, 1, 1, 1)
end
-- The same pixel-art target baked into a 16x16 Image, for the editor toolbar
-- button (full opacity there — it's a control, not a faint overlay). Cached, and
-- built lazily so it only touches love.graphics once the editor needs it.
function Mark.static.icon()
if Mark.static._icon then return Mark.static._icon end
local data = love.image.newImageData(GRID, GRID)
for _, px in ipairs(targetPixels) do
data:setPixel(px[1], px[2], 1, 0.3, 0.25, 1)
end
local img = love.graphics.newImage(data)
img:setFilter("nearest", "nearest")
Mark.static._icon = img
return img
end