mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 07:41:21 +02:00
fancy cart
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 243 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 217 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 236 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 242 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 166 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 251 KiB |
+257
-18
@@ -100,6 +100,7 @@ end
|
|||||||
-- other half of that guarantee.
|
-- other half of that guarantee.
|
||||||
function LauncherView.update(imp, dt)
|
function LauncherView.update(imp, dt)
|
||||||
if not imp._flex then return end
|
if not imp._flex then return end
|
||||||
|
if imp._launchFade then return end
|
||||||
|
|
||||||
local down = false
|
local down = false
|
||||||
if love.mouse and love.mouse.isDown then
|
if love.mouse and love.mouse.isDown then
|
||||||
@@ -312,6 +313,236 @@ local function cartColor(version)
|
|||||||
return CART_COLOR[version] or PAL.green
|
return CART_COLOR[version] or PAL.green
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local CART_DRAG_SLOP = 8
|
||||||
|
local TAU = math.pi * 2
|
||||||
|
|
||||||
|
local function cartridgeState(imp, version)
|
||||||
|
imp._cartridge = imp._cartridge or {}
|
||||||
|
local state = imp._cartridge[version]
|
||||||
|
if not state then
|
||||||
|
state = { spin = 0, lastTime = Kit.time }
|
||||||
|
imp._cartridge[version] = state
|
||||||
|
end
|
||||||
|
return state
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cartridgeLabel(imp, version)
|
||||||
|
imp._cartridgeLabels = imp._cartridgeLabels or {}
|
||||||
|
local label = imp._cartridgeLabels[version]
|
||||||
|
if label ~= nil then return label or nil end
|
||||||
|
local ok, image = pcall(love.graphics.newImage,
|
||||||
|
"assets/labels/" .. tostring(version) .. ".png")
|
||||||
|
if not ok then
|
||||||
|
imp._cartridgeLabels[version] = false
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local iw, ih = image:getDimensions()
|
||||||
|
label = { image = image, width = iw, height = ih }
|
||||||
|
imp._cartridgeLabels[version] = label
|
||||||
|
return label
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cartProject(cx, cy, yaw, pitch, x, y, z)
|
||||||
|
local cyaw, syaw = math.cos(yaw), math.sin(yaw)
|
||||||
|
local cpitch, spitch = math.cos(pitch), math.sin(pitch)
|
||||||
|
local rx = x * cyaw + z * syaw
|
||||||
|
local rz = -x * syaw + z * cyaw
|
||||||
|
local ry = y * cpitch - rz * spitch
|
||||||
|
rz = y * spitch + rz * cpitch
|
||||||
|
local perspective = 620 / (620 - rz)
|
||||||
|
return cx + rx * perspective, cy + ry * perspective
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cartPolygon(points, color, alpha)
|
||||||
|
local flat = {}
|
||||||
|
for i = 1, #points do
|
||||||
|
flat[#flat + 1], flat[#flat + 2] = points[i][1], points[i][2]
|
||||||
|
end
|
||||||
|
Theme.col(color, alpha or 1)
|
||||||
|
love.graphics.polygon("fill", flat)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cartQuad(project, x, y, w, h, z)
|
||||||
|
return {
|
||||||
|
{ project(x, y, z) }, { project(x + w, y, z) },
|
||||||
|
{ project(x + w, y + h, z) }, { project(x, y + h, z) },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cartPill(project, x, y, w, h, z, color, alpha)
|
||||||
|
local points, radius = {}, h / 2
|
||||||
|
for i = 0, 10 do
|
||||||
|
local a = math.pi + math.pi * i / 10
|
||||||
|
points[#points + 1] = { project(x + radius + math.cos(a) * radius,
|
||||||
|
y + radius + math.sin(a) * radius, z) }
|
||||||
|
end
|
||||||
|
for i = 0, 10 do
|
||||||
|
local a = math.pi * i / 10
|
||||||
|
points[#points + 1] = { project(x + w - radius + math.cos(a) * radius,
|
||||||
|
y + radius + math.sin(a) * radius, z) }
|
||||||
|
end
|
||||||
|
cartPolygon(points, color, alpha)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cartLabelMesh(imp, version, label, points)
|
||||||
|
if not love.graphics.newMesh then return nil end
|
||||||
|
imp._cartridgeLabelMeshes = imp._cartridgeLabelMeshes or {}
|
||||||
|
local mesh = imp._cartridgeLabelMeshes[version]
|
||||||
|
if not mesh then
|
||||||
|
mesh = love.graphics.newMesh({
|
||||||
|
{ 0, 0, 0, 0, 255, 255, 255, 255 },
|
||||||
|
{ 0, 0, 1, 0, 255, 255, 255, 255 },
|
||||||
|
{ 0, 0, 1, 1, 255, 255, 255, 255 },
|
||||||
|
{ 0, 0, 0, 1, 255, 255, 255, 255 },
|
||||||
|
}, "fan", "dynamic")
|
||||||
|
mesh:setTexture(label.image)
|
||||||
|
imp._cartridgeLabelMeshes[version] = mesh
|
||||||
|
end
|
||||||
|
mesh:setVertices({
|
||||||
|
{ points[1][1], points[1][2], 0, 0, 255, 255, 255, 255 },
|
||||||
|
{ points[2][1], points[2][2], 1, 0, 255, 255, 255, 255 },
|
||||||
|
{ points[3][1], points[3][2], 1, 1, 255, 255, 255, 255 },
|
||||||
|
{ points[4][1], points[4][2], 0, 1, 255, 255, 255, 255 },
|
||||||
|
})
|
||||||
|
return mesh
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cartridgeButton(imp, x, y, w, h, key, version, gameName, action)
|
||||||
|
local state = cartridgeState(imp, version)
|
||||||
|
local focused = Kit.focusable(key, x, y, w, h)
|
||||||
|
local hot = Kit.hover(x, y, w, h)
|
||||||
|
local active = state.active
|
||||||
|
local cx, cy = x + w / 2, y + h / 2
|
||||||
|
|
||||||
|
if Kit.mouseClicked and Kit.hit(x, y, w, h) and not Kit.blockClicks then
|
||||||
|
if Kit.mouseDown then
|
||||||
|
state.active = true
|
||||||
|
state.startX, state.startY = Kit.mouseX, Kit.mouseY
|
||||||
|
state.lastDragX, state.lastDragY = Kit.mouseX, Kit.mouseY
|
||||||
|
state.dragged = false
|
||||||
|
active = true
|
||||||
|
else
|
||||||
|
queueAction(imp, key, action)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if state.active then
|
||||||
|
active = true
|
||||||
|
if Kit.mouseDown then
|
||||||
|
local movedX, movedY = Kit.mouseX - state.startX, Kit.mouseY - state.startY
|
||||||
|
if movedX * movedX + movedY * movedY > CART_DRAG_SLOP * CART_DRAG_SLOP then
|
||||||
|
state.dragged = true
|
||||||
|
end
|
||||||
|
if state.dragged then
|
||||||
|
local dragX = Kit.mouseX - (state.lastDragX or Kit.mouseX)
|
||||||
|
local dragY = Kit.mouseY - (state.lastDragY or Kit.mouseY)
|
||||||
|
state.spin = state.spin + dragX * 0.018
|
||||||
|
state.pitchDrag = clamp((state.pitchDrag or 0) + dragY * 0.010,
|
||||||
|
-1.20, 1.20)
|
||||||
|
end
|
||||||
|
state.lastDragX, state.lastDragY = Kit.mouseX, Kit.mouseY
|
||||||
|
else
|
||||||
|
if not state.dragged then queueAction(imp, key, action) end
|
||||||
|
state.active, active = nil, false
|
||||||
|
state.dragged = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local dt = math.min(0.08, math.max(0, Kit.time - (state.lastTime or Kit.time)))
|
||||||
|
state.lastTime = Kit.time
|
||||||
|
if not state.active then
|
||||||
|
local upright = math.floor(state.spin / TAU + 0.5) * TAU
|
||||||
|
state.spin = state.spin + (upright - state.spin) * math.min(1, dt * 4)
|
||||||
|
state.pitchDrag = (state.pitchDrag or 0)
|
||||||
|
* (1 - math.min(1, dt * 4))
|
||||||
|
end
|
||||||
|
local pointerX = clamp((Kit.mouseX - cx) / math.max(1, w / 2), -1, 1)
|
||||||
|
local pointerY = clamp((Kit.mouseY - cy) / math.max(1, h / 2), -1, 1)
|
||||||
|
local hoverTilt = hot and pointerX * 0.12 or math.sin(Kit.time * 0.75) * 0.035
|
||||||
|
local pressX = active and pointerX * w * 0.025 or 0
|
||||||
|
local pressY = active and pointerY * h * 0.018 or 0
|
||||||
|
local yaw = -0.42 + state.spin + hoverTilt
|
||||||
|
local pitch = 0.14 + (state.pitchDrag or 0)
|
||||||
|
+ (hot and pointerY * 0.08 or math.sin(Kit.time * 0.6) * 0.018)
|
||||||
|
local pressedScale = active and 0.965 or 1
|
||||||
|
|
||||||
|
Kit._audit("control", x, y, w, h, key)
|
||||||
|
if focused then
|
||||||
|
Theme.strokeRounded(x - 3, y - 3, w + 6, h + 6, PAL.lineStrong,
|
||||||
|
Theme.A.focus, 2, Theme.cardRadius() + 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local halfW, halfH = w / 2, h / 2
|
||||||
|
local depth = math.max(8, w * 0.14)
|
||||||
|
local project = function(px, py, pz)
|
||||||
|
return cartProject(cx + pressX, cy + pressY, yaw, pitch,
|
||||||
|
px * pressedScale, py * pressedScale, pz * pressedScale)
|
||||||
|
end
|
||||||
|
|
||||||
|
local capH = h * 3 / 65
|
||||||
|
local mainTop = -halfH + capH
|
||||||
|
local capRight = halfW - w * 5 / 57
|
||||||
|
local mainFront = cartQuad(project, -halfW, mainTop, w, h - capH, depth)
|
||||||
|
local mainBack = cartQuad(project, -halfW, mainTop, w, h - capH, -depth)
|
||||||
|
local capFront = cartQuad(project, -halfW, -halfH,
|
||||||
|
capRight + halfW, capH, depth)
|
||||||
|
local capBack = cartQuad(project, -halfW, -halfH,
|
||||||
|
capRight + halfW, capH, -depth)
|
||||||
|
local shell = cartColor(version)
|
||||||
|
local side = { math.floor(shell[1] * 0.54), math.floor(shell[2] * 0.54),
|
||||||
|
math.floor(shell[3] * 0.54) }
|
||||||
|
|
||||||
|
cartPolygon(mainBack, side, 1)
|
||||||
|
cartPolygon(capBack, side, 1)
|
||||||
|
cartPolygon({ mainFront[2], mainFront[3], mainBack[3], mainBack[2] }, side, 1)
|
||||||
|
cartPolygon({ mainFront[3], mainFront[4], mainBack[4], mainBack[3] }, side, 1)
|
||||||
|
cartPolygon({ mainFront[1], mainFront[2], mainBack[2], mainBack[1] }, side, 1)
|
||||||
|
cartPolygon({ capFront[2], capFront[3], capBack[3], capBack[2] }, side, 1)
|
||||||
|
cartPolygon({ capFront[1], capFront[2], capBack[2], capBack[1] }, side, 1)
|
||||||
|
cartPolygon({ capFront[4], capFront[1], capBack[1], capBack[4] }, side, 1)
|
||||||
|
cartPolygon(mainFront, shell, 1)
|
||||||
|
cartPolygon(capFront, shell, 1)
|
||||||
|
|
||||||
|
local faceZ = depth + 0.8
|
||||||
|
for i = 0, 4 do
|
||||||
|
local ry = mainTop + 7 + i * h * 0.025
|
||||||
|
cartPolygon(cartQuad(project, -halfW + 2, ry, w * 0.13, 2, faceZ), side, 0.7)
|
||||||
|
cartPolygon(cartQuad(project, halfW - w * 0.13 - 2, ry, w * 0.13, 2, faceZ), side, 0.7)
|
||||||
|
end
|
||||||
|
local recessX, recessY = -w * 0.32, mainTop + h * 0.023
|
||||||
|
local recessW, recessH = w * 0.64, h * 0.24
|
||||||
|
cartPolygon(cartQuad(project, recessX, recessY, recessW, recessH, faceZ), shell, 0.88)
|
||||||
|
cartPill(project, recessX + w * 0.025, recessY + h * 0.025,
|
||||||
|
recessW - w * 0.05, h * 0.12, faceZ + 0.5, shell, 0.7)
|
||||||
|
cartPill(project, recessX + w * 0.045, recessY + h * 0.043,
|
||||||
|
recessW - w * 0.09, h * 0.083, faceZ + 0.8, side, 0.42)
|
||||||
|
|
||||||
|
local labelX, labelY = -w * 0.33, -h * 0.20
|
||||||
|
local labelW, labelH = w * 0.66, h * 0.55
|
||||||
|
local plate = cartQuad(project, labelX - 2, labelY - 2, labelW + 4, labelH + 4, faceZ + 0.8)
|
||||||
|
cartPolygon(plate, side, 0.95)
|
||||||
|
local labelPoints = cartQuad(project, labelX, labelY, labelW, labelH, faceZ + 1.2)
|
||||||
|
local label = cartridgeLabel(imp, version)
|
||||||
|
local mesh = label and cartLabelMesh(imp, version, label, labelPoints)
|
||||||
|
if mesh then
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
love.graphics.draw(mesh)
|
||||||
|
elseif label then
|
||||||
|
local artScale = math.min(labelW / label.width, labelH / label.height)
|
||||||
|
love.graphics.draw(label.image, labelPoints[1][1], labelPoints[1][2],
|
||||||
|
0, artScale, artScale)
|
||||||
|
end
|
||||||
|
cartPolygon({
|
||||||
|
{ project(-w * 0.07, h * 0.37, faceZ + 1) },
|
||||||
|
{ project(w * 0.07, h * 0.37, faceZ + 1) },
|
||||||
|
{ project(0, h * 0.43, faceZ + 1) },
|
||||||
|
}, side, 0.70)
|
||||||
|
|
||||||
|
if not state.active and (Kit._activateId == key) then
|
||||||
|
queueAction(imp, key, action)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function modStatusColor(status)
|
local function modStatusColor(status)
|
||||||
if status == "ok" then return Strings("Ready"), PAL.green end
|
if status == "ok" then return Strings("Ready"), PAL.green end
|
||||||
if status == "conflict" then return Strings("Conflict"), PAL.red end
|
if status == "conflict" then return Strings("Conflict"), PAL.red end
|
||||||
@@ -1011,8 +1242,16 @@ local function buildGamePanel(imp, x, y, w, availH, m, version)
|
|||||||
else tagText, tagCol = Strings("ROM REQUIRED"), PAL.yellow end
|
else tagText, tagCol = Strings("ROM REQUIRED"), PAL.yellow end
|
||||||
local tagW = Kit.textWidth("micro", tagText) + math.floor(18 * m.s)
|
local tagW = Kit.textWidth("micro", tagText) + math.floor(18 * m.s)
|
||||||
local tagH = Kit.textHeight("micro") + math.floor(10 * m.s)
|
local tagH = Kit.textHeight("micro") + math.floor(10 * m.s)
|
||||||
Kit.tag(x + Kit.textWidth("title", Kit.ellipsize("title", gameName, w * 0.6))
|
local tagX = x + Kit.textWidth("title", Kit.ellipsize("title", gameName, w * 0.6))
|
||||||
+ math.floor(12 * m.s), y + (titleH - tagH) / 2, tagW, tagH, tagText, tagCol)
|
+ math.floor(12 * m.s)
|
||||||
|
Kit.tag(tagX, y + (titleH - tagH) / 2, tagW, tagH, tagText, tagCol)
|
||||||
|
if ready then
|
||||||
|
local hint = Strings("(PRESS THE CART TO PLAY)")
|
||||||
|
local hintX = tagX + tagW + math.floor(10 * m.s)
|
||||||
|
local hintW = math.max(0, x + w - hintX)
|
||||||
|
Kit.text("micro", Kit.ellipsize("micro", hint, hintW), hintX,
|
||||||
|
y + (titleH - Kit.textHeight("micro")) / 2, PAL.heading)
|
||||||
|
end
|
||||||
local cy = y + titleH + math.floor(12 * m.s)
|
local cy = y + titleH + math.floor(12 * m.s)
|
||||||
local remaining = availH - (titleH + math.floor(12 * m.s))
|
local remaining = availH - (titleH + math.floor(12 * m.s))
|
||||||
|
|
||||||
@@ -1038,25 +1277,20 @@ local function buildGamePanel(imp, x, y, w, availH, m, version)
|
|||||||
local ly = cy
|
local ly = cy
|
||||||
|
|
||||||
if ready then
|
if ready then
|
||||||
-- Play IS the panel: it takes the space the ROM buttons used to hold, at
|
-- The cartridge takes the Play button's former place. Its portrait
|
||||||
-- the top of the column where the eye lands, wearing this game's own
|
-- ratio comes from a real Game Boy cart rather than stretching the old
|
||||||
-- cartridge colour rather than a generic green.
|
-- horizontal control, and its body colour comes from the active game.
|
||||||
-- Play grows into the room the column has: it is the one thing on this
|
local playH = math.floor(clamp(remaining * 0.52, 112 * m.s, 260 * m.s))
|
||||||
-- screen the player came for, and the space freed by moving ROM and
|
local mgW = math.max(Kit.tapMin(), math.floor(34 * m.s))
|
||||||
-- control management out belongs to it rather than to a gap. Clamped at
|
|
||||||
-- both ends so a short window still gets a real button and a tall one
|
|
||||||
-- does not get a billboard.
|
|
||||||
local playH = math.floor(clamp(remaining * 0.30, 64 * m.s, 132 * m.s))
|
|
||||||
local mgW = playH
|
|
||||||
local bgap = math.floor(8 * m.s)
|
local bgap = math.floor(8 * m.s)
|
||||||
btn(imp, lx, ly, lw - mgW - bgap, playH, "play-" .. version,
|
local cartAreaW = lw - mgW - bgap
|
||||||
Strings("Play ") .. gameName, {
|
local cartW = math.min(cartAreaW, math.floor(playH * 0.88))
|
||||||
fill = cartColor(version), ink = PAL.inverse, font = "stat",
|
local cartX = lx + math.floor((cartAreaW - cartW) / 2)
|
||||||
action = function() imp:play(version) end,
|
cartridgeButton(imp, cartX, ly, cartW, playH, "play-" .. version,
|
||||||
})
|
version, gameName, function() imp:play(version, true) end)
|
||||||
imp._gearIcon = imp._gearIcon
|
imp._gearIcon = imp._gearIcon
|
||||||
or love.graphics.newImage("assets/launcher/gear.png")
|
or love.graphics.newImage("assets/launcher/gear.png")
|
||||||
iconButton(imp, "manage-" .. version, lx + lw - mgW, ly, playH,
|
iconButton(imp, "manage-" .. version, lx + lw - mgW, ly, mgW,
|
||||||
imp._gearIcon, function() imp._gameManage = version end)
|
imp._gearIcon, function() imp._gameManage = version end)
|
||||||
ly = ly + playH + gap
|
ly = ly + playH + gap
|
||||||
end
|
end
|
||||||
@@ -2637,6 +2871,11 @@ function LauncherView.draw(imp)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if imp._launchFade then
|
||||||
|
Theme.fill(0, 0, m.W, m.H, PAL.bg,
|
||||||
|
math.min(1, imp._launchFade.elapsed / imp._launchFade.duration))
|
||||||
|
end
|
||||||
|
|
||||||
Kit.endFrame()
|
Kit.endFrame()
|
||||||
drawPadCursor(imp)
|
drawPadCursor(imp)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2048,6 +2048,15 @@ end
|
|||||||
|
|
||||||
function RomImporter:update(dt)
|
function RomImporter:update(dt)
|
||||||
self.pulse = self.pulse + dt
|
self.pulse = self.pulse + dt
|
||||||
|
if self._launchFade then
|
||||||
|
self._launchFade.elapsed = self._launchFade.elapsed + dt
|
||||||
|
if self._launchFade.elapsed >= self._launchFade.duration then
|
||||||
|
local version = self._launchFade.version
|
||||||
|
self._launchFade = nil
|
||||||
|
self:play(version)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
self:_updatePadCursor(dt)
|
self:_updatePadCursor(dt)
|
||||||
self:_stepBaseRomScan()
|
self:_stepBaseRomScan()
|
||||||
-- Pump the FlexLove view (input polling + the queued click actions). The
|
-- Pump the FlexLove view (input polling + the queued click actions). The
|
||||||
@@ -2436,9 +2445,15 @@ function RomImporter:joystickhat(joystick, hat, direction)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Player pressed Play on a game whose ROM is imported: hand off to boot.
|
-- Player pressed Play on a game whose ROM is imported: hand off to boot.
|
||||||
function RomImporter:play(version)
|
function RomImporter:play(version, fade)
|
||||||
if self.workState == "working" then return end
|
if self.workState == "working" then return end
|
||||||
if not self.ready[version] then return end
|
if not self.ready[version] then return end
|
||||||
|
if fade then
|
||||||
|
if not self._launchFade then
|
||||||
|
self._launchFade = { version = version, elapsed = 0, duration = 0.24 }
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
self._handedOff = true
|
self._handedOff = true
|
||||||
-- #835: remember the game being launched so the next launcher start opens on
|
-- #835: remember the game being launched so the next launcher start opens on
|
||||||
-- its column (_applyLastVersionTab). It rides options.lua rather than a file
|
-- its column (_applyLastVersionTab). It rides options.lua rather than a file
|
||||||
|
|||||||
Reference in New Issue
Block a user