mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
76ede1171e
- Save Editor (Gen 3): - Added full support for EV, IV, Nature, Ability, and Ribbons editing for Gen 3 Pokémon. - Implemented stat calculations, personality-based nature derivation, and Gen 3 save data structures. - Added comprehensive unit test suite in tests/save_editor_gen3_ev_iv_tests.lua. - GBA Extraction & Graphics: - Added zero-copy ARM-safe ROM accessor and fast LZ77 string decompression. - Fixed bag chrome decompression string-buffer indexing preventing solid-color renders. - Fixed TM Case HM badge tile offset and palette mapping. - Added builtin moves fallback in src/core/game3/battle/builtin_moves.lua. - Improved parallel extraction resilience, desktop fallback search paths, and prefix handling. - Android & Launchers: - Added dynamic high-res app launcher shortcuts and color palettes for all supported games. - Updated Android icon generator and intent dispatch handlers.
864 lines
36 KiB
Lua
864 lines
36 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".
|
|
--
|
|
-- #715 reflow: the inspector used to shrink its stat tiles and DV/move rows
|
|
-- against a vertical budget, and past a point the rows still ran over the
|
|
-- action buttons. Sizes are fixed at readable values now; when the card is
|
|
-- too short for them the whole body scrolls (Kit.scrollPixels), and when it
|
|
-- is too narrow for the DV | moves split the two columns stack. The clip
|
|
-- over the card doubles as the hit fence, so a control scrolled out of view
|
|
-- cannot take a stray tap.
|
|
|
|
local Theme = require("Theme")
|
|
local Ops = require("Ops")
|
|
local MonOps = require("MonOps")
|
|
local PAL = Theme.PAL
|
|
local Gen = require("Gen")
|
|
|
|
local MonEditor = {}
|
|
|
|
local DV_KEYS = { "attack", "defense", "speed", "special" }
|
|
local STAT_KEYS_G1 = {
|
|
{ key = "HP", field = "hp" },
|
|
{ key = "ATK", field = "attack" },
|
|
{ key = "DEF", field = "defense" },
|
|
{ key = "SPD", field = "speed" },
|
|
{ key = "SPC", field = "special" },
|
|
}
|
|
local STAT_KEYS_G2 = {
|
|
{ key = "HP", field = "hp" },
|
|
{ key = "ATK", field = "attack" },
|
|
{ key = "DEF", field = "defense" },
|
|
{ key = "SPA", field = "specialAttack" },
|
|
{ key = "SPD", field = "specialDefense" },
|
|
{ key = "SPE", field = "speed" },
|
|
}
|
|
|
|
-- 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 not species then return nil end
|
|
if spriteCache[species] ~= nil then return spriteCache[species] or nil end
|
|
if Gen.ofState(S) == 3 then
|
|
local okP, Pokemon = pcall(require, "src.core.game3.pokemon")
|
|
if okP and Pokemon then
|
|
local spId = tonumber(species) or (Pokemon.speciesFromName and Pokemon.speciesFromName(tostring(species)))
|
|
if spId then
|
|
local pic = (Pokemon.frontPic and Pokemon.frontPic(spId)) or (Pokemon.icon and Pokemon.icon(spId))
|
|
if pic and pic.image then
|
|
spriteCache[species] = pic.image
|
|
return pic.image
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local def = S.data and S.data.pokemon and (S.data.pokemon[species] or (type(species) == "number" and 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", tostring(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
|
|
|
|
-- The -5 -1 [Lv] +1 +5 stepper row plus the EXP readout, at (lx0, ly).
|
|
local function drawLevelRow(S, Kit, mon, lx0, ly)
|
|
local s = Kit.scale
|
|
local lh = 28 * s
|
|
Kit.caption(lx0, ly + (lh - Kit.textHeight("caption")) / 2, "LEVEL")
|
|
local lx = lx0 + 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(Gen.exp(mon)), lx + 6 * s,
|
|
ly + (lh - Kit.textHeight("mono")) / 2, PAL.muted)
|
|
return lh
|
|
end
|
|
|
|
-- Everything the level row needs in width, for the "does it fit beside the
|
|
-- sprite" decision.
|
|
local function levelRowWidth(Kit, mon)
|
|
local s = Kit.scale
|
|
return 52 * s + 2 * (40 * s + 8 * s) + 58 * s + 8 * s + 2 * (40 * s + 8 * s)
|
|
+ 6 * s + Kit.textWidth("mono", ("EXP %d"):format(Gen.exp(mon)))
|
|
end
|
|
|
|
local STAT_KEYS_G3 = {
|
|
{ key = "hp", label = "HP" },
|
|
{ key = "atk", label = "ATTACK" },
|
|
{ key = "def", label = "DEFENSE" },
|
|
{ key = "spa", label = "SP. ATK" },
|
|
{ key = "spd", label = "SP. DEF" },
|
|
{ key = "spe", label = "SPEED" },
|
|
}
|
|
|
|
local function totalEvsOf(mon)
|
|
local evs = mon.evs or {}
|
|
local total = 0
|
|
for _, st in ipairs(STAT_KEYS_G3) do
|
|
total = total + (tonumber(evs[st.key]) or 0)
|
|
end
|
|
return total
|
|
end
|
|
|
|
local function drawIvRowsG3(S, Kit, mon, cx, rowY, colW, rowH, rowGap)
|
|
local s = Kit.scale
|
|
mon.ivs = mon.ivs or { hp = 31, atk = 31, def = 31, spe = 31, spa = 31, spd = 31 }
|
|
for i, st in ipairs(STAT_KEYS_G3) do
|
|
local ry = rowY + (i - 1) * (rowH + rowGap)
|
|
Theme.row(cx, ry, colW, rowH, 10 * s, 0.6)
|
|
local v = (mon.ivs and mon.ivs[st.key]) or 0
|
|
Kit.text("tiny", st.label, 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(16 * s, btnX - meterX - 34 * s)
|
|
Kit.meter(meterX, ry + (rowH - 8 * s) / 2, meterW, 8 * s, v / 31 * 100,
|
|
v >= 31 and PAL.green or (v >= 20 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.setIv(S, mon, st.key, v - 1)
|
|
end
|
|
if Kit.stepper(btnX + btn + 6 * s, ry + (rowH - btn) / 2, btn, btn, "+") then
|
|
Ops.setIv(S, mon, st.key, v + 1)
|
|
end
|
|
if Kit.button(btnX + 2 * btn + 12 * s, ry + (rowH - btn) / 2, btn, btn,
|
|
"31", { kind = "good", font = "micro", radius = 6 * s }) then
|
|
Ops.setIv(S, mon, st.key, 31)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function drawEvRowsG3(S, Kit, mon, cx, rowY, colW, rowH, rowGap)
|
|
local s = Kit.scale
|
|
mon.evs = mon.evs or { hp = 0, atk = 0, def = 0, spe = 0, spa = 0, spd = 0 }
|
|
for i, st in ipairs(STAT_KEYS_G3) do
|
|
local ry = rowY + (i - 1) * (rowH + rowGap)
|
|
Theme.row(cx, ry, colW, rowH, 10 * s, 0.6)
|
|
local v = (mon.evs and mon.evs[st.key]) or 0
|
|
Kit.text("tiny", st.label, cx + 10 * s,
|
|
ry + (rowH - Kit.textHeight("tiny")) / 2, PAL.muted)
|
|
|
|
local btn = 24 * s
|
|
local rightX = cx + colW - 10 * s
|
|
local b252W = 28 * s
|
|
local b252X = rightX - b252W
|
|
local p1X = b252X - 6 * s - btn
|
|
local m1X = p1X - 6 * s - btn
|
|
|
|
local meterX = cx + 66 * s
|
|
local meterW = math.max(16 * s, m1X - meterX - 34 * s)
|
|
Kit.meter(meterX, ry + (rowH - 8 * s) / 2, meterW, 8 * s, v / 255 * 100,
|
|
v >= 252 and PAL.green or (v >= 100 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(m1X, ry + (rowH - btn) / 2, btn, btn, "-") then
|
|
Ops.setEv(S, mon, st.key, v - 1)
|
|
end
|
|
if Kit.stepper(p1X, ry + (rowH - btn) / 2, btn, btn, "+") then
|
|
Ops.setEv(S, mon, st.key, v + 1)
|
|
end
|
|
if Kit.button(b252X, ry + (rowH - btn) / 2, b252W, btn,
|
|
"252", { kind = "good", font = "micro", radius = 6 * s }) then
|
|
Ops.setEv(S, mon, st.key, 252)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function drawDvRows(S, Kit, mon, cx, rowY, colW, rowH, rowGap)
|
|
local s = Kit.scale
|
|
mon.dvs = mon.dvs or { attack = 0, defense = 0, speed = 0, special = 0, hp = 0 }
|
|
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 and 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
|
|
end
|
|
|
|
-- engine/pokemon/caught_data.asm:168-199
|
|
local CAUGHT_BY = { { "-", "none" }, { "BOY", "boy" }, { "GIRL", "girl" } }
|
|
|
|
local function drawCaughtRows(S, Kit, mon, cx, y, inner, row)
|
|
local s = Kit.scale
|
|
local chipH = 22 * s
|
|
local gap = 6 * s
|
|
local tinyH = Kit.textHeight("tiny")
|
|
|
|
Kit.text("tiny", "CAUGHT", cx, y + (row - tinyH) / 2, PAL.caption)
|
|
local timeX = cx + 62 * s
|
|
local timeW = math.max(34 * s, (cx + inner - timeX - 3 * gap) / 4)
|
|
for i, label in ipairs(Ops.CAUGHT_TIMES) do
|
|
if Kit.chip(timeX + (i - 1) * (timeW + gap), y + (row - chipH) / 2,
|
|
timeW, chipH, label, (mon.caughtTime or 0) == i - 1, PAL.blue, PAL.steel) then
|
|
Ops.setCaughtTime(S, mon, i - 1)
|
|
end
|
|
end
|
|
y = y + row + gap
|
|
|
|
local btn = 24 * s
|
|
local lvX = cx + inner - 2 * btn - gap
|
|
if Kit.stepper(lvX, y + (row - btn) / 2, btn, btn, "-", { font = "small" }) then
|
|
Ops.setCaughtLevel(S, mon, (mon.caughtLevel or 0) - 1)
|
|
end
|
|
if Kit.stepper(lvX + btn + gap, y + (row - btn) / 2, btn, btn, "+",
|
|
{ font = "small" }) then
|
|
Ops.setCaughtLevel(S, mon, (mon.caughtLevel or 0) + 1)
|
|
end
|
|
Kit.textRight("tiny", ("MET LV %d"):format(mon.caughtLevel or 0), lvX - 10 * s,
|
|
y + (row - tinyH) / 2, PAL.text)
|
|
Kit.text("tiny", "OT", cx, y + (row - tinyH) / 2, PAL.caption)
|
|
local otX = cx + 26 * s
|
|
local otW = math.max(30 * s, (inner * 0.42 - 26 * s - 2 * gap) / 3)
|
|
for i, pair in ipairs(CAUGHT_BY) do
|
|
if Kit.chip(otX + (i - 1) * (otW + gap), y + (row - chipH) / 2, otW, chipH,
|
|
pair[1], (mon.caughtByGender or "none") == pair[2], PAL.blue, PAL.steel) then
|
|
Ops.setCaughtByGender(S, mon, pair[2])
|
|
end
|
|
end
|
|
y = y + row + gap
|
|
|
|
local whereX = cx + inner - 3 * btn - 2 * gap
|
|
if Kit.stepper(whereX, y + (row - btn) / 2, btn, btn, "-", { font = "small" }) then
|
|
Ops.setCaughtLocation(S, mon, (mon.caughtLocation or 0) - 1)
|
|
end
|
|
if Kit.stepper(whereX + btn + gap, y + (row - btn) / 2, btn, btn, "+",
|
|
{ font = "small" }) then
|
|
Ops.setCaughtLocation(S, mon, (mon.caughtLocation or 0) + 1)
|
|
end
|
|
if Kit.button(whereX + 2 * (btn + gap), y + (row - btn) / 2, btn, btn, "0",
|
|
{ kind = "danger", font = "micro", radius = 6 * s }) then
|
|
Ops.setCaughtLocation(S, mon, 0)
|
|
end
|
|
local where = ("WHERE %s"):format(Gen.landmarkName(S.data, mon.caughtLocation or 0))
|
|
Kit.text("tiny", Kit.ellipsize("tiny", where, whereX - 10 * s - cx), cx,
|
|
y + (row - tinyH) / 2, PAL.text)
|
|
return y + row + gap
|
|
end
|
|
|
|
local function drawMoveRows(S, Kit, mon, rightX, rowY, colW, rowH, rowGap)
|
|
local s = Kit.scale
|
|
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 = Kit.tapMin()
|
|
local clearX = rightX + colW - 8 * s - clear
|
|
|
|
local mvId = nil
|
|
if type(mv) == "table" then
|
|
mvId = (S.data and S.data.moves and S.data.moves[mv.id] and S.data.moves[mv.id].name) or mv.id or mv.name
|
|
elseif type(mv) == "number" then
|
|
mvId = S.data and S.data.moves and S.data.moves[mv] and S.data.moves[mv].name
|
|
if not mvId then
|
|
local okP, PokemonG3 = pcall(require, "src.core.game3.pokemon")
|
|
if okP and PokemonG3 and PokemonG3.moveName then
|
|
mvId = PokemonG3.moveName(mv)
|
|
end
|
|
end
|
|
elseif type(mv) == "string" then
|
|
mvId = mv
|
|
end
|
|
|
|
Kit.text("mono", tostring(slot), rightX + 10 * s,
|
|
ry + (rowH - Kit.textHeight("mono")) / 2, PAL.faint)
|
|
|
|
if mv then
|
|
local basePp = MonOps.getBasePp(S.data, mon, slot)
|
|
local ppUps = MonOps.getPpUps(mon, slot)
|
|
local maxPp = (mon.maxPp and mon.maxPp[slot]) or (type(mv) == "table" and mv.maxPp) or MonOps.calcMaxPp(basePp, ppUps)
|
|
local currentPp = (type(mv) == "table" and mv.pp) or (mon.pp and mon.pp[slot]) or maxPp
|
|
|
|
local ppUpW = 44 * s
|
|
local btnH = 22 * s
|
|
local ppUpX = clearX - 6 * s - ppUpW
|
|
local plusX = ppUpX - 4 * s - btnH
|
|
local minusX = plusX - 4 * s - btnH
|
|
|
|
-- PP Up button
|
|
local upLabel = ("+PP %d"):format(ppUps)
|
|
if Kit.button(ppUpX, ry + (rowH - btnH) / 2, ppUpW, btnH, upLabel,
|
|
{ kind = ppUps > 0 and "accent" or "ghost", font = "micro", radius = 5 * s }) then
|
|
Ops.setPpUps(S, mon, slot, (ppUps + 1) % 4)
|
|
end
|
|
|
|
-- Steppers for current PP
|
|
if Kit.stepper(minusX, ry + (rowH - btnH) / 2, btnH, btnH, "-", { font = "small" }) then
|
|
Ops.setPp(S, mon, slot, currentPp - 1)
|
|
end
|
|
if Kit.stepper(plusX, ry + (rowH - btnH) / 2, btnH, btnH, "+", { font = "small" }) then
|
|
Ops.setPp(S, mon, slot, currentPp + 1)
|
|
end
|
|
|
|
local ppText = ("%d/%d"):format(currentPp, maxPp)
|
|
local ppW = Kit.textWidth("tiny", ppText)
|
|
local ppColor = currentPp == 0 and PAL.red or (currentPp < maxPp and PAL.caption or PAL.text)
|
|
Kit.textRight("tiny", ppText, minusX - 6 * s,
|
|
ry + (rowH - Kit.textHeight("tiny")) / 2, ppColor)
|
|
|
|
local nameX = rightX + 28 * s
|
|
local nameW = math.max(20 * s, minusX - 10 * s - ppW - nameX)
|
|
Kit.text("monoRow", Kit.ellipsize("monoRow", mvId or "-- --", nameW),
|
|
nameX, ry + (rowH - Kit.textHeight("monoRow")) / 2, PAL.text)
|
|
|
|
-- Press on move name opens picker
|
|
if Kit.press(rightX, ry, math.max(0, minusX - 8 * s - rightX), rowH) then
|
|
Ops.openMovePicker(S, Kit, slot)
|
|
end
|
|
|
|
-- Clear move button
|
|
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
|
|
else
|
|
local nameX = rightX + 28 * s
|
|
local nameW = math.max(20 * s, clearX - 8 * s - nameX)
|
|
Kit.text("monoRow", Kit.ellipsize("monoRow", "-- --", nameW),
|
|
nameX, ry + (rowH - Kit.textHeight("monoRow")) / 2, PAL.faint)
|
|
|
|
if Kit.press(rightX, ry, clearX - rightX - 4 * s, rowH) then
|
|
Ops.openMovePicker(S, Kit, slot)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
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 " ..
|
|
"stat formulas, so HP and stats stay legal.",
|
|
x + (w - tw) / 2, y + h / 2 - Kit.textHeight("button"), tw, PAL.muted)
|
|
return
|
|
end
|
|
|
|
mon.dvs = mon.dvs or { attack = 0, defense = 0, speed = 0, special = 0, hp = 0 }
|
|
local def = (S.data and S.data.pokemon and (S.data.pokemon[mon.species] or S.data.pokemon[mon.speciesId])) or {}
|
|
local inner = w - 2 * pad
|
|
local capH = Kit.textHeight("caption")
|
|
local titleH = Kit.textHeight("title")
|
|
|
|
-- Reflow decisions, all against real pixels (#715): the DV | moves split
|
|
-- needs ~470px of card interior; below that the two stacks go one above
|
|
-- the other. A narrow card also drops the sprite to 64px so the header
|
|
-- text keeps room.
|
|
local narrow = inner < 470 * s
|
|
local sprite = (narrow and 64 or 96) * s
|
|
local rowH = math.max(Kit.tapMin(), 30 * s)
|
|
local rowGap = 8 * s
|
|
local cellH = 52 * s
|
|
local actH = math.max(Kit.tapMin(), 34 * s)
|
|
|
|
local hw = inner - sprite - 18 * s
|
|
local levelInHeader = hw >= levelRowWidth(Kit, mon)
|
|
local headerH
|
|
if levelInHeader then
|
|
headerH = math.max(sprite, titleH + 14 * s + 28 * s)
|
|
else
|
|
-- the level row does not fit beside the sprite: it drops below the
|
|
-- header block at full card width instead of painting over the sprite
|
|
headerH = math.max(sprite, titleH) + 12 * s + 28 * s
|
|
end
|
|
|
|
local isG3 = Gen.ofState(S) == 3
|
|
local colRowsH = (isG3 and 6 or 4) * (rowH + rowGap) - rowGap
|
|
local movesRowsH = 4 * (rowH + rowGap) - rowGap
|
|
local colsH
|
|
if isG3 then
|
|
if narrow then
|
|
colsH = (capH + 10 * s + colRowsH) * 2 + (capH + 10 * s + movesRowsH) + 28 * s + 10 * s + actH
|
|
else
|
|
colsH = (capH + 10 * s + colRowsH) + 14 * s + (capH + 10 * s + movesRowsH) + 12 * s + actH
|
|
end
|
|
else
|
|
if narrow then
|
|
colsH = (capH + 10 * s + colRowsH) * 2 + 14 * s + 10 * s + actH
|
|
else
|
|
colsH = capH + 10 * s + colRowsH + 12 * s + actH
|
|
end
|
|
end
|
|
-- the nickname section: a caption line (with the Clear button on it) plus
|
|
-- the field + Set row
|
|
local extraH = 0
|
|
if Gen.ofState(S) == 3 then
|
|
extraH = 154 * s
|
|
elseif Gen.ofState(S) == 2 then
|
|
extraH = 88 * s
|
|
end
|
|
if Gen.hasCaughtData(S.save, S.version) then extraH = extraH + 102 * s end
|
|
local nickFieldH = 30 * s
|
|
local contentH = pad + headerH + 18 * s
|
|
+ capH + 10 * s + nickFieldH + 18 * s
|
|
+ capH + 10 * s + cellH + 18 * s
|
|
+ extraH
|
|
+ colsH + pad
|
|
|
|
-- Called before the widgets so this frame already draws at the updated
|
|
-- offset; any list-free card body is fair game for the drag (#715).
|
|
S.inspectorScroll = Kit.scrollPixels(x, y, w, h, S.inspectorScroll, contentH)
|
|
Kit.pushClip(x, y, w, h)
|
|
local cx = x + pad
|
|
local cy = y + pad - S.inspectorScroll
|
|
|
|
-- ---------------------------------------------------------- header row
|
|
MonEditor.drawSprite(S, Kit, mon.species or mon.speciesId, cx, cy, sprite)
|
|
local hx = cx + sprite + 18 * s
|
|
|
|
-- One control instead of a pair of arrows: cycling walked the catalog an
|
|
-- entry at a time (151 taps to cross the dex) and ran a full MonOps
|
|
-- recalculation on every step, including on records the Gen1 formulas
|
|
-- cannot use, which is what crashed the editor (#541). This opens the
|
|
-- searchable picker; the species name itself is a second, larger target.
|
|
local pickH = 30 * s
|
|
local pickW = math.min(150 * s, math.max(90 * s, hw * 0.6))
|
|
local px = hx + hw - pickW
|
|
local py = cy + (titleH - pickH) / 2
|
|
|
|
-- the species name yields to the button instead of running under it (#715)
|
|
local spName = (type(mon.species) == "string" and mon.species) or def.name or tostring(mon.species or "POKEMON")
|
|
local name = Kit.ellipsize("title", spName, math.max(40 * s, px - hx - 12 * s))
|
|
Kit.text("title", name, hx, cy, PAL.heading)
|
|
local nameW = Kit.textWidth("title", name)
|
|
if nameW + Kit.textWidth("tiny", "#000") + 12 * s < px - hx - 12 * s then
|
|
Kit.text("tiny", ("#%03d"):format(tonumber(def and def.dex) or (tonumber(mon.speciesId) or 0)), hx + nameW + 12 * s,
|
|
cy + titleH - Kit.textHeight("tiny") - 2 * s, PAL.caption)
|
|
end
|
|
|
|
local openPicker = Kit.button(px, py, pickW, pickH, "Change species",
|
|
{ kind = "accent", font = "small", radius = 8 * s })
|
|
if not openPicker then
|
|
openPicker = Kit.press(hx, cy, math.max(0, px - hx - 10 * s), titleH)
|
|
end
|
|
if openPicker then Ops.openSpeciesPicker(S, Kit) end
|
|
|
|
-- level stepper: -5 -1 [Lv] +1 +5, matching MonOps.setLevel's 1..100 clamp
|
|
if levelInHeader then
|
|
drawLevelRow(S, Kit, mon, hx, cy + titleH + 14 * s)
|
|
else
|
|
drawLevelRow(S, Kit, mon, cx, cy + math.max(sprite, titleH) + 12 * s)
|
|
end
|
|
|
|
-- ---------------------------------------------------------- nickname
|
|
-- Editing the field is a draft (S.nicknameDraft) held on the mon it belongs
|
|
-- to; Set / Enter commit it through Ops.setNickname, which clears on an
|
|
-- empty value, and Clear goes through Ops.clearNickname. The draft resets
|
|
-- when the selection moves so one mon's typing can never leak onto another.
|
|
local nickY = cy + headerH + 18 * s
|
|
Kit.caption(cx, nickY, "NICKNAME")
|
|
local clearW = 74 * s
|
|
local clearH = 24 * s
|
|
if Kit.button(cx + inner - clearW, nickY + (capH - clearH) / 2, clearW, clearH,
|
|
"Clear", { kind = "danger", font = "micro", radius = 6 * s }) then
|
|
Ops.clearNickname(S, mon)
|
|
S.nicknameDraft = ""
|
|
end
|
|
local fieldY = nickY + capH + 10 * s
|
|
local setW = 64 * s
|
|
local fieldW = inner - setW - 10 * s
|
|
if S.nicknameMon ~= mon then
|
|
local switching = S.nicknameMon ~= nil
|
|
S.nicknameMon = mon
|
|
S.nicknameDraft = mon.nickname or ""
|
|
-- a still-focused field would keep appending keystrokes to the newly
|
|
-- selected mon; the selection move counts as leaving the field. The
|
|
-- first sync (nicknameMon starts nil) never blurs: the species picker
|
|
-- owns focus when it opens, and blurring there drops the player's typing.
|
|
if switching and Kit.focus == "mon-nickname" then Kit.blur() end
|
|
end
|
|
S.nicknameDraft = Kit.textfield("mon-nickname", cx, fieldY, fieldW, nickFieldH,
|
|
S.nicknameDraft or "", "no nickname",
|
|
{ sanitize = function(value) return Ops.nicknameSanitize(S, value) end })
|
|
if Kit.button(cx + fieldW + 10 * s, fieldY, setW, nickFieldH, "Set",
|
|
{ kind = "accent", font = "small", radius = 8 * s }) then
|
|
if Ops.setNickname(S, mon, S.nicknameDraft) then
|
|
S.nicknameDraft = mon.nickname or ""
|
|
end
|
|
end
|
|
|
|
-- ------------------------------------------------------- derived stats
|
|
local statsY = nickY + capH + 10 * s + nickFieldH + 18 * s
|
|
if isG3 then
|
|
Kit.caption(cx, statsY, "STATS . recalculated from level + IVs + EVs + nature")
|
|
else
|
|
Kit.caption(cx, statsY, "STATS . recalculated from level + DVs")
|
|
end
|
|
statsY = statsY + capH + 10 * s
|
|
local STAT_KEYS = Gen.ofState(S) >= 2 and STAT_KEYS_G2 or STAT_KEYS_G1
|
|
local gap = 12 * s
|
|
local cellW = (inner - gap * (#STAT_KEYS - 1)) / #STAT_KEYS
|
|
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 mon.stats[st.field:lower()])) or mon[st.field] or 0
|
|
Kit.text("micro", st.key, bx + 12 * s, statsY + 8 * s, PAL.caption)
|
|
Kit.text("stat", tostring(value), bx + 12 * s,
|
|
statsY + 8 * s + Kit.textHeight("micro") + 2 * s, PAL.heading)
|
|
Kit.meter(bx + 12 * s, statsY + cellH - 12 * s, cellW - 24 * s, 5 * s,
|
|
value / STAT_SCALE * 100, PAL.blue)
|
|
end
|
|
|
|
-- --------------------------------------------------- DVs | moves split
|
|
local colY = statsY + cellH + 18 * s
|
|
if Gen.ofState(S) == 3 then
|
|
local extraY = colY
|
|
Kit.caption(cx, extraY, "FIRE RED CHARACTERISTICS . PID " .. string.format("0x%08X", mon.personality or 0))
|
|
extraY = extraY + capH + 8 * s
|
|
local row = 28 * s
|
|
|
|
-- Held item row
|
|
local heldId = mon.heldItem or mon.item
|
|
local heldName = heldId and (S.data and S.data.items and S.data.items[heldId] and (S.data.items[heldId].name or S.data.items[heldId].id)) or tostring(heldId or "none")
|
|
Kit.text("tiny", "HELD " .. tostring(heldName), cx, extraY, PAL.text)
|
|
if heldId then
|
|
if Kit.button(cx + inner - 70 * s, extraY, 70 * s, row, "Clear item",
|
|
{ kind = "danger", font = "tiny", radius = 6 * s }) then
|
|
Ops.setHeldItem(S, mon, nil)
|
|
end
|
|
end
|
|
extraY = extraY + row + 6 * s
|
|
|
|
-- Nature and Friendship
|
|
local SummaryData = package.loaded["src.core.game3.summary_data"] or require("src.core.game3.summary_data")
|
|
local natureIdx = mon.nature or ((mon.personality or 0) % 25)
|
|
local okN, nName = pcall(function() return SummaryData.NATURES[natureIdx] end)
|
|
local natureName = (okN and nName) or ("Nature " .. natureIdx)
|
|
Kit.text("tiny", "NATURE " .. natureName, cx, extraY, PAL.text)
|
|
if Kit.stepper(cx + 140 * s, extraY, 28 * s, row, "<", { font = "small" }) then
|
|
Ops.setNature(S, mon, (natureIdx - 1 + 25) % 25)
|
|
end
|
|
if Kit.stepper(cx + 174 * s, extraY, 28 * s, row, ">", { font = "small" }) then
|
|
Ops.setNature(S, mon, (natureIdx + 1) % 25)
|
|
end
|
|
|
|
local friendVal = mon.friendship or mon.happiness or 70
|
|
Kit.text("tiny", ("FRIENDSHIP %d"):format(friendVal), cx + 220 * s, extraY, PAL.text)
|
|
if Kit.stepper(cx + 340 * s, extraY, 28 * s, row, "-", { font = "small" }) then
|
|
Ops.setHappiness(S, mon, friendVal - 10)
|
|
end
|
|
if Kit.stepper(cx + 374 * s, extraY, 28 * s, row, "+", { font = "small" }) then
|
|
Ops.setHappiness(S, mon, friendVal + 10)
|
|
end
|
|
extraY = extraY + row + 6 * s
|
|
|
|
-- Ability, Gender, Shiny
|
|
local PokemonG3 = require("src.core.game3.pokemon")
|
|
local abilitySlot = ((mon.personality or 0) % 2)
|
|
local abilityId = mon.ability or PokemonG3.abilityId(mon.speciesId or mon.species, mon.personality or 0)
|
|
local abName = PokemonG3.abilityName and PokemonG3.abilityName(abilityId) or ("Ability " .. (abilitySlot + 1))
|
|
if Kit.button(cx, extraY, 150 * s, row, abName .. " (Slot " .. (abilitySlot + 1) .. ")",
|
|
{ kind = "accent", font = "micro", radius = 6 * s }) then
|
|
Ops.setAbility(S, mon, (abilitySlot + 1) % 2)
|
|
end
|
|
|
|
local curGender = mon.gender or PokemonG3.gender(mon.speciesId or mon.species, mon.personality or 0)
|
|
local genderLabel = curGender == "M" and "♂ MALE" or (curGender == "F" and "♀ FEMALE" or "— GENDERLESS")
|
|
if Kit.button(cx + 160 * s, extraY, 110 * s, row, genderLabel,
|
|
{ kind = "ghost", font = "micro", radius = 6 * s }) then
|
|
local nextG = curGender == "M" and "F" or (curGender == "F" and "M" or "U")
|
|
Ops.setMonGender(S, mon, nextG)
|
|
end
|
|
|
|
local isShiny = SummaryData.isShiny(mon)
|
|
if Kit.chip(cx + 280 * s, extraY, 80 * s, row, "★ SHINY", isShiny, PAL.yellow, PAL.steel) then
|
|
Ops.setShiny(S, mon, not isShiny)
|
|
end
|
|
extraY = extraY + row + 12 * s
|
|
colY = extraY
|
|
elseif Gen.ofState(S) == 2 then
|
|
local extraY = colY
|
|
Kit.caption(cx, extraY, Gen.editionLabel(S.save, S.version))
|
|
extraY = extraY + capH + 8 * s
|
|
local row = 28 * s
|
|
Kit.text("tiny", "HELD " .. tostring(mon.item or "none"), cx, extraY, PAL.text)
|
|
if Kit.button(cx + inner - 70 * s, extraY, 70 * s, row, "Clear item",
|
|
{ kind = "danger", font = "tiny", radius = 6 * s }) then
|
|
Ops.setHeldItem(S, mon, nil)
|
|
end
|
|
extraY = extraY + row + 6 * s
|
|
Kit.text("tiny", ("HAPPINESS %d"):format(mon.happiness or 0), cx, extraY, PAL.text)
|
|
if Kit.stepper(cx + 140 * s, extraY, 28 * s, row, "-", { font = "small" }) then
|
|
Ops.setHappiness(S, mon, (mon.happiness or 0) - 10)
|
|
end
|
|
if Kit.stepper(cx + 174 * s, extraY, 28 * s, row, "+", { font = "small" }) then
|
|
Ops.setHappiness(S, mon, (mon.happiness or 0) + 10)
|
|
end
|
|
Kit.text("tiny", ("PKRS %d"):format(mon.pokerus or 0), cx + 220 * s, extraY, PAL.text)
|
|
if Kit.stepper(cx + 300 * s, extraY, 28 * s, row, "-", { font = "small" }) then
|
|
Ops.setPokerus(S, mon, (mon.pokerus or 0) - 1)
|
|
end
|
|
if Kit.stepper(cx + 334 * s, extraY, 28 * s, row, "+", { font = "small" }) then
|
|
Ops.setPokerus(S, mon, (mon.pokerus or 0) + 1)
|
|
end
|
|
extraY = extraY + row + 4 * s
|
|
local bits = {}
|
|
if mon.gender then bits[#bits + 1] = mon.gender end
|
|
if mon.shiny then bits[#bits + 1] = "shiny" end
|
|
if mon.unownLetter then bits[#bits + 1] = "Unown " .. tostring(mon.unownLetter) end
|
|
Kit.text("tiny", table.concat(bits, " ") ~= "" and table.concat(bits, " ")
|
|
or "gender/shiny follow DVs", cx, extraY, PAL.caption)
|
|
extraY = extraY + 22 * s
|
|
if Gen.hasCaughtData(S.save, S.version) then
|
|
extraY = drawCaughtRows(S, Kit, mon, cx, extraY, inner, row)
|
|
end
|
|
colY = extraY
|
|
end
|
|
if isG3 then
|
|
local ivEvRowsH = 6 * (rowH + rowGap) - rowGap
|
|
local mRowsH = 4 * (rowH + rowGap) - rowGap
|
|
local totalEvs = totalEvsOf(mon)
|
|
|
|
if narrow then
|
|
-- 1. IVs section
|
|
Kit.caption(cx, colY, "IVs (0-31)")
|
|
local maxIvW = 84 * s
|
|
local maxIvH = 22 * s
|
|
if Kit.button(cx + inner - maxIvW, colY + (capH - maxIvH) / 2, maxIvW, maxIvH,
|
|
"Max all (31)", { kind = "good", font = "micro", radius = 6 * s }) then
|
|
Ops.maxIvs(S, mon)
|
|
end
|
|
local ivRowY = colY + capH + 10 * s
|
|
drawIvRowsG3(S, Kit, mon, cx, ivRowY, inner, rowH, rowGap)
|
|
|
|
-- 2. EVs section
|
|
local evY = ivRowY + ivEvRowsH + 14 * s
|
|
Kit.caption(cx, evY, "EVs (0-255)")
|
|
local clearEvW = 54 * s
|
|
local clearEvH = 22 * s
|
|
if Kit.button(cx + inner - clearEvW, evY + (capH - clearEvH) / 2, clearEvW, clearEvH,
|
|
"Clear", { kind = "danger", font = "micro", radius = 6 * s }) then
|
|
Ops.clearEvs(S, mon)
|
|
end
|
|
local evTotalText = ("TOTAL %d / 510"):format(totalEvs)
|
|
Kit.textRight("tiny", evTotalText, cx + inner - clearEvW - 10 * s,
|
|
evY + (capH - Kit.textHeight("tiny")) / 2, totalEvs >= 510 and PAL.green or PAL.caption)
|
|
local evRowY = evY + capH + 10 * s
|
|
drawEvRowsG3(S, Kit, mon, cx, evRowY, inner, rowH, rowGap)
|
|
|
|
-- 3. Moves section
|
|
local movesY = evRowY + ivEvRowsH + 14 * s
|
|
Kit.caption(cx, movesY, "MOVES")
|
|
local maxPpW = 84 * s
|
|
local maxPpH = 22 * s
|
|
if Kit.button(cx + inner - maxPpW, movesY + (capH - maxPpH) / 2, maxPpW, maxPpH,
|
|
"Max all PP", { kind = "good", font = "micro", radius = 6 * s }) then
|
|
Ops.maxAllPpUps(S, mon)
|
|
end
|
|
Kit.textRight("tiny", "click a slot to search", cx + inner - maxPpW - 10 * s,
|
|
movesY + (capH - Kit.textHeight("tiny")) / 2, PAL.caption)
|
|
local mRowY = movesY + capH + 10 * s
|
|
drawMoveRows(S, Kit, mon, cx, mRowY, inner, rowH, rowGap)
|
|
|
|
local actY = mRowY + mRowsH + 10 * s
|
|
local actW = (inner - 10 * s) / 2
|
|
if Kit.button(cx, actY, actW, actH, "Reset to learnset",
|
|
{ font = "small", radius = 9 * s }) then
|
|
Ops.resetMoves(S, mon)
|
|
end
|
|
if Kit.button(cx + actW + 10 * s, actY, actW, actH, "Full heal",
|
|
{ kind = "good", font = "small", radius = 9 * s }) then
|
|
Ops.healMon(S, mon)
|
|
end
|
|
else
|
|
local colGap = 18 * s
|
|
local colW = (inner - colGap) / 2
|
|
local rightX = cx + colW + colGap
|
|
|
|
-- Left: IVs
|
|
Kit.caption(cx, colY, "IVs (0-31)")
|
|
local maxIvW = 84 * s
|
|
local maxIvH = 22 * s
|
|
if Kit.button(cx + colW - maxIvW, colY + (capH - maxIvH) / 2, maxIvW, maxIvH,
|
|
"Max all (31)", { kind = "good", font = "micro", radius = 6 * s }) then
|
|
Ops.maxIvs(S, mon)
|
|
end
|
|
local ivRowY = colY + capH + 10 * s
|
|
drawIvRowsG3(S, Kit, mon, cx, ivRowY, colW, rowH, rowGap)
|
|
|
|
-- Right: EVs
|
|
Kit.caption(rightX, colY, "EVs (0-255)")
|
|
local clearEvW = 54 * s
|
|
local clearEvH = 22 * s
|
|
if Kit.button(rightX + colW - clearEvW, colY + (capH - clearEvH) / 2, clearEvW, clearEvH,
|
|
"Clear", { kind = "danger", font = "micro", radius = 6 * s }) then
|
|
Ops.clearEvs(S, mon)
|
|
end
|
|
local evTotalText = ("TOTAL %d / 510"):format(totalEvs)
|
|
Kit.textRight("tiny", evTotalText, rightX + colW - clearEvW - 10 * s,
|
|
colY + (capH - Kit.textHeight("tiny")) / 2, totalEvs >= 510 and PAL.green or PAL.caption)
|
|
drawEvRowsG3(S, Kit, mon, rightX, ivRowY, colW, rowH, rowGap)
|
|
|
|
-- Moves Section below IVs and EVs
|
|
local movesY = ivRowY + ivEvRowsH + 14 * s
|
|
Kit.caption(cx, movesY, "MOVES")
|
|
local maxPpW = 84 * s
|
|
local maxPpH = 22 * s
|
|
if Kit.button(cx + inner - maxPpW, movesY + (capH - maxPpH) / 2, maxPpW, maxPpH,
|
|
"Max all PP", { kind = "good", font = "micro", radius = 6 * s }) then
|
|
Ops.maxAllPpUps(S, mon)
|
|
end
|
|
Kit.textRight("tiny", "click a slot to search", cx + inner - maxPpW - 10 * s,
|
|
movesY + (capH - Kit.textHeight("tiny")) / 2, PAL.caption)
|
|
local mRowY = movesY + capH + 10 * s
|
|
drawMoveRows(S, Kit, mon, cx, mRowY, inner, rowH, rowGap)
|
|
|
|
local actY = mRowY + mRowsH + 12 * s
|
|
local actW = (inner - 10 * s) / 2
|
|
if Kit.button(cx, actY, actW, actH, "Reset to learnset",
|
|
{ font = "small", radius = 9 * s }) then
|
|
Ops.resetMoves(S, mon)
|
|
end
|
|
if Kit.button(cx + actW + 10 * s, actY, actW, actH, "Full heal",
|
|
{ kind = "good", font = "small", radius = 9 * s }) then
|
|
Ops.healMon(S, mon)
|
|
end
|
|
end
|
|
elseif narrow then
|
|
-- stacked: DVs first, then moves, then the two actions side by side at
|
|
-- full width (#715)
|
|
Kit.caption(cx, colY, "DVs")
|
|
Kit.textRight("tiny", ("HP DV auto-derived . %d"):format((mon.dvs and mon.dvs.hp) or 0),
|
|
cx + inner, colY, PAL.caption)
|
|
local rowY = colY + capH + 10 * s
|
|
drawDvRows(S, Kit, mon, cx, rowY, inner, rowH, rowGap)
|
|
|
|
local movesY = rowY + colRowsH + 14 * s
|
|
Kit.caption(cx, movesY, "MOVES")
|
|
local maxPpW = 84 * s
|
|
local maxPpH = 22 * s
|
|
if Kit.button(cx + inner - maxPpW, movesY + (capH - maxPpH) / 2, maxPpW, maxPpH,
|
|
"Max all PP", { kind = "good", font = "micro", radius = 6 * s }) then
|
|
Ops.maxAllPpUps(S, mon)
|
|
end
|
|
Kit.textRight("tiny", "click a slot to search", cx + inner - maxPpW - 10 * s,
|
|
movesY + (capH - Kit.textHeight("tiny")) / 2, PAL.caption)
|
|
local mRowY = movesY + capH + 10 * s
|
|
drawMoveRows(S, Kit, mon, cx, mRowY, inner, rowH, rowGap)
|
|
|
|
local actY = mRowY + colRowsH + 10 * s
|
|
local actW = (inner - 10 * s) / 2
|
|
if Kit.button(cx, actY, actW, actH, "Reset to learnset",
|
|
{ font = "small", radius = 9 * s }) then
|
|
Ops.resetMoves(S, mon)
|
|
end
|
|
if Kit.button(cx + actW + 10 * s, actY, actW, actH, "Full heal",
|
|
{ kind = "good", font = "small", radius = 9 * s }) then
|
|
Ops.healMon(S, mon)
|
|
end
|
|
else
|
|
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 and mon.dvs.hp) or 0),
|
|
cx + colW, colY, PAL.caption)
|
|
Kit.caption(rightX, colY, "MOVES")
|
|
local maxPpW = 84 * s
|
|
local maxPpH = 22 * s
|
|
if Kit.button(rightX + colW - maxPpW, colY + (capH - maxPpH) / 2, maxPpW, maxPpH,
|
|
"Max all PP", { kind = "good", font = "micro", radius = 6 * s }) then
|
|
Ops.maxAllPpUps(S, mon)
|
|
end
|
|
Kit.textRight("tiny", "click a slot to search", rightX + colW - maxPpW - 10 * s,
|
|
colY + (capH - Kit.textHeight("tiny")) / 2, PAL.caption)
|
|
|
|
local rowY = colY + capH + 10 * s
|
|
drawDvRows(S, Kit, mon, cx, rowY, colW, rowH, rowGap)
|
|
drawMoveRows(S, Kit, mon, rightX, rowY, colW, rowH, rowGap)
|
|
|
|
local actY = rowY + colRowsH + 12 * 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
|
|
Kit.popClip()
|
|
end
|
|
|
|
return MonEditor
|