diff --git a/room.lua b/room.lua index 09b5523..a906c69 100644 --- a/room.lua +++ b/room.lua @@ -239,12 +239,21 @@ function Room:drawWiresAt(originX, originY, cellSize, selectedCircuit, editorCol for _, line in ipairs(lines) do love.graphics.line(line) end local wireBlend = (editorColors or not switchColors) and "alpha" or "screen" love.graphics.setBlendMode(wireBlend) + local previousShader = love.graphics.getShader() + if not editorColors and switchColors and wireNeonShader then + local powered = gameWorld:wirePoweredAt(self, localCell, circuitId) + wireNeonShader:send("wirePower", powered and 1 or 0) + wireNeonShader:send("wireTime", love.timer.getTime()) + wireNeonShader:send("wirePhase", (#circuitId * 0.137) + x * 0.071 + y * 0.113) + love.graphics.setShader(wireNeonShader) + end love.graphics.setLineWidth(math.max(1, cellSize * 0.045)) for _, color in ipairs(drawColors) do love.graphics.setColor(color[1], color[2], color[3], selected and 1 or 0.32) for _, line in ipairs(lines) do love.graphics.line(line) end love.graphics.circle("fill", cx, cy, math.max(0.75, cellSize * 0.0275)) end + love.graphics.setShader(previousShader) end end end diff --git a/shaders.lua b/shaders.lua index 491bac7..168a895 100644 --- a/shaders.lua +++ b/shaders.lua @@ -89,6 +89,45 @@ goopShader = love.graphics.newShader[[ } ]] +-- Wires are drawn as primitives rather than textured sprites, but LÖVE still +-- runs this shader over its default white texture. Inactive runs stay visibly +-- connected at half brightness; powered runs get a travelling neon shimmer, +-- occasional tube-like noise, and a sub-pixel bend rather than a global fade. +wireNeonShader = love.graphics.newShader[[ + extern number wirePower; + extern number wireTime; + extern number wirePhase; + + float wireHash(vec2 point) { + return fract(sin(dot(point, vec2(127.1, 311.7))) * 43758.5453123); + } + + vec4 position(mat4 transformProjection, vec4 vertexPosition) { + if (wirePower < 0.5) return transformProjection * vertexPosition; + + // Every segment sharing an endpoint receives the same offset, so corners + // stay connected while the live current gives the tube a gentle movement. + float wave = sin(vertexPosition.x * 0.085 + vertexPosition.y * 0.13 + + wireTime * 4.4 + wirePhase * 9.0); + float wobble = wave * 0.32; + vertexPosition.xy += vec2(wobble, -wobble * 0.55); + return transformProjection * vertexPosition; + } + + vec4 effect(vec4 color, Image texture, vec2 textureCoords, vec2 screenCoords) { + if (wirePower < 0.5) return vec4(color.rgb * 0.50, color.a * 0.72); + + float travel = dot(screenCoords, vec2(0.070, 0.043)) - wireTime * 6.8 + wirePhase * 13.0; + float shimmer = sin(travel) * 0.075 + sin(travel * 2.37 + 1.7) * 0.035; + vec2 noiseCell = floor(screenCoords * 0.34) + floor(wireTime * 15.0) * vec2(1.0, -1.0); + float noise = wireHash(noiseCell + wirePhase); + float flicker = max(0.0, noise - 0.82) * 0.32; + float brightness = 0.92 + shimmer + flicker; + float alpha = 0.84 + shimmer * 0.80 + flicker * 0.55; + return vec4(color.rgb * brightness, color.a * alpha); + } +]] + function drawGoopySprite(sprite, x, y, scale) local w, h = sprite:getWidth(), sprite:getHeight() local time = love.timer.getTime() diff --git a/tests/test_wire.lua b/tests/test_wire.lua index 4e9c47d..adbf404 100644 --- a/tests/test_wire.lua +++ b/tests/test_wire.lua @@ -169,6 +169,15 @@ test("a switchless wire component has no gameplay color", function() assertTrue(world:wireColorAt(roomA, {x = 1, y = 1}, "circuit_a") == nil) end) +test("wire power follows its terminal switch", function() + local world, roomA = makeWorld() + world:resolveCircuits() + assertFalse(world:wirePoweredAt(roomA, {x = 1, y = 1}, "circuit_a")) + world.collidableMatrices.blue[1] = {[4] = {}} + world:resolveCircuits() + assertTrue(world:wirePoweredAt(roomA, {x = 1, y = 1}, "circuit_a")) +end) + test("a door requires every independent switch run at its terminal", function() local greenSwitch = switch("green", 1, 2) local world, roomA, _, wiredDoor = makeWorld() diff --git a/world.lua b/world.lua index 68cf5cd..e455ba8 100644 --- a/world.lua +++ b/world.lua @@ -494,6 +494,20 @@ function World:wireSwitchColorsAt(room, localCell, circuitId) return colors end +-- A valid wire is bright only while every switch at its sole terminal cell is +-- pressed. Rendering reads this derived state rather than entity sprite flags +-- so every segment of a cross-room component agrees in the same frame. +function World:wirePoweredAt(room, localCell, circuitId) + local worldCell = self:worldCell(room, localCell) + local component = worldCell and self.circuitComponents and + self.circuitComponents[circuitId] and self.circuitComponents[circuitId][cellKey(worldCell)] + if not component or not component.valid then return false end + for switch in pairs(component.switches) do + if not (self.circuitState and self.circuitState.switchPressed[switch]) then return false end + end + return true +end + function World:wireColorAt(room, localCell, circuitId, visibleColors) local colors = self:wireSwitchColorsAt(room, localCell, circuitId) if not colors then return nil end