mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-08 09:42:55 -04:00
866 lines
31 KiB
Lua
866 lines
31 KiB
Lua
local Pokemon = require("src.pokemon.Pokemon")
|
|
local Stats = require("src.pokemon.Stats")
|
|
local Growth = require("src.pokemon.Growth")
|
|
|
|
local MonOps = {}
|
|
|
|
function MonOps.create(data, species, level, gen)
|
|
level = math.max(1, math.min(100, math.floor(tonumber(level) or 5)))
|
|
if gen == 3 then
|
|
local PokemonG3 = require("src.core.game3.pokemon")
|
|
local SummaryData = require("src.core.game3.summary_data")
|
|
pcall(PokemonG3.install, nil)
|
|
|
|
local spId = tonumber(species) or (PokemonG3.speciesFromName and PokemonG3.speciesFromName(tostring(species))) or 1
|
|
local name = (PokemonG3.name and PokemonG3.name(spId)) or tostring(species)
|
|
|
|
local personality = (love and love.math and love.math.random and love.math.random(0, 0xFFFFFFFF))
|
|
or (math.floor(math.random() * 0x100000000) % 0x100000000)
|
|
|
|
local ivs = {}
|
|
for _, k in ipairs({ "hp", "atk", "def", "spe", "spa", "spd" }) do
|
|
ivs[k] = (love and love.math and love.math.random and love.math.random(0, 31)) or math.random(0, 31)
|
|
end
|
|
|
|
local meta = PokemonG3.speciesMeta and PokemonG3.speciesMeta(spId)
|
|
local friendship = (meta and meta.friendship) or 70
|
|
local growthRate = (meta and tonumber(meta.growthRate)) or 0
|
|
local ability = PokemonG3.abilityId and PokemonG3.abilityId(spId, personality) or 0
|
|
local gender = PokemonG3.gender and PokemonG3.gender(spId, personality) or "U"
|
|
|
|
local moves, pp, maxPp = {}, {}, {}
|
|
if PokemonG3.movesAtLevel then
|
|
moves, pp, maxPp = PokemonG3.movesAtLevel(spId, level)
|
|
end
|
|
|
|
local monMoves = {}
|
|
for slot = 1, 4 do
|
|
local mvId = moves[slot]
|
|
if mvId and mvId > 0 then
|
|
monMoves[slot] = {
|
|
id = mvId,
|
|
moveId = mvId,
|
|
pp = pp[slot] or 10,
|
|
maxPp = maxPp[slot] or 10,
|
|
}
|
|
end
|
|
end
|
|
|
|
local mon = {
|
|
species = spId,
|
|
speciesId = spId,
|
|
name = name,
|
|
nickname = "",
|
|
level = level,
|
|
growthRate = growthRate,
|
|
exp = SummaryData.expForLevel(growthRate, level),
|
|
moves = monMoves,
|
|
personality = personality,
|
|
nature = PokemonG3.natureId and PokemonG3.natureId(personality) or 0,
|
|
ivs = ivs,
|
|
dvs = {
|
|
attack = math.floor(ivs.atk / 2),
|
|
defense = math.floor(ivs.def / 2),
|
|
speed = math.floor(ivs.spe / 2),
|
|
special = math.floor(ivs.spa / 2),
|
|
hp = math.floor(ivs.hp / 2),
|
|
},
|
|
evs = { hp = 0, atk = 0, def = 0, spe = 0, spa = 0, spd = 0 },
|
|
ability = ability,
|
|
gender = gender,
|
|
happiness = friendship,
|
|
friendship = friendship,
|
|
pokeball = 4, -- pokefirered/src/pokemon.c:1820
|
|
}
|
|
|
|
require("src.core.game3.save_mon").normalize(mon)
|
|
mon.stats = {
|
|
hp = mon.maxHp,
|
|
attack = mon.attack,
|
|
defense = mon.defense,
|
|
speed = mon.speed,
|
|
spAtk = mon.spAtk,
|
|
spDef = mon.spDef,
|
|
specialAttack = mon.spAtk,
|
|
specialDefense = mon.spDef,
|
|
}
|
|
return mon
|
|
elseif gen == 2 then
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
local mon = Mon.new(data, species, level)
|
|
assert(mon, "unknown species " .. tostring(species))
|
|
return mon
|
|
end
|
|
return Pokemon.new(data, species, level)
|
|
end
|
|
|
|
function MonOps.recalc(data, mon, gen)
|
|
if type(mon) ~= "table" then return end
|
|
local isG3 = (gen == 3) or (mon.speciesId ~= nil) or (mon.personality ~= nil)
|
|
or (mon.ivs ~= nil) or (mon.evs ~= nil and mon.evs.spa ~= nil)
|
|
if isG3 then
|
|
local okP, PokemonG3 = pcall(require, "src.core.game3.pokemon")
|
|
if okP and PokemonG3 then
|
|
require("src.core.game3.save_mon").normalize(mon)
|
|
if mon.personality then
|
|
mon.nature = PokemonG3.natureId and PokemonG3.natureId(mon.personality) or mon.nature
|
|
mon.gender = PokemonG3.gender and PokemonG3.gender(mon.speciesId or mon.species, mon.personality) or mon.gender
|
|
local pair = PokemonG3.abilities(mon.speciesId or mon.species)
|
|
mon.abilityNum = mon.abilityNum or (pair[2] and pair[2] ~= 0 and mon.personality % 2 or 0)
|
|
mon.ability = pair[mon.abilityNum + 1] or pair[1]
|
|
mon.abilityId = mon.ability
|
|
end
|
|
if mon.maxHp then
|
|
mon.hp = math.max(0, math.min(mon.hp or mon.maxHp, mon.maxHp))
|
|
end
|
|
end
|
|
return
|
|
end
|
|
if gen == 2 or (mon.stats and mon.stats.specialAttack) or mon.experience then
|
|
require("src.battle.gen2.Mon").refreshStats(mon, data)
|
|
return
|
|
end
|
|
local def = data and data.pokemon and data.pokemon[mon.species]
|
|
assert(def, "unknown species")
|
|
mon.stats = Stats.calc(def, mon.level, mon.dvs, mon.statExp)
|
|
mon.hp = math.max(0, math.min(mon.hp or mon.stats.hp, mon.stats.hp))
|
|
end
|
|
|
|
MonOps.SPECIES_UNOWN_G3 = 201
|
|
MonOps.UNOWN_FORMS_G3 = 28
|
|
|
|
local function spliceLetterBits(half, lo2, hi2)
|
|
local bit = require("bit")
|
|
return bit.bor(bit.band(half, 0xFCFC), lo2, hi2 * 256)
|
|
end
|
|
|
|
-- include/pokemon.h:364 GET_UNOWN_LETTER, src/pokemon.c:2318 CreateMonWithGenderNatureLetter
|
|
function MonOps.unownPid(pid, letter, nature, shiny, tid, sid)
|
|
local bit = require("bit")
|
|
local PokemonG3 = require("src.core.game3.pokemon")
|
|
pid = (tonumber(pid) or 0) % 4294967296
|
|
letter = math.floor(tonumber(letter) or 0) % MonOps.UNOWN_FORMS_G3
|
|
nature = nature ~= nil and tonumber(nature) or nil
|
|
local tsv = bit.bxor((tonumber(tid) or 0) % 65536, (tonumber(sid) or 0) % 65536)
|
|
local oldLow, oldHigh = pid % 65536, math.floor(pid / 65536)
|
|
local function accept(high, low)
|
|
local p = high * 65536 + low
|
|
if p == 0 then return nil end
|
|
if nature ~= nil and p % 25 ~= nature then return nil end
|
|
local isShiny = bit.bxor(tsv, bit.bxor(high, low)) < 8
|
|
if shiny ~= nil and isShiny ~= (shiny and true or false) then return nil end
|
|
if PokemonG3.unownLetter(p) ~= letter then return nil end
|
|
return p
|
|
end
|
|
local same = accept(oldHigh, oldLow)
|
|
if same then return same end
|
|
local tsv89 = math.floor(tsv / 256) % 4
|
|
for d = 0, 4095 do
|
|
local low0 = bit.bxor(oldLow, (d % 64) * 4 + math.floor(d / 64) * 1024)
|
|
if shiny then
|
|
for b1 = 0, 3 do
|
|
local b3 = bit.bxor(b1, tsv89)
|
|
for b0 = 0, 3 do
|
|
for b2 = 0, 3 do
|
|
if (b3 * 64 + b2 * 16 + b1 * 4 + b0) % MonOps.UNOWN_FORMS_G3 == letter then
|
|
local low = spliceLetterBits(low0, b0, b1)
|
|
local base = bit.bxor(low, tsv)
|
|
for k2 = 0, 1 do
|
|
local high = bit.bor(bit.band(bit.bxor(base, k2 * 4), 0xFFFC), b2)
|
|
local p = accept(high, low)
|
|
if p then return p end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
for v = letter, 255, MonOps.UNOWN_FORMS_G3 do
|
|
local b0, b1 = v % 4, math.floor(v / 4) % 4
|
|
local b2, b3 = math.floor(v / 16) % 4, math.floor(v / 64)
|
|
local p = accept(spliceLetterBits(oldHigh, b2, b3), spliceLetterBits(low0, b0, b1))
|
|
if p then return p end
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function unownReq(mon)
|
|
if tonumber(mon.speciesId or mon.species) ~= MonOps.SPECIES_UNOWN_G3 then return nil end
|
|
return require("src.core.game3.pokemon").unownLetter(mon.personality)
|
|
end
|
|
|
|
-- src/pokemon.c:5751 DRAW_SPINDA_SPOTS
|
|
local function spindaPid(base, tsv, accept, shinyOnly)
|
|
local bit = require("bit")
|
|
base = (tonumber(base) or 0) % 4294967296
|
|
local ob = {}
|
|
for i = 0, 3 do ob[i] = bit.band(bit.rshift(base, 8 * i), 0xFF) end
|
|
local best, bestCost
|
|
local function try(p)
|
|
if not accept(p) then return end
|
|
local cost = 0
|
|
for i = 0, 3 do
|
|
local b = bit.band(bit.rshift(p, 8 * i), 0xFF)
|
|
if b ~= ob[i] then
|
|
cost = cost + 1000 + math.abs(b % 16 - ob[i] % 16)
|
|
+ math.abs(bit.rshift(b, 4) - bit.rshift(ob[i], 4))
|
|
end
|
|
end
|
|
if not bestCost or cost < bestCost then best, bestCost = p, cost end
|
|
end
|
|
if shinyOnly then
|
|
for low = 0, 65535 do
|
|
local h0 = bit.bxor(low, tsv)
|
|
for s = 0, 7 do try(bit.bxor(h0, s) * 65536 + low) end
|
|
end
|
|
return best
|
|
end
|
|
try(base)
|
|
if best then return best end
|
|
local function with(p, i, v)
|
|
return p + (v - bit.band(bit.rshift(p, 8 * i), 0xFF)) * 256 ^ i
|
|
end
|
|
for i = 0, 3 do
|
|
for v = 0, 255 do
|
|
if v ~= ob[i] then try(with(base, i, v)) end
|
|
end
|
|
end
|
|
if best then return best end
|
|
for i = 0, 2 do
|
|
for j = i + 1, 3 do
|
|
for v = 0, 255 do
|
|
if v ~= ob[i] then
|
|
local pi = with(base, i, v)
|
|
for w = 0, 255 do
|
|
if w ~= ob[j] then try(with(pi, j, w)) end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return best
|
|
end
|
|
|
|
function MonOps.generatePid(species, otId, otSecretId, reqs)
|
|
reqs = reqs or {}
|
|
local PokemonG3 = require("src.core.game3.pokemon")
|
|
local bit = require("bit")
|
|
|
|
local spId = tonumber(species) or (PokemonG3.speciesFromName and PokemonG3.speciesFromName(tostring(species))) or 1
|
|
if spId == MonOps.SPECIES_UNOWN_G3 and reqs.unownLetter ~= nil then
|
|
local p = MonOps.unownPid(reqs.basePid, reqs.unownLetter, reqs.nature, reqs.shiny, otId, otSecretId)
|
|
if p then return p end
|
|
end
|
|
otId = bit.band(tonumber(otId) or 0, 0xFFFF)
|
|
otSecretId = bit.band(tonumber(otSecretId) or 0, 0xFFFF)
|
|
local trainerXor = bit.bxor(otId, otSecretId)
|
|
|
|
local meta = PokemonG3.speciesMeta and PokemonG3.speciesMeta(spId)
|
|
local ratio = (meta and meta.genderRatio) or 127
|
|
|
|
local targetNature = reqs.nature and tonumber(reqs.nature)
|
|
local targetAbility = reqs.ability and tonumber(reqs.ability)
|
|
local targetGender = reqs.gender
|
|
local targetShiny = reqs.shiny
|
|
|
|
if spId == PokemonG3.SPECIES_SPINDA and reqs.basePid ~= nil then
|
|
local p = spindaPid(reqs.basePid, trainerXor, function(p)
|
|
if p == 0 then return false end
|
|
if targetNature ~= nil and p % 25 ~= targetNature then return false end
|
|
local b0 = p % 256
|
|
if targetAbility ~= nil and b0 % 2 ~= targetAbility then return false end
|
|
if targetGender ~= nil and targetGender ~= "" and targetGender ~= "U" then
|
|
local g
|
|
if ratio == PokemonG3.GENDER_MALE then g = "M"
|
|
elseif ratio == PokemonG3.GENDER_FEMALE then g = "F"
|
|
elseif ratio == PokemonG3.GENDER_GENDERLESS then g = "U"
|
|
elseif ratio > b0 then g = "F"
|
|
else g = "M" end
|
|
if g ~= targetGender then return false end
|
|
end
|
|
if targetShiny ~= nil then
|
|
local isShiny = bit.bxor(trainerXor, bit.bxor(math.floor(p / 65536), p % 65536)) < 8
|
|
if isShiny ~= (targetShiny and true or false) then return false end
|
|
end
|
|
return true
|
|
end, targetShiny == true)
|
|
if p then return p end
|
|
end
|
|
|
|
-- 1. Determine valid low-byte (b0) for gender & ability slot
|
|
local validB0 = {}
|
|
for b = 0, 255 do
|
|
local ok = true
|
|
if targetAbility ~= nil and (b % 2) ~= targetAbility then ok = false end
|
|
if ok and targetGender ~= nil and targetGender ~= "" and targetGender ~= "U" then
|
|
local g
|
|
if ratio == PokemonG3.GENDER_MALE then g = "M"
|
|
elseif ratio == PokemonG3.GENDER_FEMALE then g = "F"
|
|
elseif ratio == PokemonG3.GENDER_GENDERLESS then g = "U"
|
|
elseif ratio > b then g = "F"
|
|
else g = "M" end
|
|
if g ~= targetGender then ok = false end
|
|
end
|
|
if ok then validB0[#validB0 + 1] = b end
|
|
end
|
|
if #validB0 == 0 then
|
|
for b = 0, 255 do
|
|
if targetAbility == nil or (b % 2) == targetAbility then validB0[#validB0 + 1] = b end
|
|
end
|
|
end
|
|
|
|
local sOffset = (targetShiny == true) and math.random(0, 7) or (targetShiny == false and math.random(8, 255) or math.random(0, 255))
|
|
local targetXor = bit.bxor(trainerXor, sOffset)
|
|
|
|
-- 2. Search across b0 and b1 for matching nature modulo 25
|
|
for _, b0 in ipairs(validB0) do
|
|
for b1 = 0, 255 do
|
|
local pLow = b1 * 256 + b0
|
|
local pHigh = bit.bxor(pLow, targetXor)
|
|
local p = pHigh * 65536 + pLow
|
|
if targetNature == nil or (p % 25) == targetNature then
|
|
return p
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Fallback satisfying nature and ability parity
|
|
local base = (targetNature or 0)
|
|
if targetAbility == 1 and (base % 2 == 0) then
|
|
base = base + 25
|
|
elseif targetAbility == 0 and (base % 2 == 1) then
|
|
base = base + 25
|
|
end
|
|
return base
|
|
end
|
|
|
|
function MonOps.setHeldItem(data, mon, itemId, gen)
|
|
if type(mon) ~= "table" then return end
|
|
local isG3 = (gen == 3) or (mon.speciesId ~= nil) or (mon.personality ~= nil)
|
|
if isG3 then
|
|
local ItemsData = require("src.core.game3.items_data")
|
|
local numId = tonumber(itemId) or (itemId and ItemsData.toNumericId and ItemsData.toNumericId(itemId))
|
|
mon.heldItem = numId or itemId
|
|
mon.item = mon.heldItem
|
|
return
|
|
end
|
|
mon.item = itemId
|
|
end
|
|
|
|
function MonOps.setHappiness(data, mon, val, gen)
|
|
if type(mon) ~= "table" then return end
|
|
local v = math.max(0, math.min(255, math.floor(tonumber(val) or 0)))
|
|
mon.happiness = v
|
|
mon.friendship = v
|
|
end
|
|
|
|
function MonOps.setNature(data, mon, natureId, gen)
|
|
if type(mon) ~= "table" then return end
|
|
natureId = math.max(0, math.min(24, math.floor(tonumber(natureId) or 0)))
|
|
mon.nature = natureId
|
|
local isG3 = (gen == 3) or (mon.speciesId ~= nil) or (mon.personality ~= nil)
|
|
if isG3 then
|
|
local currentAbility = mon.abilityNum or (mon.personality and (mon.personality % 2)) or 0
|
|
local isShiny = require("src.core.game3.summary_data").isShiny(mon)
|
|
mon.personality = MonOps.generatePid(mon.speciesId or mon.species, mon.otId or 0, mon.otSecretId or 0, {
|
|
nature = natureId,
|
|
ability = currentAbility,
|
|
gender = mon.gender,
|
|
shiny = isShiny,
|
|
unownLetter = unownReq(mon),
|
|
basePid = mon.personality,
|
|
})
|
|
MonOps.recalc(data, mon, gen)
|
|
end
|
|
end
|
|
|
|
function MonOps.setAbility(data, mon, abilitySlot, gen)
|
|
if type(mon) ~= "table" then return end
|
|
abilitySlot = (tonumber(abilitySlot) or 0) % 2
|
|
local isG3 = (gen == 3) or (mon.speciesId ~= nil) or (mon.personality ~= nil)
|
|
if isG3 then
|
|
local currentNature = (mon.personality and (mon.personality % 25)) or mon.nature or 0
|
|
local isShiny = require("src.core.game3.summary_data").isShiny(mon)
|
|
mon.personality = MonOps.generatePid(mon.speciesId or mon.species, mon.otId or 0, mon.otSecretId or 0, {
|
|
nature = currentNature,
|
|
ability = abilitySlot,
|
|
gender = mon.gender,
|
|
shiny = isShiny,
|
|
unownLetter = unownReq(mon),
|
|
basePid = mon.personality,
|
|
})
|
|
mon.abilityNum = abilitySlot
|
|
local PokemonG3 = require("src.core.game3.pokemon")
|
|
mon.ability = PokemonG3.abilityId(mon.speciesId or mon.species, mon.personality)
|
|
MonOps.recalc(data, mon, gen)
|
|
end
|
|
end
|
|
|
|
function MonOps.setGender(data, mon, gender, gen)
|
|
if type(mon) ~= "table" then return end
|
|
local isG3 = (gen == 3) or (mon.speciesId ~= nil) or (mon.personality ~= nil)
|
|
if isG3 then
|
|
local currentNature = (mon.personality and (mon.personality % 25)) or mon.nature or 0
|
|
local currentAbility = mon.abilityNum or (mon.personality and (mon.personality % 2)) or 0
|
|
local isShiny = require("src.core.game3.summary_data").isShiny(mon)
|
|
mon.personality = MonOps.generatePid(mon.speciesId or mon.species, mon.otId or 0, mon.otSecretId or 0, {
|
|
nature = currentNature,
|
|
ability = currentAbility,
|
|
gender = gender,
|
|
shiny = isShiny,
|
|
unownLetter = unownReq(mon),
|
|
basePid = mon.personality,
|
|
})
|
|
local PokemonG3 = require("src.core.game3.pokemon")
|
|
mon.gender = PokemonG3.gender(mon.speciesId or mon.species, mon.personality)
|
|
MonOps.recalc(data, mon, gen)
|
|
else
|
|
mon.gender = gender
|
|
end
|
|
end
|
|
|
|
function MonOps.setShiny(data, mon, shiny, gen)
|
|
if type(mon) ~= "table" then return end
|
|
local isG3 = (gen == 3) or (mon.speciesId ~= nil) or (mon.personality ~= nil)
|
|
if isG3 then
|
|
local currentNature = (mon.personality and (mon.personality % 25)) or mon.nature or 0
|
|
local currentAbility = mon.abilityNum or (mon.personality and (mon.personality % 2)) or 0
|
|
mon.personality = MonOps.generatePid(mon.speciesId or mon.species, mon.otId or 0, mon.otSecretId or 0, {
|
|
nature = currentNature,
|
|
ability = currentAbility,
|
|
gender = mon.gender,
|
|
shiny = shiny,
|
|
unownLetter = unownReq(mon),
|
|
basePid = mon.personality,
|
|
})
|
|
mon.isShiny = shiny
|
|
MonOps.recalc(data, mon, gen)
|
|
else
|
|
mon.shiny = shiny
|
|
end
|
|
end
|
|
|
|
function MonOps.setLevel(data, mon, level, gen)
|
|
if type(mon) ~= "table" then return end
|
|
level = math.max(1, math.min(100, math.floor(level)))
|
|
local isG3 = (gen == 3) or (mon.speciesId ~= nil) or (mon.personality ~= nil) or (mon.ivs ~= nil)
|
|
mon.level = level
|
|
|
|
if isG3 then
|
|
local SummaryData = require("src.core.game3.summary_data")
|
|
local gr = require("src.core.game3.pokemon").growthRate(require("src.core.game3.pokemon").speciesOf(mon)) or mon.growthRate or 0
|
|
mon.exp = SummaryData.expForLevel(gr, level)
|
|
mon.experience = mon.exp
|
|
MonOps.recalc(data, mon, gen)
|
|
return
|
|
end
|
|
|
|
local def = data and data.pokemon and data.pokemon[mon.species]
|
|
if gen == 2 or mon.experience ~= nil then
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
local growth = Mon.growthFor(data, def and def.growthRate)
|
|
mon.experience = Mon.experienceForLevel(growth, level)
|
|
Mon.refreshStats(mon, data)
|
|
return
|
|
end
|
|
mon.exp = Growth.expForLevel(def and def.growthRate or 0, level)
|
|
MonOps.recalc(data, mon, gen)
|
|
end
|
|
|
|
function MonOps.clearMove(mon, slot)
|
|
for _, key in ipairs({ "moves", "pp", "maxPp", "moveIds", "ppBonuses", "ppBonus", "ppUp" }) do
|
|
if type(mon[key]) == "table" then mon[key][slot] = nil end
|
|
end
|
|
if type(mon.ppBonusesPacked) == "number" then
|
|
local bit = require("bit")
|
|
mon.ppBonusesPacked = bit.band(mon.ppBonusesPacked, bit.bnot(bit.lshift(3, (slot - 1) * 2)))
|
|
end
|
|
end
|
|
|
|
function MonOps.setMove(data, mon, slot, moveId)
|
|
assert(slot >= 1 and slot <= 4)
|
|
local mdef = data and data.moves and data.moves[moveId]
|
|
local numMove = tonumber(moveId)
|
|
if not mdef and numMove then
|
|
local okP, PokemonG3 = pcall(require, "src.core.game3.pokemon")
|
|
if okP and PokemonG3 then
|
|
local mName = PokemonG3.moveName(numMove)
|
|
local bMove = PokemonG3.battleMove(numMove)
|
|
mdef = {
|
|
id = mName,
|
|
moveId = numMove,
|
|
name = mName,
|
|
pp = (bMove and tonumber(bMove.pp)) or 10,
|
|
maxPp = (bMove and tonumber(bMove.pp)) or 10,
|
|
}
|
|
end
|
|
end
|
|
assert(type(mdef) == "table", "unknown move: " .. tostring(moveId))
|
|
mon.moves = mon.moves or {}
|
|
local basePp = mdef.pp or 10
|
|
if mon.speciesId ~= nil or mon.personality ~= nil then
|
|
local nativeId = assert(tonumber(mdef.moveId or numMove), "unknown native move")
|
|
MonOps.clearMove(mon, slot)
|
|
mon.moves[slot] = nativeId
|
|
mon.pp, mon.maxPp = mon.pp or {}, mon.maxPp or {}
|
|
mon.pp[slot], mon.maxPp[slot] = basePp, basePp
|
|
return
|
|
end
|
|
local currentUps = (mon.moves[slot] and mon.moves[slot].ppUps) or 0
|
|
mon.moves[slot] = {
|
|
id = mdef.id or mdef.name or tostring(moveId),
|
|
moveId = mdef.moveId or numMove,
|
|
pp = basePp + currentUps * math.floor(basePp / 5),
|
|
ppUps = currentUps > 0 and currentUps or nil,
|
|
maxPp = basePp + currentUps * math.floor(basePp / 5),
|
|
}
|
|
end
|
|
|
|
function MonOps.getPpUps(mon, slot)
|
|
if type(mon) ~= "table" or not slot then return 0 end
|
|
if type(mon.ppBonusesPacked) == "number" then
|
|
local bit = require("bit")
|
|
return bit.band(bit.rshift(mon.ppBonusesPacked, (slot - 1) * 2), 3)
|
|
end
|
|
local mv = mon.moves and mon.moves[slot]
|
|
if type(mv) == "table" and mv.ppUps ~= nil then
|
|
return tonumber(mv.ppUps) or 0
|
|
end
|
|
if type(mon.ppBonuses) == "table" and mon.ppBonuses[slot] ~= nil then
|
|
return tonumber(mon.ppBonuses[slot]) or 0
|
|
end
|
|
return 0
|
|
end
|
|
|
|
function MonOps.getBasePp(data, mon, slot)
|
|
if type(mon) ~= "table" or not slot then return 10 end
|
|
local mv = mon.moves and mon.moves[slot]
|
|
local moveId = nil
|
|
if type(mv) == "table" then
|
|
moveId = mv.moveId or mv.id or mv.move
|
|
if mv.basePp then return tonumber(mv.basePp) end
|
|
elseif type(mv) == "number" then
|
|
moveId = mv
|
|
elseif type(mv) == "string" then
|
|
moveId = mv
|
|
end
|
|
if not moveId then return 10 end
|
|
|
|
local num = tonumber(moveId)
|
|
local mdef = data and data.moves and (data.moves[moveId] or (num and data.moves[num]))
|
|
if mdef and mdef.pp then return tonumber(mdef.pp) end
|
|
|
|
local okP, PokemonG3 = pcall(require, "src.core.game3.pokemon")
|
|
if okP and PokemonG3 then
|
|
if num and PokemonG3.battleMove then
|
|
local row = PokemonG3.battleMove(num)
|
|
if row and row.pp then return tonumber(row.pp) end
|
|
end
|
|
if num and PokemonG3.movePp then
|
|
local pp = PokemonG3.movePp(num)
|
|
if pp and pp > 0 then return pp end
|
|
end
|
|
end
|
|
|
|
local okB, BuiltinMoves = pcall(require, "src.core.game3.battle.builtin_moves")
|
|
if okB and BuiltinMoves then
|
|
if num and BuiltinMoves[num] and BuiltinMoves[num].pp then
|
|
return tonumber(BuiltinMoves[num].pp)
|
|
end
|
|
if type(moveId) == "string" then
|
|
local norm = moveId:upper():gsub("[^A-Z0-9]", " ")
|
|
for _, row in pairs(BuiltinMoves) do
|
|
if row.name and row.name:upper() == norm and row.pp then
|
|
return tonumber(row.pp)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return 10
|
|
end
|
|
|
|
function MonOps.calcMaxPp(basePp, ppUps, gen)
|
|
basePp = math.max(1, tonumber(basePp) or 10)
|
|
ppUps = math.max(0, math.min(3, tonumber(ppUps) or 0))
|
|
if gen == 1 or gen == 2 then
|
|
-- GBPKM.GetMovePP and Gen 2 engine/items/item_effects.asm cap the
|
|
-- per-PP-Up bonus at seven: a 40 PP move reaches 61, not 64.
|
|
return basePp + ppUps * math.min(7, math.floor(basePp / 5))
|
|
end
|
|
return basePp + math.floor(basePp * 20 * ppUps / 100)
|
|
end
|
|
|
|
function MonOps.setPpUps(data, mon, slot, ppUps, gen)
|
|
if type(mon) ~= "table" or not slot then return end
|
|
ppUps = math.max(0, math.min(3, math.floor(tonumber(ppUps) or 0)))
|
|
local basePp = MonOps.getBasePp(data, mon, slot)
|
|
if basePp == 1 then ppUps = 0 end -- Sketch cannot receive PP Ups.
|
|
|
|
local isG3 = (gen == 3) or (mon.speciesId ~= nil) or (mon.personality ~= nil) or (type(mon.ppBonusesPacked) == "number")
|
|
local newMaxPp = MonOps.calcMaxPp(basePp, ppUps, isG3 and 3 or (gen or 1))
|
|
if isG3 then
|
|
local bit = require("bit")
|
|
local shift = (slot - 1) * 2
|
|
local packed = tonumber(mon.ppBonusesPacked) or 0
|
|
mon.ppBonusesPacked = bit.bor(bit.band(packed, bit.bnot(bit.lshift(3, shift))), bit.lshift(ppUps, shift))
|
|
mon.maxPp = mon.maxPp or {}
|
|
mon.maxPp[slot] = newMaxPp
|
|
mon.pp = mon.pp or {}
|
|
if mon.pp[slot] == nil or mon.pp[slot] > newMaxPp then
|
|
mon.pp[slot] = newMaxPp
|
|
end
|
|
local mv = mon.moves and mon.moves[slot]
|
|
if type(mv) == "table" then
|
|
mv.pp, mv.maxPp = mon.pp[slot], newMaxPp
|
|
mv.ppUps = ppUps > 0 and ppUps or nil
|
|
end
|
|
else
|
|
local mv = mon.moves and mon.moves[slot]
|
|
if type(mv) == "table" then
|
|
mv.ppUps = ppUps > 0 and ppUps or nil
|
|
mv.maxPp = newMaxPp
|
|
if mv.pp == nil or mv.pp > newMaxPp then
|
|
mv.pp = newMaxPp
|
|
end
|
|
end
|
|
if type(mon.pp) == "table" and (mon.pp[slot] == nil or mon.pp[slot] > newMaxPp) then
|
|
mon.pp[slot] = newMaxPp
|
|
end
|
|
if type(mon.maxPp) == "table" then
|
|
mon.maxPp[slot] = newMaxPp
|
|
end
|
|
end
|
|
return ppUps, newMaxPp
|
|
end
|
|
|
|
function MonOps.setPp(data, mon, slot, value, gen)
|
|
if type(mon) ~= "table" or not slot then return end
|
|
local basePp = MonOps.getBasePp(data, mon, slot)
|
|
local ppUps = MonOps.getPpUps(mon, slot)
|
|
local isG3 = (gen == 3) or (mon.speciesId ~= nil) or (mon.personality ~= nil) or (type(mon.ppBonusesPacked) == "number")
|
|
local maxPp = MonOps.calcMaxPp(basePp, ppUps, isG3 and 3 or (gen or 1))
|
|
local target = math.max(0, math.min(maxPp, math.floor(tonumber(value) or 0)))
|
|
if isG3 then
|
|
mon.pp = mon.pp or {}
|
|
mon.pp[slot] = target
|
|
mon.maxPp = mon.maxPp or {}
|
|
mon.maxPp[slot] = maxPp
|
|
local mv = mon.moves and mon.moves[slot]
|
|
if type(mv) == "table" then mv.pp, mv.maxPp = target, maxPp end
|
|
else
|
|
local mv = mon.moves and mon.moves[slot]
|
|
if type(mv) == "table" then
|
|
mv.pp = target
|
|
mv.maxPp = maxPp
|
|
end
|
|
if type(mon.pp) == "table" then
|
|
mon.pp[slot] = target
|
|
end
|
|
end
|
|
return target, maxPp
|
|
end
|
|
|
|
function MonOps.maxAllPpUps(data, mon, gen)
|
|
if type(mon) ~= "table" or not mon.moves then return end
|
|
local ups = (mon.isEgg or mon.egg) and 0 or 3
|
|
for slot = 1, 4 do
|
|
local move = mon.moves[slot]
|
|
local id = type(move) == "table" and (move.moveId or move.id) or move
|
|
if id and id ~= 0 then
|
|
MonOps.setPpUps(data, mon, slot, ups, gen)
|
|
local basePp = MonOps.getBasePp(data, mon, slot)
|
|
local isG3 = gen == 3 or mon.speciesId ~= nil or mon.personality ~= nil
|
|
local maxPp = MonOps.calcMaxPp(basePp, MonOps.getPpUps(mon, slot), isG3 and 3 or (gen or 1))
|
|
MonOps.setPp(data, mon, slot, maxPp, gen)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- HP DV is derived from the low bits of the other four (Stats.randomDVs).
|
|
function MonOps.syncHpDv(dvs)
|
|
if not dvs then return end
|
|
dvs.hp = (dvs.attack % 2) * 8 + (dvs.defense % 2) * 4
|
|
+ (dvs.speed % 2) * 2 + (dvs.special % 2)
|
|
return dvs
|
|
end
|
|
|
|
local function deriveGen2(data, mon)
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
local Unown = require("src.core.gen2.Unown")
|
|
mon.dvs.hp = Mon.hpDV(mon.dvs)
|
|
local def = data and data.pokemon and data.pokemon[mon.species]
|
|
if def then
|
|
mon.gender = Mon.gender(def, mon.dvs, { species = mon.species, level = mon.level })
|
|
end
|
|
mon.shiny = Mon.isShiny(mon.dvs, { species = mon.species, def = def, level = mon.level })
|
|
if mon.species == Unown.SPECIES then
|
|
mon.unownLetter = Unown.letterFromDVs(mon.dvs)
|
|
end
|
|
end
|
|
|
|
function MonOps.setDv(data, mon, key, value, gen)
|
|
if type(mon) ~= "table" then return end
|
|
mon.dvs = mon.dvs or { attack = 15, defense = 15, speed = 15, special = 15, hp = 15 }
|
|
mon.dvs[key] = math.max(0, math.min(15, math.floor(value)))
|
|
if key ~= "hp" then
|
|
MonOps.syncHpDv(mon.dvs)
|
|
end
|
|
|
|
local isG3 = (gen == 3) or (mon.speciesId ~= nil) or (mon.personality ~= nil) or (mon.ivs ~= nil)
|
|
if isG3 then
|
|
mon.ivs = mon.ivs or {}
|
|
mon.ivs.hp = math.min(31, (mon.dvs.hp or 0) * 2 + 1)
|
|
mon.ivs.atk = math.min(31, (mon.dvs.attack or 0) * 2 + 1)
|
|
mon.ivs.def = math.min(31, (mon.dvs.defense or 0) * 2 + 1)
|
|
mon.ivs.spe = math.min(31, (mon.dvs.speed or 0) * 2 + 1)
|
|
mon.ivs.spa = math.min(31, (mon.dvs.special or 0) * 2 + 1)
|
|
mon.ivs.spd = math.min(31, (mon.dvs.special or 0) * 2 + 1)
|
|
elseif gen == 2 or (mon.stats and mon.stats.specialAttack) then
|
|
deriveGen2(data, mon)
|
|
end
|
|
MonOps.recalc(data, mon, gen)
|
|
end
|
|
|
|
local DV_KEYS = { "attack", "defense", "speed", "special" }
|
|
|
|
-- engine/gfx/load_pics.asm:1 GetUnownLetter, engine/gfx/color.asm:8 CheckShininess
|
|
function MonOps.unownDvs(dvs, letter, keepShiny)
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
letter = math.floor(tonumber(letter) or 1)
|
|
if letter < 1 or letter > 26 then return nil end
|
|
local lo, hi = (letter - 1) * 10, math.min(255, letter * 10 - 1)
|
|
local best, bestKey
|
|
for p = lo, hi do
|
|
local out = {}
|
|
for i, k in ipairs(DV_KEYS) do
|
|
local d = tonumber(dvs[k]) or 0
|
|
local mid = math.floor(p / 4 ^ (4 - i)) % 4
|
|
out[k] = d - (math.floor(d / 2) % 4) * 2 + mid * 2
|
|
end
|
|
local cost = 0
|
|
for _, k in ipairs(DV_KEYS) do cost = cost + math.abs(out[k] - (tonumber(dvs[k]) or 0)) end
|
|
local key = (Mon.vanillaShiny(out) == (keepShiny and true or false) and 0 or 1000) + cost
|
|
if not bestKey or key < bestKey then best, bestKey = out, key end
|
|
end
|
|
return best
|
|
end
|
|
|
|
-- pokecrystal/engine/gfx/color.asm:8 CheckShininess, pokecrystal/engine/battle/hidden_power.asm:13
|
|
function MonOps.maxGen2Dvs(data, mon, gen)
|
|
if type(mon) ~= "table" then return end
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
local Unown = require("src.core.gen2.Unown")
|
|
mon.dvs = mon.dvs or {}
|
|
local keepShiny = mon.shiny == true or Mon.vanillaShiny(mon.dvs)
|
|
local out = keepShiny and { attack = 15, defense = 10, speed = 10, special = 10 }
|
|
or { attack = 15, defense = 15, speed = 15, special = 15 }
|
|
if mon.species == Unown.SPECIES then
|
|
out = MonOps.unownDvs(out, Unown.monLetter(mon), keepShiny) or out
|
|
end
|
|
for k, v in pairs(out) do mon.dvs[k] = v end
|
|
deriveGen2(data, mon)
|
|
MonOps.recalc(data, mon, gen)
|
|
return keepShiny and mon.shiny == true
|
|
end
|
|
|
|
function MonOps.setUnownForm(data, mon, letter, gen)
|
|
if type(mon) ~= "table" then return nil end
|
|
if gen == 3 then
|
|
local Summary = require("src.core.game3.summary_data")
|
|
local p = MonOps.unownPid(mon.personality, letter, (tonumber(mon.personality) or 0) % 25,
|
|
Summary.isShiny(mon), mon.otId, mon.otSecretId)
|
|
if not p then return nil end
|
|
mon.personality = p
|
|
mon.isShiny = nil
|
|
MonOps.recalc(data, mon, gen)
|
|
return true
|
|
end
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
mon.dvs = mon.dvs or { attack = 0, defense = 0, speed = 0, special = 0 }
|
|
local wasShiny = mon.shiny == true or Mon.vanillaShiny(mon.dvs)
|
|
local out = MonOps.unownDvs(mon.dvs, letter, wasShiny)
|
|
if not out then return nil end
|
|
for k, v in pairs(out) do mon.dvs[k] = v end
|
|
deriveGen2(data, mon)
|
|
MonOps.recalc(data, mon, gen)
|
|
return true, wasShiny and not mon.shiny
|
|
end
|
|
|
|
local EV_KEYS = { "hp", "atk", "def", "spe", "spa", "spd" }
|
|
|
|
function MonOps.setEv(data, mon, key, value, gen)
|
|
if type(mon) ~= "table" or not key then return end
|
|
mon.evs = mon.evs or { hp = 0, atk = 0, def = 0, spe = 0, spa = 0, spd = 0 }
|
|
local target = math.max(0, math.min(255, math.floor(tonumber(value) or 0)))
|
|
local otherTotal = 0
|
|
for _, k in ipairs(EV_KEYS) do
|
|
if k ~= key then
|
|
otherTotal = otherTotal + (tonumber(mon.evs[k]) or 0)
|
|
end
|
|
end
|
|
if otherTotal + target > 510 then
|
|
target = math.max(0, 510 - otherTotal)
|
|
end
|
|
mon.evs[key] = target
|
|
MonOps.recalc(data, mon, gen)
|
|
return target
|
|
end
|
|
|
|
function MonOps.clearEvs(data, mon, gen)
|
|
if type(mon) ~= "table" then return end
|
|
mon.evs = { hp = 0, atk = 0, def = 0, spe = 0, spa = 0, spd = 0 }
|
|
MonOps.recalc(data, mon, gen)
|
|
end
|
|
|
|
function MonOps.setIv(data, mon, key, value, gen)
|
|
if type(mon) ~= "table" or not key then return end
|
|
mon.ivs = mon.ivs or { hp = 31, atk = 31, def = 31, spe = 31, spa = 31, spd = 31 }
|
|
local target = math.max(0, math.min(31, math.floor(tonumber(value) or 0)))
|
|
mon.ivs[key] = target
|
|
mon.dvs = mon.dvs or {}
|
|
mon.dvs.attack = math.floor((mon.ivs.atk or 0) / 2)
|
|
mon.dvs.defense = math.floor((mon.ivs.def or 0) / 2)
|
|
mon.dvs.speed = math.floor((mon.ivs.spe or 0) / 2)
|
|
mon.dvs.special = math.floor(((mon.ivs.spa or 0) + (mon.ivs.spd or 0)) / 4)
|
|
mon.dvs.hp = math.floor((mon.ivs.hp or 0) / 2)
|
|
MonOps.recalc(data, mon, gen)
|
|
return target
|
|
end
|
|
|
|
function MonOps.maxIvs(data, mon, gen)
|
|
if type(mon) ~= "table" then return end
|
|
mon.ivs = { hp = 31, atk = 31, def = 31, spe = 31, spa = 31, spd = 31 }
|
|
mon.dvs = { attack = 15, defense = 15, speed = 15, special = 15, hp = 15 }
|
|
MonOps.recalc(data, mon, gen)
|
|
end
|
|
|
|
function MonOps.setSpecies(data, mon, species, gen)
|
|
if type(mon) ~= "table" then return end
|
|
local isG3 = (gen == 3) or (mon.speciesId ~= nil) or (mon.personality ~= nil)
|
|
if isG3 then
|
|
local PokemonG3 = require("src.core.game3.pokemon")
|
|
pcall(PokemonG3.install, nil)
|
|
local spId = tonumber(species) or (PokemonG3.speciesFromName and PokemonG3.speciesFromName(tostring(species))) or 1
|
|
local name = (PokemonG3.name and PokemonG3.name(spId)) or tostring(species)
|
|
mon.species = spId
|
|
mon.speciesNumbering = PokemonG3.NUMBERING_INTERNAL
|
|
mon.speciesId = spId
|
|
mon.name = name
|
|
local meta = PokemonG3.speciesMeta and PokemonG3.speciesMeta(spId)
|
|
mon.growthRate = (meta and tonumber(meta.growthRate)) or 0
|
|
MonOps.setLevel(data, mon, mon.level or 5, gen)
|
|
return
|
|
end
|
|
assert(data.pokemon[species], "unknown species: " .. tostring(species))
|
|
mon.species = species
|
|
MonOps.setLevel(data, mon, mon.level, gen)
|
|
if gen == 2 or mon.experience ~= nil or (mon.stats and mon.stats.specialAttack) then
|
|
require("src.battle.gen2.Mon").syncIdentity(mon, data)
|
|
end
|
|
end
|
|
|
|
return MonOps
|