summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--conf.lua6
-rw-r--r--main.lua216
-rw-r--r--shaders/missing_texture.glsl8
-rw-r--r--world.lua2
4 files changed, 216 insertions, 16 deletions
diff --git a/conf.lua b/conf.lua
index c41f6d6..611800d 100644
--- a/conf.lua
+++ b/conf.lua
@@ -10,6 +10,12 @@ function love.conf(t)
t.audio.mic = false -- Request and use microphone capabilities in Android (boolean)
t.audio.mixwithsystem = true -- Keep background music playing when opening LOVE (boolean, iOS and Android only)
+ t.window.title = "Openformer"
+ t.window.width = 1280
+ t.window.height = 720
+ t.window.resizable = true
+ t.window.minwidth = 800
+ t.window.minheight = 450
t.window.vsync = 1 -- Vertical sync mode (number)
t.window.msaa = 0 -- The number of samples to use with multi-sampled antialiasing (number)
t.window.depth = nil -- The number of bits per sample in the depth buffer
diff --git a/main.lua b/main.lua
index 0d66c98..608d82a 100644
--- a/main.lua
+++ b/main.lua
@@ -1,7 +1,7 @@
local VIRTUAL_WIDTH, VIRTUAL_HEIGHT = 16*10*5, 9*10*5
local CANVAS_PADDING = 6
-DEBUG = false
+DEBUG = true
local CANVAS_WIDTH = VIRTUAL_WIDTH + CANVAS_PADDING
local CANVAS_HEIGHT = VIRTUAL_HEIGHT + CANVAS_PADDING
@@ -13,6 +13,10 @@ local offsetX, offsetY = 0, 0
local dpiScale = 1
local canvas = nil
local smoothCameraShader = nil
+local menuBgShader = nil
+local liquidShader = nil
+local menuCanvas = nil
+local menuTime = 0
local shaderEnabled = true
local cameraModule = require("camera")
@@ -22,7 +26,7 @@ local HUD = require("hud")
local hud = nil
local previousState = nil
-local currentState = "game"
+local currentState = "menu"
local currentMapPath = "assets/maps/tilemap.lua"
local currentTilesets = {
tileset = { path = "assets/maps/tileset.png", tilewidth = 16, tileheight = 16 },
@@ -30,6 +34,15 @@ local currentTilesets = {
}
local world = nil
+local menuItems = {}
+local menuHover = nil
+
+local gameFinishPhase = nil
+local gameFinishTimer = 0
+local irisRadius = 0
+local IRIS_CLOSE_DURATION = 1.5
+local TEXT_DISPLAY_DURATION = 5.0
+
local states = {
game = {},
menu = {},
@@ -39,41 +52,185 @@ local states = {
local function recalcScale(w, h)
dpiScale = (love.window.getDPIScale and love.window.getDPIScale()) or 1
- scale = math.max(1, math.floor(math.min(w / VIRTUAL_WIDTH, h / VIRTUAL_HEIGHT) / dpiScale))
+ scale = math.max(1, math.min(w / VIRTUAL_WIDTH, h / VIRTUAL_HEIGHT) / dpiScale)
finalScale = scale * dpiScale
offsetX = math.floor((w - VIRTUAL_WIDTH * finalScale) / 2)
offsetY = math.floor((h - VIRTUAL_HEIGHT * finalScale) / 2)
end
+local function screenToCanvas(sx, sy)
+ local drawX = offsetX - (CANVAS_PADDING * finalScale) / 2
+ local drawY = offsetY - (CANVAS_PADDING * finalScale) / 2
+ return (sx - drawX) / finalScale, (sy - drawY) / finalScale
+end
+
+local function buildMenuItems()
+ local btnW, btnH = 120, 32
+ local centerX = CANVAS_WIDTH / 2
+ local centerY = CANVAS_HEIGHT / 2
+ local spacing = 12
+ local totalH = 2 * btnH + spacing
+ local startY = centerY - totalH / 2 + 20
+ menuItems = {
+ { label = "Play", x = centerX - btnW / 2, y = startY, w = btnW, h = btnH, action = "play" },
+ { label = "Exit", x = centerX - btnW / 2, y = startY + btnH + spacing, w = btnW, h = btnH, action = "exit" },
+ }
+end
+
function states.game.load()
local World = require("world")
world = World:new()
world:load(currentMapPath, currentTilesets)
local target = world:getPlayer() or { x = 0, y = 0, width = 16, height = 16 }
- camera = cameraModule:new(target, VIRTUAL_WIDTH/2, VIRTUAL_HEIGHT/2, true, WORLD_TO_CANVAS)
+ camera = cameraModule:new(target, VIRTUAL_WIDTH / 2, VIRTUAL_HEIGHT / 2, true, WORLD_TO_CANVAS)
world:setCamera(camera)
hud = HUD:new()
+ gameFinishPhase = nil
+ gameFinishTimer = 0
+ irisRadius = 0
end
function states.game.update(dt)
- if world then world:update(dt) end
+ if not world then return end
+
+ if not gameFinishPhase then
+ world:update(dt)
+ if world.doorOpened then
+ gameFinishPhase = "iris"
+ gameFinishTimer = 0
+ irisRadius = math.sqrt((CANVAS_WIDTH / 2) ^ 2 + (CANVAS_HEIGHT / 2) ^ 2)
+ end
+ elseif gameFinishPhase == "iris" then
+ world:update(dt)
+ gameFinishTimer = gameFinishTimer + dt
+ local maxR = math.sqrt((CANVAS_WIDTH / 2) ^ 2 + (CANVAS_HEIGHT / 2) ^ 2)
+ local progress = math.min(gameFinishTimer / IRIS_CLOSE_DURATION, 1)
+ local eased = 1 - (1 - progress) ^ 2
+ irisRadius = maxR * (1 - eased)
+ if progress >= 1 then
+ gameFinishPhase = "text"
+ gameFinishTimer = 0
+ irisRadius = 0
+ end
+ elseif gameFinishPhase == "text" then
+ gameFinishTimer = gameFinishTimer + dt
+ if gameFinishTimer >= TEXT_DISPLAY_DURATION then
+ gameFinishPhase = nil
+ gameFinishTimer = 0
+ currentState = "menu"
+ states.menu.load()
+ end
+ end
end
function states.game.draw()
- if camera then camera:set() end
- if world then world:draw() end
- if camera then camera:unset() end
+ if gameFinishPhase ~= "text" then
+ if camera then camera:set() end
+ if world then world:draw() end
+ if camera then camera:unset() end
+ end
+
+ if gameFinishPhase == "iris" then
+ local cx = CANVAS_WIDTH / 2
+ local cy = CANVAS_HEIGHT / 2
+ local outerR = math.sqrt(cx * cx + cy * cy) + 10
+ local innerR = math.max(irisRadius, 0)
+ local segments = 64
+ local verts = {}
+ for i = 0, segments do
+ local angle = (i / segments) * math.pi * 2
+ local cs, sn = math.cos(angle), math.sin(angle)
+ verts[#verts + 1] = { cx + cs * innerR, cy + sn * innerR, 0, 0, 0, 0, 0, 1 }
+ verts[#verts + 1] = { cx + cs * outerR, cy + sn * outerR, 0, 0, 0, 0, 0, 1 }
+ end
+ local mesh = love.graphics.newMesh(verts, "strip", "stream")
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.draw(mesh)
+ elseif gameFinishPhase == "text" then
+ love.graphics.setColor(0, 0, 0, 1)
+ love.graphics.rectangle("fill", 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT)
+ local font = fonts.get(14)
+ love.graphics.setFont(font)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.printf("The Frog Escaped the Evil Aquarium", 0, CANVAS_HEIGHT / 2 - font:getHeight() / 2, CANVAS_WIDTH, "center")
+ love.graphics.setFont(fonts.default)
+ end
end
function states.menu.load()
camera = nil
world = nil
+ hud = nil
+ gameFinishPhase = nil
+ gameFinishTimer = 0
+ irisRadius = 0
+ menuTime = 0
+ love.mouse.setVisible(true)
+ menuHover = nil
end
-function states.menu.update(dt) end
+function states.menu.update(dt)
+ menuTime = menuTime + dt
+ local mx, my = love.mouse.getPosition()
+ local cx, cy = screenToCanvas(mx, my)
+ menuHover = nil
+ for _, btn in ipairs(menuItems) do
+ if cx >= btn.x and cx <= btn.x + btn.w and cy >= btn.y and cy <= btn.y + btn.h then
+ menuHover = btn.action
+ break
+ end
+ end
+end
function states.menu.draw()
- love.graphics.print("Menu (state machine ready)", 10, 10)
+ local prevCanvas = love.graphics.getCanvas()
+
+ love.graphics.setCanvas(menuCanvas)
+ love.graphics.clear()
+ if menuBgShader then
+ love.graphics.setShader(menuBgShader)
+ end
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.rectangle("fill", 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT)
+ love.graphics.setShader()
+ love.graphics.setCanvas(prevCanvas)
+
+ if liquidShader then
+ liquidShader:send("scene", menuCanvas)
+ liquidShader:send("time", menuTime)
+ liquidShader:send("resolution", { CANVAS_WIDTH, CANVAS_HEIGHT })
+ liquidShader:send("splashCenter", { -10.0, -10.0 })
+ liquidShader:send("splashTime", 0.0)
+ love.graphics.setShader(liquidShader)
+ end
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.draw(menuCanvas, 0, 0)
+ love.graphics.setShader()
+
+ local titleFont = fonts.get(20)
+ love.graphics.setFont(titleFont)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.printf("The Frog Escapes Evil Aquarium", 0, CANVAS_HEIGHT / 2 - 80, CANVAS_WIDTH, "center")
+
+ local btnFont = fonts.get(20)
+ love.graphics.setFont(btnFont)
+ for _, btn in ipairs(menuItems) do
+ local hover = menuHover == btn.action
+
+ if hover then
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setLineWidth(1)
+ love.graphics.rectangle("line", btn.x, btn.y, btn.w, btn.h)
+ end
+
+ love.graphics.setColor(1, 1, 1, 1)
+ local tw = btnFont:getWidth(btn.label)
+ local th = btnFont:getHeight()
+ love.graphics.print(btn.label, btn.x + btn.w / 2 - tw / 2, btn.y + btn.h / 2 - th / 2)
+ end
+
+ love.graphics.setFont(fonts.default)
+ love.graphics.setColor(1, 1, 1, 1)
end
function states.settings.load()
@@ -103,7 +260,7 @@ end
function love.load()
love.graphics.setDefaultFilter("nearest", "nearest")
- love.window.setTitle("Openformer")
+ love.window.setTitle("The Frog Escapes Evil Aquarium")
fonts.load()
love.graphics.setFont(fonts.default)
@@ -116,10 +273,20 @@ function love.load()
print("Warning: smooth_camera.glsl not loaded, using fallback (no sub-pixel offset)")
end
+ local ok2, shader2 = pcall(love.graphics.newShader, "shaders/missing_texture.glsl")
+ menuBgShader = ok2 and shader2 or nil
+
+ local ok3, shader3 = pcall(love.graphics.newShader, "shaders/liquid.glsl")
+ liquidShader = ok3 and shader3 or nil
+
+ menuCanvas = love.graphics.newCanvas(CANVAS_WIDTH, CANVAS_HEIGHT)
+ menuCanvas:setFilter("nearest", "nearest")
+
local w, h = love.graphics.getWidth(), love.graphics.getHeight()
recalcScale(w, h)
+ buildMenuItems()
- love.mouse.setVisible(false)
+ love.mouse.setVisible(true)
local state = states[currentState]
if state and state.load then state.load() end
@@ -130,6 +297,9 @@ function love.resize(w, h)
if canvas then canvas:release() end
canvas = love.graphics.newCanvas(CANVAS_WIDTH, CANVAS_HEIGHT)
canvas:setFilter("nearest", "nearest")
+ if menuCanvas then menuCanvas:release() end
+ menuCanvas = love.graphics.newCanvas(CANVAS_WIDTH, CANVAS_HEIGHT)
+ menuCanvas:setFilter("nearest", "nearest")
end
function love.update(dt)
@@ -144,6 +314,12 @@ function love.keypressed(key, scancode, isrepeat)
if key == "f1" and DEBUG then
shaderEnabled = not shaderEnabled
end
+ if key == "escape" and not isrepeat then
+ if currentState == "game" or currentState == "hud" then
+ currentState = "menu"
+ states.menu.load()
+ end
+ end
if key == "tab" and not isrepeat then
if currentState == "game" then
previousState = currentState
@@ -164,7 +340,21 @@ function love.keypressed(key, scancode, isrepeat)
end
function love.mousepressed(x, y, button)
- if currentState == "hud" and hud then
+ if currentState == "menu" and button == 1 then
+ local cx, cy = screenToCanvas(x, y)
+ for _, btn in ipairs(menuItems) do
+ if cx >= btn.x and cx <= btn.x + btn.w and cy >= btn.y and cy <= btn.y + btn.h then
+ if btn.action == "play" then
+ currentState = "game"
+ states.game.load()
+ love.mouse.setVisible(false)
+ elseif btn.action == "exit" then
+ love.event.quit()
+ end
+ break
+ end
+ end
+ elseif currentState == "hud" and hud then
hud:mousepressed(x, y, button)
end
end
diff --git a/shaders/missing_texture.glsl b/shaders/missing_texture.glsl
index 3824ade..e15a5ab 100644
--- a/shaders/missing_texture.glsl
+++ b/shaders/missing_texture.glsl
@@ -12,8 +12,12 @@ vec4 effect(vec4 color, Image texture, vec2 uv, vec2 screen_coords)
float checker = mod(cx + cy, 2.0);
- vec3 magenta = vec3(1.0, 0.0, 1.0);
- vec3 black = vec3(0.0, 0.0, 0.0);
+ // vec3 magenta = vec3(1.0, 0.0, 1.0);
+ //magenta main menu hurts my eyes
+ // {0.22, 0.38, 0.22, 1},
+ // {0.18, 0.30, 0.18, 1},
+ vec3 magenta = vec3(0.22, 0.38, 0.22);
+ vec3 black = vec3(0.18, 0.30, 0.18);
vec3 pattern = mix(magenta, black, checker);
diff --git a/world.lua b/world.lua
index 0678804..ff7a1ad 100644
--- a/world.lua
+++ b/world.lua
@@ -258,7 +258,7 @@ function World:handleDoor(door, player)
else
local remaining = needed - keyCount
self.playerTextbox:show(
- "Locked. You need " .. remaining .. " more key" .. (remaining > 1 and "s" or "") .. ".",
+ "Locked. I need " .. remaining .. " more key" .. (remaining > 1 and "s" or "") .. ".",
{ player.x + player.width / 2, player.y - 20, "center" },
"write",
{ life = 3, fontSize = 9, centeredText = true, wrapToFit = true }