Files
gen1recomp/tools/save-editor/Properties.lua
T
bryanthaboi 20ab97abf8 save editor
2026-10-01 21:31:40 -04:00

235 lines
6.1 KiB
Lua

-- One property schema for forms, writes and validation. Storage ranges alone
-- do not prove an encounter or event distribution was obtainable.
local Gen = require("Gen")
local P = {}
local function field(key, label, lo, hi, extra)
local d = extra or {}
d.key, d.label, d.lo, d.hi = key, label, lo, hi
return d
end
local COMMON = {
field("otName", "Original trainer", nil, nil, { text = true, max = 7, alias = "ot" }),
field("otId", "Trainer ID", 0, 65535),
}
local G3 = {
field("otSecretId", "Secret ID", 0, 65535),
field("personality", "Personality (PID)", 0, 4294967295),
field("otGender", "OT gender", 0, 1, { choices = { { 0, "Male" }, { 1, "Female" } } }),
field(
"language",
"Language",
1,
7,
{
values = { 1, 2, 3, 4, 5, 7 },
default = 2,
choices = {
{ 1, "Japanese" },
{ 2, "English" },
{ 3, "French" },
{ 4, "Italian" },
{ 5, "German" },
{ 7, "Spanish" },
},
}
),
field("metLevel", "Met level (0 hatched / 1-100 caught)", 0, 100),
field("metLocation", "Met location ID", 0, 255),
field(
"metGame",
"Origin game",
0,
15,
{
values = { 0, 1, 2, 3, 4, 5, 15 },
choices = {
{ 0, "Unknown" },
{ 1, "Sapphire" },
{ 2, "Ruby" },
{ 3, "Emerald" },
{ 4, "FireRed" },
{ 5, "LeafGreen" },
{ 15, "Colosseum/XD" },
},
}
),
field(
"pokeball",
"Ball",
1,
12,
{
default = 4,
choices = {
{ 1, "Master" },
{ 2, "Ultra" },
{ 3, "Great" },
{ 4, "Poke Ball" },
{ 5, "Safari" },
{ 6, "Net" },
{ 7, "Dive" },
{ 8, "Nest" },
{ 9, "Repeat" },
{ 10, "Timer" },
{ 11, "Luxury" },
{ 12, "Premier" },
},
}
),
field("pokerus", "Pokerus (packed strain / days)", 0, 255),
field("markings", "Markings (bit mask)", 0, 15),
field("isEgg", "Egg", nil, nil, { toggle = true, alias = "egg" }),
field("modernFatefulEncounter", "Fateful encounter", nil, nil, { toggle = true }),
}
P.contest = {}
for _, key in ipairs({ "cool", "beauty", "cute", "smart", "tough", "sheen" }) do
P.contest[#P.contest + 1] =
field("contest." .. key, key:upper(), 0, 255, { parent = "contest", child = key })
end
P.ribbons = {}
for i, key in ipairs({ "Cool", "Beauty", "Cute", "Smart", "Tough" }) do
P.ribbons[#P.ribbons + 1] =
field("ribbon." .. key, key .. " contest rank (0-4)", 0, 4, { shift = (i - 1) * 3, width = 3 })
end
for i, key in ipairs({
"Champion",
"Winning",
"Victory",
"Artist",
"Effort",
"Marine",
"Land",
"Sky",
"Country",
"National",
"Earth",
"World",
}) do
P.ribbons[#P.ribbons + 1] =
field("ribbon." .. key, key .. " ribbon", 0, 1, { shift = 14 + i, width = 1, toggle = true })
end
function P.identity(S)
local out = {}
for _, d in ipairs(COMMON) do
out[#out + 1] = d
end
if Gen.ofState(S) == 3 then
for _, d in ipairs(G3) do
out[#out + 1] = d
end
elseif Gen.ofState(S) == 2 then
out[#out + 1] = field("pokerus", "Pokerus (packed strain / days)", 0, 255)
out[#out + 1] = field("isEgg", "Egg", nil, nil, { toggle = true, alias = "egg" })
if Gen.hasCaughtData(S.save, S.version) then
out[#out + 1] = field("caughtLevel", "Caught level", 0, 63)
out[#out + 1] = field("caughtLocation", "Caught landmark ID", 0, 127)
out[#out + 1] =
field("caughtTime", "Caught time (0 unknown / 1 morning / 2 day / 3 night)", 0, 3)
end
end
return out
end
function P.all(S)
local out = P.identity(S)
if Gen.ofState(S) == 3 then
for _, list in ipairs({ P.contest, P.ribbons }) do
for _, d in ipairs(list) do
out[#out + 1] = d
end
end
end
return out
end
function P.find(S, key)
for _, d in ipairs(P.all(S)) do
if d.key == key then
return d
end
end
end
function P.ribbonWord(mon)
if type(mon.ribbons) == "table" then
return require("src.core.game3.rse.ribbons").word(mon)
end
return tonumber(mon.ribbons) or 0
end
function P.get(mon, d)
if d.shift then
if d.key == "ribbon.Champion" and mon.championRibbon ~= nil then
return mon.championRibbon and 1 or 0
end
return math.floor(P.ribbonWord(mon) / 2 ^ d.shift) % 2 ^ d.width
end
if d.parent then
return (mon[d.parent] and mon[d.parent][d.child]) or 0
end
local v = mon[d.key]
if d.key == "modernFatefulEncounter" and v == nil then
return math.floor(P.ribbonWord(mon) / 2 ^ 31) % 2 == 1
end
if v == nil and d.alias then
v = mon[d.alias]
end
if v == nil then
if d.toggle then
return false
end
v = d.default or (d.text and "") or 0
end
return v
end
function P.parse(d, value)
if d.toggle then
if value == true or value == 1 then
return true
end
if value == false or value == 0 then
return false
end
return nil, "Choose on or off"
end
if d.text then
return tostring(value or "")
end
local n = tonumber(value)
if not n or n ~= n or n ~= math.floor(n) or n < d.lo or n > d.hi then
return nil, ("%s must be a whole number from %d to %d"):format(d.label, d.lo, d.hi)
end
if d.values then
local found = false
for _, v in ipairs(d.values) do
if v == n then
found = true
end
end
if not found then
return nil, "Choose a supported value for " .. d.label
end
end
return n
end
-- Only Ops calls this after validation.
function P.write(mon, d, value)
if d.shift then
local word = P.ribbonWord(mon)
local old = math.floor(word / 2 ^ d.shift) % 2 ^ d.width
local n = value == true and 1 or value == false and 0 or value
mon.ribbons = word + (n - old) * 2 ^ d.shift
if d.key == "ribbon.Champion" then
mon.championRibbon = n == 1
end
elseif d.parent then
mon[d.parent] = mon[d.parent] or {}
mon[d.parent][d.child] = value
else
mon[d.key] = value
if d.key == "modernFatefulEncounter" then
require("src.core.game3.rse.ribbons").set(mon, "fateful", value and 1 or 0)
end
if d.alias then
mon[d.alias] = value
end
end
end
return P