mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-25 22:46:36 -04:00
970 lines
38 KiB
Lua
970 lines
38 KiB
Lua
local Ops = require("Ops")
|
|
local Gen = require("Gen")
|
|
local P = require("Properties")
|
|
local MonOps = require("MonOps")
|
|
local Theme = require("Theme")
|
|
local PAL = Theme.PAL
|
|
local Motion = require("Motion")
|
|
local Chooser = require("Chooser")
|
|
local Touch = require("TouchEditor")
|
|
local Limits = require("ValueLimits")
|
|
local Named = require("NamedChoices")
|
|
local Legality = require("Legality")
|
|
local Form = require("Form")
|
|
local MonEditor = require("MonEditor")
|
|
local Body = {}
|
|
|
|
local SECTIONS = {
|
|
{ "main", "Main" },
|
|
{ "stats", "Stats" },
|
|
{ "moves", "Moves" },
|
|
{ "origin", "Origin" },
|
|
{ "extras", "Extras" },
|
|
{ "checks", "Checks" },
|
|
}
|
|
local NATURE_STATS = { "Atk", "Def", "Spe", "SpA", "SpD" }
|
|
local GENDER_NAMES = { M = "Male", F = "Female", male = "Male", female = "Female" }
|
|
local STAT_NAMES = {
|
|
hp = "HP", atk = "Attack", def = "Defense", spa = "Sp. Atk", spd = "Sp. Def", spe = "Speed",
|
|
attack = "Attack", defense = "Defense", speed = "Speed", special = "Special",
|
|
}
|
|
local STATUSES = {
|
|
{ "healthy", "Healthy" },
|
|
{ "SLP", "Asleep" },
|
|
{ "PSN", "Poisoned" },
|
|
{ "BRN", "Burned" },
|
|
{ "FRZ", "Frozen" },
|
|
{ "PAR", "Paralyzed" },
|
|
}
|
|
|
|
local function titleCase(str)
|
|
str = tostring(str or "")
|
|
if str:find("%l") then return str end
|
|
return (str:lower():gsub("(%a)([%w']*)", function(a, b) return a:upper() .. b end))
|
|
end
|
|
|
|
local function typeName(S, t)
|
|
if t == nil then return nil end
|
|
if Gen.ofState(S) == 3 then
|
|
if type(t) == "number" then
|
|
local name
|
|
for k, v in pairs(require("src.core.game3.battle.types").ID) do
|
|
if v == t then name = k end
|
|
end
|
|
t = name or tostring(t)
|
|
end
|
|
t = tostring(t):gsub("^TYPE_", "")
|
|
if t == "MYSTERY" then return "???" end
|
|
else
|
|
local ok, name = pcall(require("src.battle.TypeChart").displayName, t, S.data)
|
|
if ok and name then t = name end
|
|
end
|
|
if t == "???" then return t end
|
|
return titleCase(t)
|
|
end
|
|
|
|
local function hpColor(frac)
|
|
if frac <= 0.2 then return PAL.red end
|
|
if frac <= 0.5 then return PAL.yellow end
|
|
return PAL.green
|
|
end
|
|
|
|
local function hint(C, id, x, y, w)
|
|
local message = C.issues.fields[id]
|
|
if not message then return 0 end
|
|
return 6 * C.s + Touch.issue(C.Kit, message, x, y + 6 * C.s, w)
|
|
end
|
|
|
|
local function speciesTypes(C)
|
|
local S, mon = C.S, C.mon
|
|
local out = {}
|
|
if C.g == 3 then
|
|
local t = require("src.core.game3.pokemon").types(mon.species)
|
|
out[1] = typeName(S, t[1])
|
|
if t[2] ~= t[1] then out[2] = typeName(S, t[2]) end
|
|
return out
|
|
end
|
|
local t = (C.def and C.def.types) or mon.types or {}
|
|
for i, v in ipairs(t) do
|
|
if i == 1 or v ~= t[1] then out[#out + 1] = typeName(S, v) end
|
|
end
|
|
return out
|
|
end
|
|
|
|
local function natureOf(mon)
|
|
return mon.nature or (tonumber(mon.personality) or 0) % 25
|
|
end
|
|
|
|
local function natureName(id)
|
|
local ok, name = pcall(function() return require("src.core.game3.summary_data").NATURES[id] end)
|
|
return ok and name and titleCase(name) or tostring(id)
|
|
end
|
|
|
|
-- src/pokemon.c:1365
|
|
local function natureEffect(id)
|
|
id = tonumber(id) or 0
|
|
local up, down = math.floor(id / 5) + 1, id % 5 + 1
|
|
if up == down or not NATURE_STATS[up] then return "No stat change" end
|
|
return "+" .. NATURE_STATS[up] .. " -" .. NATURE_STATS[down]
|
|
end
|
|
|
|
local function genderOf(C)
|
|
local mon = C.mon
|
|
if C.g == 3 then
|
|
return mon.gender or require("src.core.game3.pokemon").gender(mon.species, tonumber(mon.personality) or 0)
|
|
elseif C.g == 2 then
|
|
local ok, gender = pcall(require("src.core.gen2.Breeding").gender, C.def, mon.dvs)
|
|
return ok and gender or nil
|
|
end
|
|
end
|
|
|
|
local function speciesName(C)
|
|
local def = C.def
|
|
return (def and def.name) or tostring(C.mon.species or C.mon.speciesId)
|
|
end
|
|
|
|
local function savedStatus(mon)
|
|
local status = mon.status or "healthy"
|
|
if type(status) == "number" then
|
|
status = ({ [0] = "healthy", [8] = "PSN", [16] = "BRN", [32] = "FRZ", [64] = "PAR", [128] = "TOX" })[status]
|
|
or (status >= 1 and status <= 7 and "SLP") or status
|
|
end
|
|
return status
|
|
end
|
|
|
|
local function heroActions(C)
|
|
local S, mon = C.S, C.mon
|
|
local list = {}
|
|
if C.g == 3 then
|
|
local shiny = MonEditor.isShiny(S, mon)
|
|
list[#list + 1] = { "Shiny", function()
|
|
Ops.setShiny(S, mon, not shiny)
|
|
end, {
|
|
kind = shiny and "warn" or "ghost",
|
|
icon = "sparkles",
|
|
iconInk = not shiny and PAL.yellow or nil,
|
|
invalid = C.issues.fields.shiny ~= nil,
|
|
id = "shiny",
|
|
} }
|
|
end
|
|
list[#list + 1] = { "Full heal", function()
|
|
Ops.healMon(S, mon)
|
|
end, { kind = "ghost", icon = "heart", iconInk = PAL.green } }
|
|
return list
|
|
end
|
|
|
|
local function metaLine(C)
|
|
local mon = C.mon
|
|
local parts = { "Lv " .. tostring(mon.level or "?") }
|
|
if C.g == 3 then parts[#parts + 1] = natureName(natureOf(mon)) end
|
|
local gender = GENDER_NAMES[genderOf(C) or ""]
|
|
if gender then parts[#parts + 1] = gender end
|
|
return table.concat(parts, " · ")
|
|
end
|
|
|
|
local function drawHero(C, x, y, w, stacked)
|
|
local S, Kit, mon, s = C.S, C.Kit, C.mon, C.s
|
|
local box = math.floor((stacked and 96 or 92) * s)
|
|
local actions = heroActions(C)
|
|
local actW, gap = 0, 10 * s
|
|
for i, a in ipairs(actions) do
|
|
actW = actW + Kit.buttonWidth(a[1], { font = "button", icon = a[3].icon }, C.row) + (i > 1 and gap or 0)
|
|
end
|
|
local inline = not stacked and w - box - actW - 36 * s >= 240 * s
|
|
local tx, ty = x + box + 18 * s, y
|
|
local textW = inline and (w - box - actW - 36 * s) or (w - box - 18 * s)
|
|
if stacked then
|
|
tx, ty, textW = x, y + box + 16 * s, w
|
|
end
|
|
Theme.fillRounded(x, y, box, box, PAL.rowBg, 0.7, Theme.radius())
|
|
Theme.stroke(x, y, box, box, Theme.radius(), PAL.cardBorder, 0.3, 1)
|
|
MonEditor.drawSprite(S, Kit, mon.species, x + 6 * s, y + 6 * s, box - 12 * s, mon)
|
|
|
|
local def = C.def or {}
|
|
local dex = def.dex or def.national
|
|
local dexStr = dex and ("#" .. tostring(dex)) or nil
|
|
local dexW = dexStr and Kit.textWidth("button", dexStr) + 12 * s or 0
|
|
local name = Kit.ellipsize("title", MonEditor.displayName(S, mon), math.max(40 * s, textW - dexW))
|
|
Kit.textBold("title", name, tx, ty, PAL.heading)
|
|
if dexStr then
|
|
Kit.text("button", dexStr, tx + Kit.textWidth("title", name) + 12 * s,
|
|
ty + Kit.textHeight("title") - Kit.textHeight("button") - 2 * s, PAL.muted)
|
|
end
|
|
ty = ty + Kit.textHeight("title") + 8 * s
|
|
|
|
local chipH = math.floor(Kit.textHeight("small") + 10 * s)
|
|
local cx = tx
|
|
for _, t in ipairs(speciesTypes(C)) do
|
|
local cw = Kit.textWidth("small", t) + 18 * s
|
|
if cx > tx and cx + cw > tx + textW then
|
|
cx, ty = tx, ty + chipH + 6 * s
|
|
end
|
|
Theme.fillRounded(cx, ty, cw, chipH, PAL.rowBg, 0.8, 7 * s)
|
|
Theme.strokeRounded(cx, ty, cw, chipH, PAL.line, 0.45, 1, 7 * s)
|
|
Kit.textCenterBold("small", t, cx, ty + (chipH - Kit.textHeight("small")) / 2, cw, PAL.heading)
|
|
cx = cx + cw + 8 * s
|
|
end
|
|
local meta = metaLine(C)
|
|
if cx > tx and cx + Kit.textWidth("small", meta) > tx + textW then
|
|
cx, ty = tx, ty + chipH + 6 * s
|
|
end
|
|
Kit.text("small", Kit.ellipsize("small", meta, tx + textW - cx), cx, ty + (chipH - Kit.textHeight("small")) / 2, PAL.muted)
|
|
ty = ty + chipH + 12 * s
|
|
|
|
local maxHp = mon.maxHp or (mon.stats and mon.stats.hp) or 0
|
|
local hp = tonumber(mon.hp) or 0
|
|
local hpText = ("HP %d/%d"):format(hp, maxHp)
|
|
local barW = math.max(60 * s, math.min(400 * s, textW - Kit.textWidth("small", hpText) - 14 * s))
|
|
local frac = Theme.clamp(hp / math.max(1, maxHp), 0, 1)
|
|
Kit.meter(tx, ty + (Kit.textHeight("small") - 6 * s) / 2, barW, 6 * s, frac * 100, hpColor(frac))
|
|
Kit.text("small", hpText, tx + barW + 14 * s, ty, PAL.muted)
|
|
ty = ty + Kit.textHeight("small")
|
|
local bottom = math.max(y + box, ty)
|
|
|
|
if inline then
|
|
local ax, ay = x + w - actW, y + math.max(0, (box - C.row) / 2 - 8 * s)
|
|
for _, a in ipairs(actions) do
|
|
local opts = {}
|
|
for k, v in pairs(a[3]) do opts[k] = v end
|
|
opts.font = "button"
|
|
local bw = Kit.buttonWidth(a[1], opts, C.row)
|
|
if Kit.button(ax, ay, bw, C.row, a[1], opts) then a[2]() end
|
|
ax = ax + bw + gap
|
|
end
|
|
else
|
|
local fy = bottom + 16 * s
|
|
bottom = fy + Form.flow(Kit, nil, actions, x, fy, w)
|
|
end
|
|
local shiny = C.issues.fields.shiny
|
|
if shiny then bottom = bottom + hint(C, "shiny", x, bottom, w) end
|
|
return bottom - y
|
|
end
|
|
|
|
local function sectionOptions(C)
|
|
local list = {}
|
|
for _, e in ipairs(SECTIONS) do
|
|
local errors = C.issues.sections[e[1]] or 0
|
|
list[#list + 1] = {
|
|
e[1], e[2],
|
|
errors = errors,
|
|
icon = errors > 0 and "triangle-alert" or C.Kit.navigationIcon(e[2]),
|
|
}
|
|
end
|
|
return list
|
|
end
|
|
|
|
local function drawNav(C, x, y, w, stacked)
|
|
local S, Kit = C.S, C.Kit
|
|
local options = sectionOptions(C)
|
|
local function pick(value, index)
|
|
if stacked then
|
|
local keep = S._navKeep or 0
|
|
S.monSection = value
|
|
Kit.blur()
|
|
Ops.disarm(S)
|
|
S.propertyChoice = nil
|
|
S.inspectorScroll = keep
|
|
return
|
|
end
|
|
local old = 1
|
|
for i, o in ipairs(options) do
|
|
if o[1] == S.monSection then old = i end
|
|
end
|
|
Motion.change(S, "monSection", value, index >= old and 1 or -1)
|
|
end
|
|
if Form.tabs(S, Kit, "monSection", options, x, y, w, C.row, pick) then return end
|
|
Chooser.navigation(S, Kit, "monSection", "Pokemon section", options, x, y,
|
|
math.min(w, 360 * C.s), C.row, stacked and function()
|
|
S.inspectorScroll = S._navKeep or 0
|
|
end or nil)
|
|
end
|
|
|
|
local function identityColumn(C, x, y, w)
|
|
local S, Kit, mon, g, s, row, issues = C.S, C.Kit, C.mon, C.g, C.s, C.row, C.issues
|
|
local gap, block = 10 * s, 22 * s
|
|
local cy = y + Kit.caption(x, y, "IDENTITY") + 16 * s
|
|
|
|
cy = cy + Form.label(Kit, "Species", x, cy, w, nil, issues.fields.species and PAL.red)
|
|
local chOpts = { font = "button", icon = "pencil", id = "change-species" }
|
|
local chW = Kit.buttonWidth("Change", chOpts, row)
|
|
Form.box(Kit, x, cy, w - chW - gap, row, speciesName(C), issues.fields.species ~= nil)
|
|
if Kit.button(x + w - chW, cy, chW, row, "Change", chOpts) then
|
|
Ops.openSpeciesPicker(S, Kit)
|
|
end
|
|
cy = cy + row
|
|
cy = cy + hint(C, "species", x, cy, w) + block
|
|
|
|
if S.nicknameMon ~= mon then
|
|
S.nicknameMon, S.nicknameDraft, S._nickFocus = mon, mon.nickname or "", nil
|
|
end
|
|
local focused = Kit.focus == "mon-nickname"
|
|
if S._nickFocus == mon and not focused then
|
|
S._nickFocus = nil
|
|
if (S.nicknameDraft or "") ~= (mon.nickname or "") then
|
|
Ops.setNickname(S, mon, S.nicknameDraft)
|
|
S.nicknameDraft = mon.nickname or ""
|
|
end
|
|
end
|
|
if focused then S._nickFocus = mon end
|
|
local count = Ops.nicknameLength(S.nicknameDraft or "") .. "/" .. Ops.NICKNAME_MAX
|
|
cy = cy + Form.label(Kit, "Nickname", x, cy, w, count, issues.fields.nickname and PAL.red)
|
|
if C.contentTop then
|
|
S._fieldOffset = { id = "mon-nickname", off = cy - C.contentTop, h = row }
|
|
end
|
|
S.nicknameDraft = Kit.textfield("mon-nickname", x, cy, w, row, S.nicknameDraft or "", "None (uses species name)", {
|
|
invalid = issues.fields.nickname ~= nil,
|
|
sanitize = function(v)
|
|
return Ops.nicknameSanitize(S, v)
|
|
end,
|
|
})
|
|
cy = cy + row
|
|
cy = cy + hint(C, "nickname", x, cy, w) + block
|
|
|
|
local function unownForm(help)
|
|
local letter = Ops.unownForm(S, mon)
|
|
if letter == nil then return end
|
|
cy = cy + Form.label(Kit, "Unown form", x, cy, w, nil, issues.fields.form and PAL.red)
|
|
cy = cy + Form.select(S, Kit, "form", "Unown form", letter, Ops.unownFormOptions(S), x, cy, w, function(v)
|
|
return Ops.setUnownForm(S, mon, v)
|
|
end, issues.fields.form, help)
|
|
cy = cy + hint(C, "form", x, cy, w) + block
|
|
end
|
|
|
|
if g == 3 then
|
|
local Pokemon = require("src.core.game3.pokemon")
|
|
local nature = natureOf(mon)
|
|
local natures = {}
|
|
for id = 0, 24 do
|
|
natures[#natures + 1] = { id, natureName(id) }
|
|
end
|
|
local slot = mon.abilityNum or (tonumber(mon.personality) or 0) % 2
|
|
local abilities = {}
|
|
for i, id in ipairs(Pokemon.abilities(mon.species)) do
|
|
if id and id ~= 0 then
|
|
abilities[#abilities + 1] = { i - 1, titleCase(Pokemon.abilityName(id)) }
|
|
end
|
|
end
|
|
local half = w >= 340 * s
|
|
local hw = half and (w - 16 * s) / 2 or w
|
|
local top = cy
|
|
cy = cy + Form.label(Kit, "Nature", x, cy, hw, nil, issues.fields.nature and PAL.red)
|
|
cy = cy + Form.select(S, Kit, "nature", "Nature", nature, natures, x, cy, hw, function(v)
|
|
return Ops.setNature(S, mon, v)
|
|
end, issues.fields.nature)
|
|
cy = cy + 8 * s
|
|
Kit.text("small", natureEffect(nature), x, cy, PAL.muted)
|
|
cy = cy + Kit.textHeight("small")
|
|
cy = cy + hint(C, "nature", x, cy, hw)
|
|
local ax, ay = x, cy + block
|
|
if half then ax, ay = x + hw + 16 * s, top end
|
|
ay = ay + Form.label(Kit, "Ability", ax, ay, hw, nil, issues.fields.ability and PAL.red)
|
|
ay = ay + Form.select(S, Kit, "ability", "Ability", slot, abilities, ax, ay, hw, function(v)
|
|
return Ops.setAbility(S, mon, v)
|
|
end, issues.fields.ability)
|
|
ay = ay + hint(C, "ability", ax, ay, hw)
|
|
cy = math.max(cy, ay) + block
|
|
|
|
local gender = genderOf(C)
|
|
local ratio = (Pokemon.speciesMeta(mon.species) or {}).genderRatio or 255
|
|
local genders = ratio == 255 and { { "U", "Genderless" } }
|
|
or ratio == 0 and { { "M", "Male" } }
|
|
or ratio == 254 and { { "F", "Female" } }
|
|
or { { "M", "Male" }, { "F", "Female" } }
|
|
cy = cy + Form.label(Kit, "Gender", x, cy, w, nil, issues.fields.gender and PAL.red)
|
|
if #genders == 1 then
|
|
Form.box(Kit, x, cy, w, row, genders[1][2], issues.fields.gender ~= nil)
|
|
else
|
|
Form.segmented(Kit, "gender", genders, gender, x, cy, w, row, function(v)
|
|
return Ops.setMonGender(S, mon, v)
|
|
end, issues.fields.gender)
|
|
end
|
|
cy = cy + row
|
|
cy = cy + hint(C, "gender", x, cy, w) + block
|
|
end
|
|
|
|
if g >= 2 then
|
|
local heldId = mon.heldItem or mon.item
|
|
local held = S.data.items and S.data.items[heldId]
|
|
local hasItem = heldId ~= nil and heldId ~= 0
|
|
local heldName = held and held.name or (hasItem and tostring(heldId)) or "None"
|
|
cy = cy + Form.label(Kit, "Held item", x, cy, w, nil, issues.fields.heldItem and PAL.red)
|
|
local itemOpts = { font = "button", icon = "pencil", id = "change-item" }
|
|
local itemW = Kit.buttonWidth("Change", itemOpts, row)
|
|
Form.box(Kit, x, cy, w - itemW - row - 2 * gap, row, heldName, issues.fields.heldItem ~= nil,
|
|
not hasItem and PAL.muted or nil)
|
|
if Kit.button(x + w - row - gap - itemW, cy, itemW, row, "Change", itemOpts) then
|
|
Ops.openItemPicker(S, Kit, "held")
|
|
end
|
|
if Kit.iconButton(x + w - row, cy, row, row, "x", "Clear held item", { kind = "danger", enabled = hasItem }) then
|
|
Ops.setHeldItem(S, mon, nil)
|
|
end
|
|
cy = cy + row
|
|
cy = cy + hint(C, "heldItem", x, cy, w) + block
|
|
end
|
|
|
|
if g == 3 then
|
|
unownForm("Keeps nature and shininess. Changes the personality value.")
|
|
elseif g == 2 then
|
|
local d = P.find(S, "pokerus")
|
|
cy = cy + Form.label(Kit, "Pokérus", x, cy, w, nil, issues.fields.pokerus and PAL.red)
|
|
cy = cy + Form.select(S, Kit, "pokerus", "Pokérus", mon.pokerus or 0, Named.property(S, d), x, cy, w, function(v)
|
|
return Ops.setPokerus(S, mon, v)
|
|
end, issues.fields.pokerus)
|
|
cy = cy + hint(C, "pokerus", x, cy, w) + block
|
|
local isUnown = Ops.unownForm(S, mon) ~= nil
|
|
unownForm("Rewrites the middle bits of the DVs. Only I and V can be shiny.")
|
|
cy = cy + Kit.textWrapped("small", isUnown and "Gender and shininess follow DVs. Picking a form rewrites the middle DV bits."
|
|
or "Gender and shininess follow DVs.", x, cy, w, PAL.muted)
|
|
cy = cy + hint(C, "gender", x, cy, w)
|
|
cy = cy + hint(C, "shiny", x, cy, w)
|
|
if not isUnown then cy = cy + hint(C, "form", x, cy, w) end
|
|
cy = cy + block
|
|
end
|
|
return cy - block
|
|
end
|
|
|
|
local function conditionColumn(C, x, y, w)
|
|
local S, Kit, mon, g, s, issues = C.S, C.Kit, C.mon, C.g, C.s, C.issues
|
|
local block = 22 * s
|
|
local cy = y + Kit.caption(x, y, "LEVEL & CONDITION") + 16 * s
|
|
local function num(id, title, value, apply)
|
|
cy = cy + Form.numberRow(S, Kit, id, title, value, Limits.mon(S, mon, id), x, cy, w, apply, issues.fields[id]) + block
|
|
end
|
|
num("level", "Level", mon.level, function(n)
|
|
if not Legality.integer(n, 1, 100) then
|
|
return Ops.say(S, "Level must be a whole number from 1 to 100")
|
|
end
|
|
return Ops.setLevel(S, mon, n)
|
|
end)
|
|
num("experience", "Experience", Gen.exp(mon), function(n)
|
|
return Ops.setExperience(S, mon, n)
|
|
end)
|
|
num("current-hp", "Current HP", mon.hp or 0, function(n)
|
|
return Ops.setCurrentHp(S, mon, n)
|
|
end)
|
|
if g >= 2 then
|
|
num("friendship", "Friendship", mon.friendship or mon.happiness or 0, function(n)
|
|
if not Legality.integer(n, 0, 255) then
|
|
return Ops.say(S, "Friendship must be 0-255")
|
|
end
|
|
return Ops.setHappiness(S, mon, n)
|
|
end)
|
|
end
|
|
local statuses = {}
|
|
for i, st in ipairs(STATUSES) do statuses[i] = st end
|
|
if g >= 2 then statuses[#statuses + 1] = { "TOX", "Badly poisoned" } end
|
|
cy = cy + Form.label(Kit, "Status", x, cy, w, nil, issues.fields.status and PAL.red)
|
|
cy = cy + Form.select(S, Kit, "status", "Status", savedStatus(mon), statuses, x, cy, w, function(v)
|
|
return Ops.setMonStatus(S, mon, v ~= "healthy" and v or nil)
|
|
end, issues.fields.status)
|
|
cy = cy + hint(C, "status", x, cy, w)
|
|
return cy
|
|
end
|
|
|
|
local function mainBody(C, x, y, w)
|
|
local s = C.s
|
|
local two = w >= 600 * s
|
|
local colGap = 32 * s
|
|
local cw = two and (w - colGap) / 2 or w
|
|
local left = identityColumn(C, x, y, cw)
|
|
local right = conditionColumn(C, two and x + cw + colGap or x, two and y or left + 30 * s, cw)
|
|
return math.max(left, right) - y
|
|
end
|
|
|
|
local function statsBody(C, x, y, w)
|
|
local S, Kit, mon, g, s, issues = C.S, C.Kit, C.mon, C.g, C.s, C.issues
|
|
local block = 22 * s
|
|
local cy = y
|
|
local keys = g == 3 and { "hp", "atk", "def", "spa", "spd", "spe" }
|
|
or { "hp", "attack", "defense", "speed", "special" }
|
|
local stats = mon.stats or {}
|
|
local calcColor = issues.fields.calculated and PAL.red or PAL.heading
|
|
cy = cy + Kit.caption(x, cy, "CALCULATED") + 12 * s
|
|
local line = ("HP %s · Atk %s · Def %s · Spe %s"):format(
|
|
tostring(stats.hp or mon.maxHp or "?"),
|
|
tostring(stats.attack or mon.attack or "?"),
|
|
tostring(stats.defense or mon.defense or "?"),
|
|
tostring(stats.speed or mon.speed or "?"))
|
|
line = line .. " · " .. (g == 1 and ("Special " .. tostring(stats.special or "?"))
|
|
or ("SpA " .. tostring(stats.specialAttack or stats.spAtk or mon.spAtk or "?")
|
|
.. " · SpD " .. tostring(stats.specialDefense or stats.spDef or mon.spDef or "?")))
|
|
cy = cy + Kit.textWrapped("button", line, x, cy, w, calcColor)
|
|
cy = cy + hint(C, "calculated", x, cy, w) + 8 * s
|
|
cy = cy + Kit.textWrapped("small", g == 3 and "IVs 0-31. EVs 0-255 with a total limit of 510."
|
|
or "DVs 0-15. HP DV follows the other DVs. Stat experience 0-65535.", x, cy, w, PAL.muted)
|
|
if g == 3 then
|
|
local total = Limits.evTotal(mon)
|
|
cy = cy + 6 * s + Kit.textWrapped("small", "EVs: " .. total .. " / 510 · " .. math.max(0, 510 - total) .. " free",
|
|
x, cy + 6 * s, w, total > 510 and PAL.red or PAL.green)
|
|
else
|
|
cy = cy + 6 * s + Kit.textWrapped("small", "HP DV: " .. tostring(mon.dvs and mon.dvs.hp or 0) .. " / 15 · follows the other DVs",
|
|
x, cy + 6 * s, w, issues.fields["dv-hp"] and PAL.red or PAL.muted)
|
|
cy = cy + hint(C, "dv-hp", x, cy, w)
|
|
end
|
|
cy = cy + block
|
|
local two = w >= 560 * s
|
|
local colGap = 32 * s
|
|
local cw = two and (w - colGap) / 2 or w
|
|
local function num(id, title, value, apply, nx, ny)
|
|
return Form.numberRow(S, Kit, id, title, value, Limits.mon(S, mon, id), nx, ny, cw, apply, issues.fields[id])
|
|
end
|
|
for _, k in ipairs(keys) do
|
|
local left, right
|
|
if g == 3 then
|
|
left = { "iv-" .. k, STAT_NAMES[k] .. " IV", mon.ivs and mon.ivs[k] or 0, function(n)
|
|
return Ops.setIv(S, mon, k, n)
|
|
end }
|
|
right = { "ev-" .. k, STAT_NAMES[k] .. " EV", mon.evs and mon.evs[k] or 0, function(n)
|
|
return Ops.setEv(S, mon, k, n)
|
|
end }
|
|
else
|
|
if k ~= "hp" then
|
|
left = { "dv-" .. k, STAT_NAMES[k] .. " DV", mon.dvs and mon.dvs[k] or 0, function(n)
|
|
return Ops.setDv(S, mon, k, n)
|
|
end }
|
|
end
|
|
right = { "se-" .. k, STAT_NAMES[k] .. " stat experience", mon.statExp and mon.statExp[k] or 0, function(n)
|
|
return Ops.setStatExp(S, mon, k, n)
|
|
end }
|
|
end
|
|
if left and two then
|
|
local lh = num(left[1], left[2], left[3], left[4], x, cy)
|
|
local rh = num(right[1], right[2], right[3], right[4], x + cw + colGap, cy)
|
|
cy = cy + math.max(lh, rh) + block
|
|
else
|
|
if left then cy = cy + num(left[1], left[2], left[3], left[4], x, cy) + block end
|
|
cy = cy + num(right[1], right[2], right[3], right[4], x, cy) + block
|
|
end
|
|
end
|
|
return cy - block - y
|
|
end
|
|
|
|
local function moveInfo(C, slot)
|
|
local S, mon = C.S, C.mon
|
|
local mv = mon.moves and mon.moves[slot]
|
|
local id = type(mv) == "table" and (mv.moveId or mv.id) or mv
|
|
local md = id and S.data.moves and S.data.moves[id]
|
|
local info = { slot = slot, id = id, md = md, empty = id == nil or id == 0 }
|
|
if not info.empty then
|
|
info.pp = tonumber(type(mv) == "table" and mv.pp or mon.pp and mon.pp[slot]) or 0
|
|
info.ups = MonOps.getPpUps(mon, slot)
|
|
info.base = MonOps.getBasePp(S.data, mon, slot)
|
|
info.max = MonOps.calcMaxPp(info.base, info.ups, C.g)
|
|
local t = md and typeName(S, md.type)
|
|
info.sub = (t and (t .. " · ") or "") .. "base PP " .. tostring(info.base or "?")
|
|
else
|
|
info.sub = "No move in this slot"
|
|
end
|
|
info.name = md and md.name or (info.empty and "Empty" or tostring(id))
|
|
return info
|
|
end
|
|
|
|
local function moveCardLayout(C, info, w)
|
|
local Kit, s, row = C.Kit, C.s, C.row
|
|
local pad, gap = 16 * s, 10 * s
|
|
local iw = w - 2 * pad
|
|
local badge = math.floor(30 * s)
|
|
local label = info.empty and "Choose" or "Change"
|
|
local btnOpts = { font = "button", icon = info.empty and "plus" or "pencil", id = "move-" .. info.slot }
|
|
local btnW = Kit.buttonWidth(label, btnOpts, row)
|
|
local xW = info.empty and 0 or row + gap
|
|
local nameW = iw - badge - 12 * s - btnW - xW - gap
|
|
if nameW < 80 * s then
|
|
btnOpts.iconOnly, btnW = true, row
|
|
nameW = iw - badge - 12 * s - btnW - xW - gap
|
|
end
|
|
local subH = Form.wrapH(Kit, "small", info.sub, nameW)
|
|
local headH = math.max(row, Kit.textHeight("button") + 2 * s + subH)
|
|
local L = { pad = pad, gap = gap, iw = iw, badge = badge, label = label, btnOpts = btnOpts, btnW = btnW,
|
|
xW = xW, nameW = nameW, headH = headH }
|
|
local h = pad + headH
|
|
local issues = C.issues.fields
|
|
h = h + Form.issueH(Kit, issues["move" .. info.slot], iw) + (issues["move" .. info.slot] and 6 * s or 0)
|
|
if not info.empty then
|
|
local upsLabelW = Kit.textWidth("small", "PP Ups") + 16 * s
|
|
local maxUps = Limits.mon(C.S, C.mon, "ppup-" .. info.slot).hi
|
|
L.maxUps = maxUps
|
|
L.segW = math.max(Kit.tapMin() * (maxUps + 1), math.min(iw - upsLabelW, 56 * s * (maxUps + 1)))
|
|
L.upsBelow = iw - upsLabelW < L.segW
|
|
h = h + 16 * s + Form.labelH(C.Kit) + row
|
|
h = h + Form.issueH(Kit, issues["pp-" .. info.slot], iw) + (issues["pp-" .. info.slot] and 6 * s or 0)
|
|
h = h + 12 * s + (L.upsBelow and Form.labelH(C.Kit) + row or row)
|
|
h = h + Form.issueH(Kit, issues["ppup-" .. info.slot], iw) + (issues["ppup-" .. info.slot] and 6 * s or 0)
|
|
end
|
|
L.h = h + pad
|
|
return L
|
|
end
|
|
|
|
local function drawMoveCard(C, info, L, x, y, w, h)
|
|
local S, Kit, mon, s, row, issues = C.S, C.Kit, C.mon, C.s, C.row, C.issues.fields
|
|
local slot = info.slot
|
|
local invalid = issues["move" .. slot] or issues["pp-" .. slot] or issues["ppup-" .. slot]
|
|
Theme.fillRounded(x, y, w, h, PAL.rowBg, 0.55, Theme.radius())
|
|
Theme.stroke(x, y, w, h, Theme.radius(), invalid and PAL.red or PAL.cardBorder, invalid and 0.9 or 0.3,
|
|
invalid and 2 * s or 1)
|
|
local ix, cy = x + L.pad, y + L.pad
|
|
local by = cy + (math.min(L.headH, row) - L.badge) / 2
|
|
Theme.strokeRounded(ix, by, L.badge, L.badge, PAL.line, 0.5, 1, 7 * s)
|
|
Kit.textCenter("small", tostring(slot), ix, by + (L.badge - Kit.textHeight("small")) / 2, L.badge, PAL.text)
|
|
local nx = ix + L.badge + 12 * s
|
|
local nameColor = info.empty and PAL.muted or (issues["move" .. slot] and PAL.red or PAL.heading)
|
|
local nameY = cy + math.max(0, (L.headH - Kit.textHeight("button") - 2 * s - Form.wrapH(Kit, "small", info.sub, L.nameW)) / 2)
|
|
Kit.textBold("button", Kit.ellipsize("button", info.name, L.nameW), nx, nameY, nameColor)
|
|
Kit.textWrapped("small", info.sub, nx, nameY + Kit.textHeight("button") + 2 * s, L.nameW, PAL.muted)
|
|
local bx = ix + L.iw - L.xW - L.btnW
|
|
local btnY = cy + (math.min(L.headH, row) - row) / 2
|
|
if Kit.button(bx, btnY, L.btnW, row, L.label, L.btnOpts) then
|
|
Ops.openMovePicker(S, Kit, slot)
|
|
end
|
|
if not info.empty and Kit.iconButton(ix + L.iw - row, btnY, row, row, "x", "Clear slot " .. slot, { kind = "danger" }) then
|
|
Ops.clearMove(S, mon, slot)
|
|
end
|
|
cy = cy + L.headH
|
|
cy = cy + hint(C, "move" .. slot, ix, cy, L.iw)
|
|
if info.empty then return end
|
|
cy = cy + 16 * s
|
|
local ppIssue = issues["pp-" .. slot]
|
|
Form.label(Kit, "PP", ix, cy, L.iw, info.pp .. " / " .. info.max, ppIssue and PAL.red, ppIssue and PAL.red or PAL.text)
|
|
cy = cy + Form.labelH(Kit)
|
|
Form.slider(S, Kit, "pp-" .. slot, info.pp, Limits.mon(S, mon, "pp-" .. slot), ix, cy, L.iw, row, function(n)
|
|
return Ops.setPp(S, mon, slot, n)
|
|
end, ppIssue)
|
|
cy = cy + row
|
|
cy = cy + hint(C, "pp-" .. slot, ix, cy, L.iw) + 12 * s
|
|
local upsIssue = issues["ppup-" .. slot]
|
|
local options = {}
|
|
for i = 0, L.maxUps do options[#options + 1] = { i, tostring(i) } end
|
|
local function apply(v)
|
|
return Ops.setPpUps(S, mon, slot, v)
|
|
end
|
|
if L.upsBelow then
|
|
cy = cy + Form.label(Kit, "PP Ups", ix, cy, L.iw, nil, upsIssue and PAL.red)
|
|
Form.segmented(Kit, "ppup-" .. slot, options, info.ups, ix, cy, L.iw, row, apply, upsIssue)
|
|
else
|
|
Kit.text("small", "PP Ups", ix, cy + (row - Kit.textHeight("small")) / 2, upsIssue and PAL.red or PAL.muted)
|
|
Form.segmented(Kit, "ppup-" .. slot, options, info.ups, ix + L.iw - L.segW, cy, L.segW, row, apply, upsIssue)
|
|
end
|
|
cy = cy + row
|
|
hint(C, "ppup-" .. slot, ix, cy, L.iw)
|
|
end
|
|
|
|
local function movesBody(C, x, y, w)
|
|
local s = C.s
|
|
local gap = 16 * s
|
|
local cols = w >= 3 * 300 * s + 2 * gap and 3 or w >= 2 * 280 * s + gap and 2 or 1
|
|
local cw = (w - (cols - 1) * gap) / cols
|
|
local cy = y
|
|
for first = 1, 4, cols do
|
|
local infos, layouts, rowH = {}, {}, 0
|
|
for slot = first, math.min(4, first + cols - 1) do
|
|
infos[#infos + 1] = moveInfo(C, slot)
|
|
layouts[#layouts + 1] = moveCardLayout(C, infos[#infos], cw)
|
|
rowH = math.max(rowH, layouts[#layouts].h)
|
|
end
|
|
for i, info in ipairs(infos) do
|
|
drawMoveCard(C, info, layouts[i], x + (i - 1) * (cw + gap), cy, cw, info.empty and layouts[i].h or rowH)
|
|
end
|
|
cy = cy + rowH + gap
|
|
end
|
|
return cy - gap - y
|
|
end
|
|
|
|
local function listBody(C, x, y, w, section)
|
|
local S, Kit, mon, g, s, issues, report = C.S, C.Kit, C.mon, C.g, C.s, C.issues, C.report
|
|
local gap, block = 10 * s, 22 * s
|
|
local cy, tail = y, 0
|
|
local function space(n)
|
|
cy, tail = cy + n, n
|
|
end
|
|
local function text(str, color)
|
|
cy = cy + Kit.textWrapped("small", str, x, cy, w, color or PAL.muted)
|
|
space(gap)
|
|
end
|
|
local function item(d, ix, iy, iw)
|
|
local issue = issues.fields[d.key]
|
|
if d.toggle then
|
|
local on = P.get(mon, d)
|
|
on = on == true or on == 1
|
|
local shown = issue and tostring(P.get(mon, d)) or (on and "ON" or "OFF")
|
|
local label = d.label .. ": " .. shown
|
|
local opts = { face = "selection", active = on, font = "small", invalid = issue ~= nil }
|
|
local height = Kit.buttonHeight(label, iw, opts)
|
|
if Kit.button(ix, iy, iw, height, label, opts) then
|
|
Ops.setMonProperty(S, mon, d.key, not on)
|
|
end
|
|
return height + hint(C, d.key, ix, iy + height, iw), gap
|
|
elseif Named.property(S, d) then
|
|
local h = Form.label(Kit, d.label, ix, iy, iw, nil, issue and PAL.red)
|
|
h = h + Form.select(S, Kit, d.key, d.label, P.get(mon, d), Named.property(S, d), ix, iy + h, iw, function(v)
|
|
return Ops.setMonProperty(S, mon, d.key, v)
|
|
end, issue)
|
|
return h + hint(C, d.key, ix, iy + h, iw), block
|
|
end
|
|
local value = P.get(mon, d)
|
|
if type(value) == "number" then
|
|
local label = d.label:gsub(" %(.-%)", "")
|
|
return Form.numberRow(S, Kit, d.key, label, value, Limits.mon(S, mon, d.key), ix, iy, iw, function(n)
|
|
return Ops.setMonProperty(S, mon, d.key, n)
|
|
end, issue), block
|
|
end
|
|
local h = Form.label(Kit, d.label, ix, iy, iw, nil, issue and PAL.red)
|
|
Form.box(Kit, ix, iy + h, iw, C.row, tostring(value or ""), issue ~= nil)
|
|
return h + C.row + hint(C, d.key, ix, iy + h + C.row, iw), block
|
|
end
|
|
local function props(list)
|
|
local cols = w >= 600 * s and 2 or 1
|
|
local colGap = 32 * s
|
|
local cw = (w - (cols - 1) * colGap) / cols
|
|
local col, rowH, rowSpace = 0, 0, 0
|
|
for _, d in ipairs(list) do
|
|
local h, after = item(d, x + col * (cw + colGap), cy, cw)
|
|
rowH, rowSpace = math.max(rowH, h), math.max(rowSpace, after)
|
|
col = col + 1
|
|
if col == cols then
|
|
cy = cy + rowH
|
|
space(rowSpace)
|
|
col, rowH, rowSpace = 0, 0, 0
|
|
end
|
|
end
|
|
if col > 0 then
|
|
cy = cy + rowH
|
|
space(rowSpace)
|
|
end
|
|
end
|
|
if section == "origin" then
|
|
props(P.identity(S))
|
|
if g == 2 and Gen.hasCaughtData(S.save, S.version) then
|
|
cy = cy + Form.label(Kit, "Caught by", x, cy, w)
|
|
Form.segmented(Kit, "caught-by", { { "boy", "Boy" }, { "girl", "Girl" } }, mon.caughtByGender, x, cy, w, C.row,
|
|
function(v)
|
|
return Ops.setCaughtByGender(S, mon, v)
|
|
end)
|
|
cy = cy + C.row
|
|
space(block)
|
|
end
|
|
elseif section == "extras" then
|
|
if g == 3 then
|
|
text("Contest conditions", issues.fields.contest and PAL.red or PAL.heading)
|
|
cy = cy + hint(C, "contest", x, cy - gap, w)
|
|
props(P.contest)
|
|
text("Ribbons: setting a ribbon does not establish award or event eligibility.", issues.fields.ribbons and PAL.red or PAL.heading)
|
|
cy = cy + hint(C, "ribbons", x, cy - gap, w)
|
|
props(P.ribbons)
|
|
else
|
|
text("This generation has no contest conditions or ribbons.")
|
|
end
|
|
else
|
|
text(report.errors > 0 and (report.errors .. " property errors") or "Values pass. Origin still needs checking.",
|
|
report.errors > 0 and PAL.red or PAL.yellow)
|
|
for _, check in ipairs(report.checks) do
|
|
text(check.kind:upper() .. ": " .. check.message,
|
|
check.kind == "error" and PAL.red or check.kind == "pass" and PAL.green or PAL.yellow)
|
|
end
|
|
end
|
|
return cy - tail - y
|
|
end
|
|
|
|
local function sectionBody(C, x, y, w)
|
|
local section = C.S.monSection
|
|
local cy = y
|
|
if C.report.errors > 0 and section ~= "checks" then
|
|
cy = cy + C.Kit.textWrapped("small", C.report.errors .. " saved value errors. Check the red fields.", x, cy, w, PAL.red)
|
|
+ 16 * C.s
|
|
end
|
|
if section == "main" then
|
|
return cy - y + mainBody(C, x, cy, w)
|
|
elseif section == "stats" then
|
|
return cy - y + statsBody(C, x, cy, w)
|
|
elseif section == "moves" then
|
|
return cy - y + movesBody(C, x, cy, w)
|
|
end
|
|
return cy - y + listBody(C, x, cy, w, section)
|
|
end
|
|
|
|
local function sectionTools(C)
|
|
local S, mon, g = C.S, C.mon, C.g
|
|
local section = S.monSection
|
|
if section == "main" then
|
|
return "TOOLS", {
|
|
{ "Max out", function() Ops.maxMon(S, mon) end, { icon = "chevrons-up", iconInk = PAL.green } },
|
|
{ "Fix errors", function() Ops.fixMonErrors(S, mon) end, { icon = "shield-check", iconInk = PAL.green } },
|
|
{ "Randomize", function() Ops.randomizeMon(S, mon) end, { icon = "shuffle", iconInk = PAL.blue } },
|
|
{ "Clone to a box", function() Ops.cloneMonToBox(S, mon) end, { icon = "copy" } },
|
|
}
|
|
elseif section == "stats" then
|
|
if g == 3 then
|
|
return "STAT TOOLS", {
|
|
{ "Max all IVs", function() Ops.maxIvs(S, mon) end, { icon = "chevrons-up", iconInk = PAL.green } },
|
|
{ "Clear EVs", function() Ops.clearEvs(S, mon) end, { icon = "x", iconInk = PAL.red } },
|
|
}
|
|
end
|
|
return "STAT TOOLS", {
|
|
{ "Max all DVs", function() Ops.maxDvs(S, mon) end, { icon = "chevrons-up", iconInk = PAL.green } },
|
|
{ "Max stat training", function() Ops.maxStatExp(S, mon) end, { icon = "chevrons-up", iconInk = PAL.green } },
|
|
}
|
|
elseif section == "moves" then
|
|
local recommend = Ops.recommendState(S, mon)
|
|
return "MOVE TOOLS", {
|
|
{ "Max all PP", function() Ops.maxAllPpUps(S, mon) end, { icon = "chevrons-up", iconInk = PAL.green } },
|
|
{ ({ pending = "Fetching recommended moveset...", none = "No recommended moveset" })[recommend]
|
|
or "Recommended moveset", function() Ops.recommendMoves(S, mon) end,
|
|
{ icon = "sparkles", iconInk = PAL.blue, enabled = recommend == "ready" } },
|
|
{ "Reset to learnset", function() Ops.resetMoves(S, mon) end, { icon = "rotate-ccw" } },
|
|
}
|
|
elseif section == "checks" then
|
|
return "TOOLS", {
|
|
{ "Fix errors", function() Ops.fixMonErrors(S, mon) end, { icon = "shield-check", iconInk = PAL.green } },
|
|
}
|
|
end
|
|
end
|
|
|
|
local function resetForm(S, Kit, mon)
|
|
if S.formMon ~= mon then
|
|
S.formMon, S.monDrafts, S.inspectorScroll = mon, {}, 0
|
|
S.propertyChoice, S.valueEdit, S._slider = nil, nil, nil
|
|
if Kit.focus and (Kit.focus:match("^property%-") or Kit.focus:match("^value%-edit")) then
|
|
Kit.blur()
|
|
end
|
|
end
|
|
end
|
|
|
|
local function scrollRegion(S, Kit, x, y, w, h)
|
|
if S._slider and S._slider.live then Kit._dragDelta = 0 end
|
|
local key = tostring(Kit._fontKey) .. ":" .. w .. ":" .. h
|
|
if S._formLayoutKey ~= key then
|
|
local scroll = S.inspectorScroll or 0
|
|
local oldMax = math.max(0, (S._formHeight or 0) - (S._formViewHeight or h))
|
|
S._stickBottom = scroll > 0 and scroll >= oldMax - 1
|
|
S._formLayoutKey = key
|
|
end
|
|
if S._stickBottom then return end
|
|
S.inspectorScroll = Kit.scrollPixels(x, y, w, h, S.inspectorScroll or 0, S._formHeight or 0)
|
|
end
|
|
|
|
local function revealFocus(S, Kit, viewH)
|
|
local fo = S._fieldOffset
|
|
if not Kit.focus then
|
|
S._revealedFocus = nil
|
|
return
|
|
end
|
|
if S._revealedFocus == Kit.focus or not fo or fo.id ~= Kit.focus then return end
|
|
S._revealedFocus = Kit.focus
|
|
local scroll = S.inspectorScroll or 0
|
|
if fo.off < scroll or fo.off + fo.h > scroll + viewH then
|
|
S.inspectorScroll = Ops.clamp(fo.off - 12 * Kit.scale, 0, math.max(0, (S._formHeight or 0) - viewH))
|
|
end
|
|
end
|
|
|
|
local function settleScroll(S, contentH, viewH)
|
|
S._formHeight, S._formViewHeight = contentH, viewH
|
|
local max = math.max(0, contentH - viewH)
|
|
S.inspectorScroll = S._stickBottom and max or Ops.clamp(S.inspectorScroll or 0, 0, max)
|
|
S._stickBottom = nil
|
|
end
|
|
|
|
local function drawFixed(C, x, y, w, h)
|
|
local S, Kit, s, row = C.S, C.Kit, C.s, C.row
|
|
local pad = 20 * s
|
|
local cx, inner = x + pad, w - 2 * pad
|
|
local heroH = drawHero(C, cx, y + pad, inner, false)
|
|
local navY = y + pad + heroH + 10 * s
|
|
drawNav(C, cx - 4 * s, navY, inner + 4 * s, false)
|
|
Theme.col(PAL.cardBorder, 0.25)
|
|
love.graphics.rectangle("fill", x, navY + row, w, 1)
|
|
local caption, tools = sectionTools(C)
|
|
local footerH = tools and Form.flowH(Kit, caption, tools, inner) + 2 * 14 * s or 0
|
|
local bodyY = navY + row + 1
|
|
local bodyH = math.max(0, y + h - footerH - bodyY)
|
|
Motion.pages(S, Kit, "monSection", x, bodyY, w, bodyH, function(st, kit, px, py, pw, ph)
|
|
local top = py + 20 * s
|
|
local viewH = math.max(0, ph - 20 * s)
|
|
scrollRegion(st, kit, px + pad, top, pw - 2 * pad, viewH)
|
|
revealFocus(st, kit, viewH)
|
|
kit.pushClip(px + pad, top, pw - 2 * pad, viewH)
|
|
C.contentTop = top - (st.inspectorScroll or 0)
|
|
local contentH = sectionBody(C, px + pad, C.contentTop, pw - 2 * pad) + 20 * s
|
|
kit.popClip()
|
|
settleScroll(st, contentH, viewH)
|
|
kit.scrollbar(px + pad, top, pw - 2 * pad, viewH, st.inspectorScroll, contentH, viewH)
|
|
end)
|
|
if tools then
|
|
local fy = y + h - footerH
|
|
Theme.col(PAL.cardBorder, 0.25)
|
|
love.graphics.rectangle("fill", x, fy, w, 1)
|
|
Form.flow(Kit, caption, tools, cx, fy + 14 * s, inner)
|
|
end
|
|
end
|
|
|
|
local function drawStacked(C, x, y, w, h, prelude)
|
|
local S, Kit, s, row = C.S, C.Kit, C.s, C.row
|
|
local pad = (prelude and 18 or 16) * s
|
|
local edge = prelude and 0 or 12 * s
|
|
if not prelude then Kit.card(x, y, w, h) end
|
|
local vx, vy, vw, vh = x + edge, y + edge, w - 2 * edge, h - 2 * edge
|
|
scrollRegion(S, Kit, vx, vy, vw, vh)
|
|
revealFocus(S, Kit, vh)
|
|
Kit.pushClip(vx, vy, vw, vh)
|
|
local top = vy - (S.inspectorScroll or 0)
|
|
C.contentTop = top
|
|
local cy = top
|
|
local cw = prelude and vw - 12 * s or vw
|
|
if prelude then
|
|
cy = cy + prelude(vx, cy, cw) + 16 * s
|
|
end
|
|
local cardTop = cy
|
|
if prelude then Kit.card(vx, cardTop, cw, S._stackCardH or vh) end
|
|
local cx, inner = vx + pad, cw - 2 * pad
|
|
if C.mon then
|
|
cy = cy + pad
|
|
cy = cy + drawHero(C, cx, cy, inner, inner < 560 * s) + 14 * s
|
|
S._navOffset = cy - top
|
|
S._navKeep = math.min(S.inspectorScroll or 0, S._navOffset)
|
|
drawNav(C, cx - 4 * s, cy, inner + 4 * s, true)
|
|
cy = cy + row
|
|
Theme.col(PAL.cardBorder, 0.25)
|
|
love.graphics.rectangle("fill", vx, cy, cw, 1)
|
|
cy = cy + 20 * s
|
|
cy = cy + sectionBody(C, cx, cy, inner) + 20 * s
|
|
local caption, tools = sectionTools(C)
|
|
if tools then
|
|
Theme.col(PAL.cardBorder, 0.25)
|
|
love.graphics.rectangle("fill", vx, cy, cw, 1)
|
|
cy = cy + 14 * s
|
|
cy = cy + Form.flow(Kit, caption, tools, cx, cy, inner) + 14 * s
|
|
end
|
|
else
|
|
cy = cy + pad
|
|
cy = cy + Kit.textWrapped("button", "Select a Pokemon to edit its properties, stats, moves and origin.",
|
|
cx, cy, inner, PAL.muted) + pad
|
|
end
|
|
S._stackCardH = cy - cardTop
|
|
Kit.popClip()
|
|
local contentH = cy - top
|
|
settleScroll(S, contentH, vh)
|
|
Kit.scrollbar(vx + vw - 7 * s, vy, 0, vh, S.inspectorScroll, contentH, vh)
|
|
end
|
|
|
|
function Body.stacked(Kit, w, h)
|
|
return w < 600 * Kit.scale or h < 520 * Kit.scale
|
|
end
|
|
|
|
function Body.draw(S, Kit, x, y, w, h, prelude)
|
|
local mon = S.editingMon
|
|
local s = Kit.scale
|
|
local C = { S = S, Kit = Kit, mon = mon, g = Gen.ofState(S), s = s, row = Kit.controlH() }
|
|
if mon then
|
|
C.report = Legality.mon(S, mon)
|
|
C.issues = Legality.highlights(C.report, mon)
|
|
C.def = (S.data and S.data.pokemon and S.data.pokemon[mon.species or mon.speciesId]) or nil
|
|
end
|
|
resetForm(S, Kit, mon)
|
|
S.monSection = S.monSection or "main"
|
|
if prelude or Body.stacked(Kit, w, h) or not mon then
|
|
return drawStacked(C, x, y, w, h, prelude)
|
|
end
|
|
Kit.card(x, y, w, h)
|
|
drawFixed(C, x, y, w, h)
|
|
end
|
|
|
|
return Body
|