mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
237 lines
9.3 KiB
Lua
237 lines
9.3 KiB
Lua
-- The mon inspector: species, level, DVs and moves for whatever S.editingMon
|
|
-- points at (a party slot or a box slot), all recalculated through MonOps so
|
|
-- stats stay in sync with the Gen1 formulas.
|
|
--
|
|
-- This used to be a modal overlay floating over the party list, which hid the
|
|
-- roster you were comparing against. It is now a permanent right-hand column
|
|
-- the Party and Boxes panels dock into (rule 1 of the design spec): the list
|
|
-- stays visible while you edit, and Escape clears the selection rather than
|
|
-- "closing a window".
|
|
|
|
local Theme = require("Theme")
|
|
local Ops = require("Ops")
|
|
local PAL = Theme.PAL
|
|
|
|
local MonEditor = {}
|
|
|
|
local DV_KEYS = { "attack", "defense", "speed", "special" }
|
|
local STAT_KEYS = {
|
|
{ key = "HP", field = "hp" },
|
|
{ key = "ATK", field = "attack" },
|
|
{ key = "DEF", field = "defense" },
|
|
{ key = "SPD", field = "speed" },
|
|
{ key = "SPC", field = "special" },
|
|
}
|
|
|
|
-- Front sprites are read straight off the generated cache. One image per
|
|
-- species, cached for the process: the old panel called newImage every frame,
|
|
-- which re-decoded a PNG sixty times a second.
|
|
local spriteCache = {}
|
|
function MonEditor.sprite(S, species)
|
|
if spriteCache[species] ~= nil then return spriteCache[species] or nil end
|
|
local def = S.data.pokemon[species]
|
|
local path = def and def.spriteFront
|
|
if not path or not love.graphics.newImage then
|
|
spriteCache[species] = false
|
|
return nil
|
|
end
|
|
local ok, img = pcall(love.graphics.newImage, path)
|
|
spriteCache[species] = ok and img or false
|
|
return ok and img or nil
|
|
end
|
|
|
|
-- Draw a species sprite fitted into a box, or a dashed placeholder when the
|
|
-- cache has no art for it (a modded species, or a headless run).
|
|
function MonEditor.drawSprite(S, Kit, species, x, y, size)
|
|
local img = MonEditor.sprite(S, species)
|
|
if img and love.graphics.draw and img.getDimensions then
|
|
local iw, ih = img:getDimensions()
|
|
if iw > 0 and ih > 0 then
|
|
local scale = math.min(size / iw, size / ih)
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.draw(img, x + (size - iw * scale) / 2,
|
|
y + (size - ih * scale) / 2, 0, scale, scale)
|
|
return
|
|
end
|
|
end
|
|
Theme.col(PAL.blue, 0.1)
|
|
love.graphics.rectangle("fill", x, y, size, size, 8 * Kit.scale, 8 * Kit.scale)
|
|
Theme.col(PAL.cardBorder, 0.35)
|
|
Theme.dashed(x, y, size, size, 8 * Kit.scale, 5 * Kit.scale, 4 * Kit.scale)
|
|
Kit.textCenter("micro", (species or "?"):sub(1, 3), x,
|
|
y + size / 2 - Kit.textHeight("micro") / 2, size, PAL.muted)
|
|
end
|
|
|
|
-- Bars are scaled against 400 so a Lv100 legend fills roughly three quarters
|
|
-- and the differences between mons stay legible.
|
|
local STAT_SCALE = 400
|
|
|
|
function MonEditor.draw(S, Kit, x, y, w, h)
|
|
local s = Kit.scale
|
|
Kit.card(x, y, w, h)
|
|
local mon = S.editingMon
|
|
local pad = 18 * s
|
|
if not mon then
|
|
-- The inspector column is always drawn, so it explains itself rather
|
|
-- than collapsing and reflowing the panel underneath it.
|
|
local tw = math.min(w - 40 * s, 340 * s)
|
|
Kit.textCenter("button",
|
|
"Pick a slot on the left to inspect it. Every change here re-runs the " ..
|
|
"Gen1 stat formulas, so HP and stats stay legal.",
|
|
x + (w - tw) / 2, y + h / 2 - Kit.textHeight("button"), tw, PAL.muted)
|
|
return
|
|
end
|
|
|
|
local def = S.data.pokemon[mon.species]
|
|
local cx, cy = x + pad, y + pad
|
|
local inner = w - 2 * pad
|
|
|
|
-- ---------------------------------------------------------- header row
|
|
local sprite = 96 * s
|
|
MonEditor.drawSprite(S, Kit, mon.species, cx, cy, sprite)
|
|
local hx = cx + sprite + 18 * s
|
|
local hw = inner - sprite - 18 * s
|
|
|
|
Kit.text("title", mon.species, hx, cy, PAL.heading)
|
|
local nameW = Kit.textWidth("title", mon.species)
|
|
Kit.text("tiny", ("#%03d"):format(def and def.dex or 0), hx + nameW + 12 * s,
|
|
cy + Kit.textHeight("title") - Kit.textHeight("tiny") - 2 * s, PAL.caption)
|
|
|
|
local stepW, stepH = 28 * s, 26 * s
|
|
local sx = hx + hw - 2 * stepW - 6 * s
|
|
local sy = cy + (Kit.textHeight("title") - stepH) / 2
|
|
if Kit.stepper(sx, sy, stepW, stepH, "<", { radius = 7 * s }) then
|
|
Ops.stepSpecies(S, mon, -1)
|
|
end
|
|
if Kit.stepper(sx + stepW + 6 * s, sy, stepW, stepH, ">", { radius = 7 * s }) then
|
|
Ops.stepSpecies(S, mon, 1)
|
|
end
|
|
Kit.textRight("tiny", "species", sx - 8 * s,
|
|
sy + (stepH - Kit.textHeight("tiny")) / 2, PAL.caption)
|
|
|
|
-- level stepper: -5 -1 [Lv] +1 +5, matching MonOps.setLevel's 1..100 clamp
|
|
local ly = cy + Kit.textHeight("title") + 14 * s
|
|
local lh = 28 * s
|
|
Kit.caption(hx, ly + (lh - Kit.textHeight("caption")) / 2, "LEVEL")
|
|
local lx = hx + 52 * s
|
|
local bw = 40 * s
|
|
for _, d in ipairs({ { "-5", -5 }, { "-1", -1 } }) do
|
|
if Kit.stepper(lx, ly, bw, lh, d[1], { font = "small", radius = 7 * s }) then
|
|
Ops.setLevel(S, mon, mon.level + d[2])
|
|
end
|
|
lx = lx + bw + 8 * s
|
|
end
|
|
Kit.textCenter("monoBig", tostring(mon.level), lx,
|
|
ly + (lh - Kit.textHeight("monoBig")) / 2, 58 * s, PAL.heading)
|
|
lx = lx + 58 * s + 8 * s
|
|
for _, d in ipairs({ { "+1", 1 }, { "+5", 5 } }) do
|
|
if Kit.stepper(lx, ly, bw, lh, d[1], { font = "small", radius = 7 * s }) then
|
|
Ops.setLevel(S, mon, mon.level + d[2])
|
|
end
|
|
lx = lx + bw + 8 * s
|
|
end
|
|
Kit.text("mono", ("EXP %d"):format(mon.exp or 0), lx + 6 * s,
|
|
ly + (lh - Kit.textHeight("mono")) / 2, PAL.muted)
|
|
|
|
-- ------------------------------------------------------- derived stats
|
|
local statsY = cy + sprite + 18 * s
|
|
Kit.caption(cx, statsY, "STATS . recalculated from level + DVs")
|
|
statsY = statsY + Kit.textHeight("caption") + 10 * s
|
|
local gap = 12 * s
|
|
local cellW = (inner - gap * 4) / 5
|
|
local cellH = 68 * s
|
|
for i, st in ipairs(STAT_KEYS) do
|
|
local bx = cx + (i - 1) * (cellW + gap)
|
|
Theme.row(bx, statsY, cellW, cellH, 10 * s, 0.6)
|
|
local value = (mon.stats and mon.stats[st.field]) or 0
|
|
Kit.text("micro", st.key, bx + 12 * s, statsY + 10 * s, PAL.caption)
|
|
Kit.text("stat", tostring(value), bx + 12 * s,
|
|
statsY + 10 * s + Kit.textHeight("micro") + 4 * s, PAL.heading)
|
|
Kit.meter(bx + 12 * s, statsY + cellH - 14 * s, cellW - 24 * s, 5 * s,
|
|
value / STAT_SCALE * 100, PAL.blue)
|
|
end
|
|
|
|
-- --------------------------------------------------- DVs | moves split
|
|
local colY = statsY + cellH + 18 * s
|
|
local colGap = 18 * s
|
|
local colW = (inner - colGap) / 2
|
|
local rightX = cx + colW + colGap
|
|
|
|
Kit.caption(cx, colY, "DVs")
|
|
Kit.textRight("tiny", ("HP DV auto-derived . %d"):format(mon.dvs.hp or 0),
|
|
cx + colW, colY, PAL.caption)
|
|
Kit.caption(rightX, colY, "MOVES")
|
|
Kit.textRight("tiny", "click a slot to cycle", rightX + colW, colY, PAL.caption)
|
|
|
|
local rowY = colY + Kit.textHeight("caption") + 10 * s
|
|
local rowH = 34 * s
|
|
local rowGap = 8 * s
|
|
|
|
for i, key in ipairs(DV_KEYS) do
|
|
local ry = rowY + (i - 1) * (rowH + rowGap)
|
|
Theme.row(cx, ry, colW, rowH, 10 * s, 0.6)
|
|
local v = mon.dvs[key] or 0
|
|
Kit.text("tiny", key:upper(), cx + 10 * s,
|
|
ry + (rowH - Kit.textHeight("tiny")) / 2, PAL.muted)
|
|
local btn = 26 * s
|
|
local btnX = cx + colW - 10 * s - 3 * btn - 18 * s
|
|
local meterX = cx + 66 * s
|
|
local meterW = math.max(20 * s, btnX - meterX - 34 * s)
|
|
Kit.meter(meterX, ry + (rowH - 8 * s) / 2, meterW, 8 * s, v / 15 * 100,
|
|
v >= 15 and PAL.green or (v >= 10 and PAL.blue or PAL.steel))
|
|
Kit.textRight("monoRow", tostring(v), meterX + meterW + 28 * s,
|
|
ry + (rowH - Kit.textHeight("monoRow")) / 2, PAL.heading)
|
|
if Kit.stepper(btnX, ry + (rowH - btn) / 2, btn, btn, "-") then
|
|
Ops.setDv(S, mon, key, v - 1)
|
|
end
|
|
if Kit.stepper(btnX + btn + 6 * s, ry + (rowH - btn) / 2, btn, btn, "+") then
|
|
Ops.setDv(S, mon, key, v + 1)
|
|
end
|
|
if Kit.button(btnX + 2 * btn + 12 * s, ry + (rowH - btn) / 2, btn, btn,
|
|
"15", { kind = "good", font = "micro", radius = 6 * s }) then
|
|
Ops.setDv(S, mon, key, 15)
|
|
end
|
|
end
|
|
|
|
for slot = 1, 4 do
|
|
local ry = rowY + (slot - 1) * (rowH + rowGap)
|
|
Theme.row(rightX, ry, colW, rowH, 10 * s, 0.6)
|
|
local mv = mon.moves and mon.moves[slot]
|
|
local clear = 24 * s
|
|
local clearX = rightX + colW - 10 * s - clear
|
|
local ppText = mv and ("PP %d"):format(mv.pp or 0) or ""
|
|
local ppW = Kit.textWidth("tiny", ppText)
|
|
Kit.text("mono", tostring(slot), rightX + 10 * s,
|
|
ry + (rowH - Kit.textHeight("mono")) / 2, PAL.faint)
|
|
local nameX = rightX + 28 * s
|
|
local nameW2 = math.max(20 * s, clearX - 12 * s - ppW - nameX)
|
|
Kit.text("monoRow", Kit.ellipsize("monoRow", mv and mv.id or "-- --", nameW2),
|
|
nameX, ry + (rowH - Kit.textHeight("monoRow")) / 2,
|
|
mv and PAL.text or PAL.faint)
|
|
Kit.textRight("tiny", ppText, clearX - 10 * s,
|
|
ry + (rowH - Kit.textHeight("tiny")) / 2, PAL.caption)
|
|
-- the row body cycles, the x empties: two targets, no modal picker
|
|
if Kit.press(rightX, ry, clearX - rightX - 4 * s, rowH) then
|
|
Ops.cycleMove(S, mon, slot)
|
|
end
|
|
if Kit.button(clearX, ry + (rowH - clear) / 2, clear, clear, "x",
|
|
{ kind = "danger", font = "tiny", radius = 6 * s }) then
|
|
Ops.clearMove(S, mon, slot)
|
|
end
|
|
end
|
|
|
|
local actY = rowY + 4 * (rowH + rowGap) + 4 * s
|
|
local actH = 34 * s
|
|
local actW = (colW - 10 * s) / 2
|
|
if Kit.button(rightX, actY, actW, actH, "Reset to learnset",
|
|
{ font = "small", radius = 9 * s }) then
|
|
Ops.resetMoves(S, mon)
|
|
end
|
|
if Kit.button(rightX + actW + 10 * s, actY, actW, actH, "Full heal",
|
|
{ kind = "good", font = "small", radius = 9 * s }) then
|
|
Ops.healMon(S, mon)
|
|
end
|
|
end
|
|
|
|
return MonEditor
|