-- Experience gain (engine/battle/experience.asm): -- exp = floor(baseExp * enemyLevel / 7) for a single participant -- (trainer battles multiply by 1.5 in Gen 1) -- Stat experience: the defeated species' base stats are added to each -- participant's stat exp. local Growth = require("src.pokemon.Growth") local Runtime = require("src.mods.Runtime") local Stats = require("src.pokemon.Stats") local Experience = {} -- engine/battle/experience.asm order: baseExp is divided by the -- participant count FIRST, then *level/7, then the traded x1.5 -- (BoostExp) and finally the trainer x1.5. -- -- EXP.ALL (core.asm .halveExpDataLoop): the base values are halved, -- GainExperience runs for the participants, then reruns for the whole -- party -- and because DivideExpDataByNumMonsGainingExp divides the -- base values IN PLACE, the second pass inherits the participant -- division: each party member gets (base/2)/participants/partyCount. -- Sequential floor divisions equal one floor division by the product, -- so callers pass numParticipants = 2*participants for the first pass -- and 2*participants*partyCount for the whole-party pass. -- -- consts is Data.constants; a constants.exp record can retune the -- divisor and the traded/trainer multipliers, with the values above as -- the defaults. function Experience.gainFor(defeatedDef, level, isTrainer, numParticipants, traded, consts) local divisor, tradedMult, trainerMult = 7, nil, nil local tuning = consts and consts.exp if tuning then divisor = tuning.divisor or divisor tradedMult = tuning.tradedMult trainerMult = tuning.trainerMult end local base = math.floor(defeatedDef.baseExp / math.max(1, numParticipants or 1)) local exp = math.floor(base * level / divisor) if traded then exp = math.floor(exp * (tradedMult or 1.5)) end if isTrainer then exp = math.floor(exp * (trainerMult or 1.5)) end return math.max(1, exp) end -- Applies exp/stat exp; returns the list of levels gained plus the raw -- exp delta (wExpAmountGained, printed by _ExpPointsText -- captured -- before the max-level cap, experience.asm:92-100). function Experience.apply(data, mon, defeatedDef, level, isTrainer, numParticipants, traded) local speciesDef = data.pokemon[mon.species] -- stat exp is divided among participants too -- (DivideExpDataByNumMonsGainingExp divides wEnemyMonBaseStats) local statShare = math.max(1, numParticipants or 1) for _, key in ipairs(Stats.ORDER) do local gain = math.floor(defeatedDef.baseStats[key] / statShare) mon.statExp[key] = math.min(65535, (mon.statExp[key] or 0) + gain) end local consts = data.constants local gained if Runtime.wantsHook("exp.gain") then gained = Runtime.call("exp.gain", function(c) return Experience.gainFor(c.defeatedDef, c.level, c.isTrainer, c.participants, c.traded, consts) end, { defeatedDef = defeatedDef, level = level, isTrainer = isTrainer, participants = numParticipants, traded = traded, mon = mon }) else gained = Experience.gainFor(defeatedDef, level, isTrainer, numParticipants, traded, consts) end mon.exp = mon.exp + gained local cap = consts and consts.levelCap or 100 local levels = {} local newLevel = Growth.levelForExp(speciesDef.growthRate, mon.exp, cap, data.growth_rates) while mon.level < math.min(newLevel, cap) do mon.level = mon.level + 1 local old = mon.stats mon.stats = Stats.calc(speciesDef, mon.level, mon.dvs, mon.statExp) mon.hp = math.min(mon.stats.hp, mon.hp + (mon.stats.hp - old.hp)) table.insert(levels, mon.level) if Runtime.wants("pokemon.level_up") then Runtime.emit("pokemon.level_up", { mon = mon, level = mon.level, prevLevel = mon.level - 1, learnable = Experience.movesLearnedAt(speciesDef, mon.level), }) end end return levels, gained end -- Moves learned when reaching exactly `level`. function Experience.movesLearnedAt(speciesDef, level) local out = {} for _, entry in ipairs(speciesDef.learnset) do if entry.level == level then table.insert(out, entry.move) end end return out end return Experience