mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-29 07:00:55 -04:00
FIELD GOAL
This commit is contained in:
@@ -133,7 +133,10 @@ local function seq_push(text, wait, id)
|
||||
-- pokefirered/src/battle_controller_pokedude.c:209 HandlePokedudeVoiceoverEtc
|
||||
Pokedude.event(st, "printstring", st.pdActor, id)
|
||||
end
|
||||
Ui.pushTimed(tostring(text or ""), tonumber(wait) or (AnimSeq.isMoveUsedId(id) and 0 or 64))
|
||||
text = tostring(text or "")
|
||||
-- pokeemerald/src/battle_message.c:3005
|
||||
if text:sub(-2) == "\\p" and not (st and (st.link or st.pokedude or (st.kinds and st.kinds.recordedLink))) then return Ui.push(text) end
|
||||
Ui.pushTimed(text, tonumber(wait) or (AnimSeq.isMoveUsedId(id) and 0 or 64))
|
||||
end
|
||||
|
||||
local function push_msgs(list)
|
||||
@@ -1653,21 +1656,39 @@ local function handle_player_faint(opts)
|
||||
})
|
||||
end
|
||||
|
||||
local function wild_victory_song(st, awards)
|
||||
if not st or not st.wild or st.pokedude or st.link or st._wildVictorySong then return end
|
||||
if not (awards and #awards > 0) then return end
|
||||
local lead = State.battler and State.battler(st, 0) or st.player
|
||||
if not (lead and lead.mon and (tonumber(lead.mon.hp) or 0) > 0) then return end
|
||||
st._wildVictorySong = true
|
||||
stop_low_hp_song()
|
||||
local Audio = require("src.core.game3.audio")
|
||||
local bp = BattleProfile.of(st)
|
||||
if bp.music then
|
||||
-- pokeemerald/src/battle_script_commands.c:3363
|
||||
Audio.playSong(BattleProfile.victorySong(bp, Battle.songInfo(st)))
|
||||
else
|
||||
-- pokefirered/src/battle_script_commands.c:3219
|
||||
local id = Audio.role("victoryWild")
|
||||
if id then Audio.playSong(id) end
|
||||
end
|
||||
end
|
||||
|
||||
local function begin_trainer_win(st)
|
||||
Battle._pendingEnd = "win"
|
||||
local Audio = require("src.core.game3.audio")
|
||||
local role, fallback
|
||||
if st and st.wild then
|
||||
role, fallback = "victoryWild", 311
|
||||
else
|
||||
role, fallback = Trainers.getVictoryMusicRole(st and st.trainerId)
|
||||
end
|
||||
local bp = BattleProfile.of(st)
|
||||
if bp.music then
|
||||
-- pokeemerald/src/battle_main.c:4983
|
||||
Audio.playSong(BattleProfile.victorySong(bp, Battle.songInfo(st)))
|
||||
-- pokefirered/src/battle_script_commands.c:3216
|
||||
elseif not st.pokedude then Audio.playSong(Audio.role(role) or fallback) end
|
||||
-- pokeemerald/src/battle_main.c:5011
|
||||
if st and not st.wild then
|
||||
if bp.music then
|
||||
-- pokeemerald/src/battle_main.c:4983
|
||||
Audio.playSong(BattleProfile.victorySong(bp, Battle.songInfo(st)))
|
||||
elseif not st.pokedude then
|
||||
local role, fallback = Trainers.getVictoryMusicRole(st.trainerId)
|
||||
Audio.playSong(Audio.role(role) or fallback)
|
||||
end
|
||||
end
|
||||
|
||||
local pname = st.playerName
|
||||
local twoTrainers = st.trainerB ~= nil
|
||||
@@ -1872,6 +1893,7 @@ local function handle_enemy_faint(opts)
|
||||
end
|
||||
end
|
||||
|
||||
wild_victory_song(st, awards)
|
||||
local hooks = choice_hooks()
|
||||
if st.pokedude then
|
||||
for _, entry in ipairs(awards) do
|
||||
@@ -3104,6 +3126,7 @@ function D.faintStep()
|
||||
and Experience.awardFoe(st, foe, { trainer = not st.wild }) or {}
|
||||
-- pokefirered/src/battle_util.c:1181
|
||||
State.opponentSwitchInResetSentPokes(st, foe)
|
||||
wild_victory_song(st, awards)
|
||||
if D.presentAwards(awards) then return end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,6 +26,11 @@ local MODULES = {
|
||||
oldMan = "src.core.game3.rse.old_man",
|
||||
dewfordTrend = "src.core.game3.rse.dewford_trend",
|
||||
lilycoveLady = "src.core.game3.rse.lilycove_lady",
|
||||
daycareMail = "src.core.game3.rse.daycare_mail_mix",
|
||||
tower = "src.core.game3.rse.frontier.tower",
|
||||
gift = "src.core.game3.rse.record_mixing_gift",
|
||||
apprentice = "src.core.game3.rse.frontier.apprentice",
|
||||
rankingHall = "src.core.game3.rse.frontier.ranking_hall",
|
||||
}
|
||||
|
||||
local function system(name)
|
||||
@@ -74,29 +79,37 @@ function RecordMix.fromWire(v)
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:220 PrepareExchangePacket
|
||||
function RecordMix.packet(session)
|
||||
function RecordMix.packet(session, multiplayerId)
|
||||
session = sessionOf(session)
|
||||
local sess = type(session) == "table" and session or {}
|
||||
local MixUtil = require("src.core.game3.rse.record_mix_util")
|
||||
local out = {
|
||||
version = type(session) == "table" and session.version or Family.activeVersion(),
|
||||
trainerId = tonumber(type(session) == "table" and (session.trainerId or session.id)) or 0,
|
||||
name = type(session) == "table" and (session.name or session.playerName) or nil,
|
||||
version = sess.version or Family.activeVersion(),
|
||||
trainerId = tonumber(sess.trainerId or sess.id) or 0,
|
||||
linkTrainerId = MixUtil.sessionLinkTrainerId(sess),
|
||||
language = MixUtil.GAME_LANGUAGE,
|
||||
name = sess.name or sess.playerName,
|
||||
}
|
||||
local tvData = call(system("tv"), "mixExport", session)
|
||||
out.secretBases = call(system("secretBase"), "mixExport", session)
|
||||
local Tv = system("tv")
|
||||
pcall(function() Tv.deactivateAllNormalShows(session) end)
|
||||
local tvData = call(Tv, "mixExport", session)
|
||||
if type(tvData) == "table" then
|
||||
out.tvShows, out.pokeNews = tvData.tvShows, tvData.pokeNews
|
||||
end
|
||||
out.secretBases = call(system("secretBase"), "mixExport", session)
|
||||
out.oldMan = call(system("oldMan"), "mixExport", session)
|
||||
out.dewfordTrends = call(system("dewfordTrend"), "mixExport", session)
|
||||
local lady = system("lilycoveLady")
|
||||
out.lilycoveLady = lady and deep(call(lady, "state", session)) or nil
|
||||
out.lilycoveLady = call(lady, "mixExport", session)
|
||||
if out.lilycoveLady == nil then out.lilycoveLady = deep(call(lady, "state", session)) end
|
||||
out.dewfordTrends = call(system("dewfordTrend"), "mixExport", session)
|
||||
out.daycareMail = call(system("daycareMail"), "mixExport", session)
|
||||
out.battleTowerRecord = call(system("tower"), "mixExport", session)
|
||||
out.giftItem = call(system("gift"), "mixExport", session, tonumber(multiplayerId) or 0) or 0
|
||||
out.apprentices = call(system("apprentice"), "mixExport", session)
|
||||
out.hallRecords = call(system("rankingHall"), "mixExport", session)
|
||||
return deep(out)
|
||||
end
|
||||
|
||||
local function tvModule()
|
||||
return system("tv")
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:253 ReceiveExchangePacket
|
||||
function RecordMix.receive(session, packets, myIndex, logger)
|
||||
session = sessionOf(session)
|
||||
@@ -104,12 +117,12 @@ function RecordMix.receive(session, packets, myIndex, logger)
|
||||
for i, p in ipairs(packets or {}) do players[i] = type(p) == "table" and deep(p) or {} end
|
||||
myIndex = tonumber(myIndex) or 1
|
||||
local applied = {}
|
||||
local sb = system("secretBase")
|
||||
local randSum = call(system("daycareMail"), "randSum", players[1] and players[1].tvShows) or 0
|
||||
local bases = {}
|
||||
for i, p in ipairs(players) do bases[i] = p.secretBases or {} end
|
||||
local _, okS = call(sb, "mixImport", session, bases)
|
||||
local _, okS = call(system("secretBase"), "mixImport", session, bases, myIndex)
|
||||
applied.secretBases = okS
|
||||
local Tv = tvModule()
|
||||
local Tv = system("tv")
|
||||
local tvPlayers = {}
|
||||
for i, p in ipairs(players) do
|
||||
tvPlayers[i] = { tvShows = p.tvShows or {}, pokeNews = p.pokeNews or {}, trainerId = p.trainerId }
|
||||
@@ -118,14 +131,20 @@ function RecordMix.receive(session, packets, myIndex, logger)
|
||||
applied.tvShows = okTv
|
||||
local _, okNews = call(Tv, "receivePokeNews", session, tvPlayers, myIndex)
|
||||
applied.pokeNews = okNews
|
||||
local oldMan = system("oldMan")
|
||||
local _, okOld = call(oldMan, "mixImport", players, session, myIndex)
|
||||
local _, okOld = call(system("oldMan"), "mixImport", players, session, myIndex)
|
||||
applied.oldMan = okOld
|
||||
if not okOld then rse().missing("oldMan", "ReceiveOldManData", logger) end
|
||||
local trends = {}
|
||||
for i, p in ipairs(players) do trends[i] = p.dewfordTrends or {} end
|
||||
local _, okDew = call(system("dewfordTrend"), "mixImport", trends, session)
|
||||
applied.dewfordTrends = okDew
|
||||
local _, okMail = call(system("daycareMail"), "mixImport", players, session, myIndex, randSum)
|
||||
applied.daycareMail = okMail
|
||||
local _, okTower = call(system("tower"), "mixImport", players, session, myIndex)
|
||||
applied.battleTower = okTower
|
||||
local gift, okGift = call(system("gift"), "mixImport", players, session, myIndex)
|
||||
applied.giftItem = okGift
|
||||
applied.gift = gift
|
||||
local lady = system("lilycoveLady")
|
||||
local _, okLady = call(lady, "mixImport", players, session, myIndex)
|
||||
if not okLady then
|
||||
@@ -134,7 +153,10 @@ function RecordMix.receive(session, packets, myIndex, logger)
|
||||
rse().missing("lilycoveLady", "ReceiveLilycoveLadyData", logger)
|
||||
end
|
||||
applied.lilycoveLady = okLady
|
||||
rse().missing("recordMixing", "ReceiveDaycareMailData/ReceiveBattleTowerData/ReceiveGiftItem/ReceiveApprenticeData/ReceiveRankingHallRecords", logger)
|
||||
local _, okApp = call(system("apprentice"), "mixImport", players, session, myIndex)
|
||||
applied.apprentices = okApp
|
||||
local _, okHall = call(system("rankingHall"), "mixImport", players, session, myIndex)
|
||||
applied.hallRecords = okHall
|
||||
RecordMix.last = { players = #players, myIndex = myIndex, applied = applied }
|
||||
return applied
|
||||
end
|
||||
@@ -178,7 +200,7 @@ function RecordMix.playerSpotTriggered(ctx, adapters)
|
||||
local spot = L.getVar(ctx, LB.VAR_0x8005)
|
||||
-- pokeemerald/src/record_mixing.c:321
|
||||
varSet(ctx, session, "VAR_TEMP_MIXED_RECORDS", 1)
|
||||
local mine = RecordMix.packet(session)
|
||||
local mine = RecordMix.packet(session, spot)
|
||||
mine.spot = spot
|
||||
lk:send({ type = RecordMix.MSG.PACKET, spot = spot, packet = RecordMix.toWire(mine) })
|
||||
RecordMix.state = "mixing"
|
||||
@@ -217,7 +239,13 @@ function RecordMix.playerSpotTriggered(ctx, adapters)
|
||||
packets[i] = row.packet
|
||||
if row.mine then myIndex = i end
|
||||
end
|
||||
RecordMix.receive(session, packets, myIndex, adapters and adapters.log)
|
||||
local applied = RecordMix.receive(session, packets, myIndex, adapters and adapters.log)
|
||||
local gift = applied and applied.gift
|
||||
if type(gift) == "table" and (tonumber(gift.item) or 0) ~= 0 then
|
||||
-- pokeemerald/src/record_mixing.c:976
|
||||
if adapters and adapters.setStringVar then adapters.setStringVar(1, tostring(gift.from or "")) end
|
||||
if ctx.stringVars then ctx.stringVars[1] = tostring(gift.from or "") end
|
||||
end
|
||||
-- pokeemerald/src/record_mixing.c:333
|
||||
flagSet(session, "FLAG_SYS_MIX_RECORD")
|
||||
message("gText_RecordMixingComplete")
|
||||
|
||||
@@ -34,5 +34,6 @@ return {
|
||||
{ name = "frontier", module = "src.core.game3.rse.frontier.util" },
|
||||
{ name = "trainerHill", module = "src.core.game3.rse.trainer_hill" },
|
||||
{ name = "apprentice", module = "src.core.game3.rse.frontier.apprentice" },
|
||||
{ name = "recordMixingGift", module = "src.core.game3.rse.record_mixing_gift" },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
local Rse = require("src.core.game3.rse.init")
|
||||
local MixUtil = require("src.core.game3.rse.record_mix_util")
|
||||
|
||||
local DaycareMail = {}
|
||||
|
||||
-- pokeemerald/include/constants/global.h:34
|
||||
DaycareMail.DAYCARE_MON_COUNT = 2
|
||||
-- pokeemerald/src/record_mixing.c:41
|
||||
DaycareMail.NUM_SWAP_COMBOS = 3
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:152
|
||||
local SWAP_3P = { { 0, 1 }, { 1, 2 }, { 2, 0 } }
|
||||
-- pokeemerald/src/record_mixing.c:159
|
||||
local SWAP_4P = { { 0, 1, 2, 3 }, { 0, 2, 1, 3 }, { 0, 3, 2, 1 } }
|
||||
|
||||
local function sessionOf(sess)
|
||||
return sess or Rse.session()
|
||||
end
|
||||
|
||||
local function daycare()
|
||||
return require("src.core.game3.daycare")
|
||||
end
|
||||
|
||||
local function blankMail()
|
||||
return { message = { itemId = 0 }, otName = "", monName = "", gameLanguage = 0, monLanguage = 0 }
|
||||
end
|
||||
|
||||
local function itemOf(mail)
|
||||
return tonumber(type(mail) == "table" and type(mail.message) == "table" and mail.message.itemId) or 0
|
||||
end
|
||||
|
||||
local function heldItem(mon)
|
||||
return tonumber(mon and (mon.heldItem or mon.item)) or 0
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:1366
|
||||
function DaycareMail.mixExport(sess)
|
||||
sess = sessionOf(sess)
|
||||
local Daycare = daycare()
|
||||
local dc = Daycare.stateOf(sess)
|
||||
local out = { mail = {}, numDaycareMons = 0, cantHoldItem = {} }
|
||||
local stored = dc and dc.mail or {}
|
||||
-- pokeemerald/src/daycare.c:122
|
||||
for i = 1, DaycareMail.DAYCARE_MON_COUNT do
|
||||
local m = stored[i]
|
||||
out.mail[i] = type(m) == "table" and MixUtil.deep(m) or blankMail()
|
||||
if type(out.mail[i].message) ~= "table" then out.mail[i].message = { itemId = 0 } end
|
||||
local mon = dc and Daycare.mon(dc, i)
|
||||
if mon and Daycare.speciesOf(mon) ~= 0 then
|
||||
out.numDaycareMons = out.numDaycareMons + 1
|
||||
out.cantHoldItem[i] = heldItem(mon) ~= 0
|
||||
else
|
||||
out.cantHoldItem[i] = true
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:743
|
||||
function DaycareMail.randSum(tvShows)
|
||||
local sum = 0
|
||||
if type(tvShows) == "table" then
|
||||
for i = 0, 7 do
|
||||
local show = tvShows[i]
|
||||
if type(show) == "table" then
|
||||
sum = sum + MixUtil.num(show.kind) + ((show.active == true or show.active == 1) and 1 or 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
return sum % 256
|
||||
end
|
||||
|
||||
local function recordOf(players, i)
|
||||
local p = players[i + 1]
|
||||
local r = type(p) == "table" and p.daycareMail or nil
|
||||
if type(r) ~= "table" then
|
||||
r = { mail = { blankMail(), blankMail() }, numDaycareMons = 0, cantHoldItem = { true, true } }
|
||||
if type(p) == "table" then p.daycareMail = r end
|
||||
end
|
||||
r.mail = r.mail or {}
|
||||
for j = 1, DaycareMail.DAYCARE_MON_COUNT do
|
||||
if type(r.mail[j]) ~= "table" then r.mail[j] = blankMail() end
|
||||
end
|
||||
r.cantHoldItem = r.cantHoldItem or {}
|
||||
return r
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:724
|
||||
local function swap(players, idxs, s1, s2)
|
||||
local a, b = idxs[s1 + 1], idxs[s2 + 1]
|
||||
local ra, rb = recordOf(players, a[1]), recordOf(players, b[1])
|
||||
local tmp = ra.mail[a[2] + 1]
|
||||
ra.mail[a[2] + 1] = rb.mail[b[2] + 1]
|
||||
rb.mail[b[2] + 1] = tmp
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:760
|
||||
function DaycareMail.mixImport(players, sess, myIndex, randSum)
|
||||
sess = sessionOf(sess)
|
||||
players = players or {}
|
||||
local Rng = require("src.core.game3.rng")
|
||||
local oldSeed = Rng.Random2()
|
||||
Rng.SeedRng2(MixUtil.linkTrainerId(players[1]) % 0x10000)
|
||||
local count = #players
|
||||
local canHold = {}
|
||||
for i = 0, count - 1 do
|
||||
canHold[i] = { false, false }
|
||||
local r = recordOf(players, i)
|
||||
local n = MixUtil.num(r.numDaycareMons)
|
||||
for j = 1, math.min(n, DaycareMail.DAYCARE_MON_COUNT) do
|
||||
local cant = r.cantHoldItem[j]
|
||||
if not (cant == true or cant == 1) then canHold[i][j] = true end
|
||||
end
|
||||
end
|
||||
local idxs, numCanHold = {}, 0
|
||||
for i = 0, count - 1 do
|
||||
local r = recordOf(players, i)
|
||||
local c0, c1 = canHold[i][1], canHold[i][2]
|
||||
if c0 or c1 then numCanHold = numCanHold + 1 end
|
||||
if c0 and not c1 then
|
||||
idxs[#idxs + 1] = { i, 0 }
|
||||
elseif c1 and not c0 then
|
||||
idxs[#idxs + 1] = { i, 1 }
|
||||
elseif c0 and c1 then
|
||||
local item1, item2 = itemOf(r.mail[1]), itemOf(r.mail[2])
|
||||
local slot
|
||||
if (item1 == 0 and item2 == 0) or (item1 ~= 0 and item2 ~= 0) then
|
||||
slot = Rng.Random2() % 2
|
||||
elseif item1 ~= 0 then
|
||||
slot = 0
|
||||
else
|
||||
slot = 1
|
||||
end
|
||||
idxs[#idxs + 1] = { i, slot }
|
||||
end
|
||||
end
|
||||
local tableId = (tonumber(randSum) or 0) % DaycareMail.NUM_SWAP_COMBOS
|
||||
if numCanHold == 2 then
|
||||
swap(players, idxs, 0, 1)
|
||||
elseif numCanHold == 3 then
|
||||
swap(players, idxs, SWAP_3P[tableId + 1][1], SWAP_3P[tableId + 1][2])
|
||||
elseif numCanHold == 4 then
|
||||
local row = SWAP_4P[tableId + 1]
|
||||
swap(players, idxs, row[1], row[2])
|
||||
swap(players, idxs, row[3], row[4])
|
||||
end
|
||||
local mine = recordOf(players, (tonumber(myIndex) or 1) - 1)
|
||||
local dc = daycare().stateOf(sess)
|
||||
if dc then
|
||||
dc.mail = dc.mail or {}
|
||||
for j = 1, DaycareMail.DAYCARE_MON_COUNT do
|
||||
local m = mine.mail[j]
|
||||
dc.mail[j] = itemOf(m) ~= 0 and MixUtil.deep(m) or nil
|
||||
end
|
||||
end
|
||||
Rng.SeedRng(oldSeed)
|
||||
return true
|
||||
end
|
||||
|
||||
Rse.register("daycareMail", DaycareMail)
|
||||
|
||||
return DaycareMail
|
||||
@@ -655,6 +655,84 @@ local function easyChatType()
|
||||
end
|
||||
easyChatType()
|
||||
|
||||
local function mixUtil()
|
||||
return require("src.core.game3.rse.record_mix_util")
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:1062
|
||||
function Apprentice.mixExport(sess)
|
||||
sess = sess or Rse.session()
|
||||
local MixUtil = mixUtil()
|
||||
local src = Apprentice.saved(sess)
|
||||
local saveId = tonumber(Apprentice.player(sess).saveId) or 0
|
||||
local dst = { Apprentice.copy(src[1]), Apprentice.newSaved() }
|
||||
dst[2].playerName = ""
|
||||
local mine = MixUtil.sessionLinkTrainerId(sess)
|
||||
local slots = Apprentice.COUNT - 1
|
||||
local oldId, numOld, mixId, numMix = 0, 0, 0, 0
|
||||
for i = 0, 1 do
|
||||
local id = (i + saveId) % slots + 1
|
||||
if (src[id + 1].playerName or "") ~= "" then
|
||||
local tid = MixUtil.getTrainerId(src[id + 1].playerId)
|
||||
if tid ~= mine then numMix, mixId = numMix + 1, id end
|
||||
if tid == mine then numOld, oldId = numOld + 1, id end
|
||||
end
|
||||
end
|
||||
if numMix == 0 and numOld ~= 0 then numMix, mixId = numOld, oldId end
|
||||
if numMix == 1 then
|
||||
dst[2] = Apprentice.copy(src[mixId + 1])
|
||||
elseif numMix == 2 then
|
||||
if rng().Random2() > 0x3333 then
|
||||
dst[2] = Apprentice.copy(src[saveId + 1 + 1])
|
||||
else
|
||||
dst[2] = Apprentice.copy(src[((saveId + 1) % slots + 1) + 1])
|
||||
end
|
||||
end
|
||||
return dst
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:1155
|
||||
local function alreadySaved(a, saved)
|
||||
local MixUtil = mixUtil()
|
||||
for i = 1, Apprentice.COUNT do
|
||||
if MixUtil.getTrainerId(a.playerId) == MixUtil.getTrainerId(saved[i].playerId)
|
||||
and (tonumber(a.number) or 0) == (tonumber(saved[i].number) or 0) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:1169
|
||||
function Apprentice.mixImport(players, sess, myIndex)
|
||||
sess = sess or Rse.session()
|
||||
local partner = mixUtil().partner(players or {}, tonumber(myIndex) or 1)
|
||||
local mix = type(partner) == "table" and partner.apprentices or nil
|
||||
if type(mix) ~= "table" then return false end
|
||||
local saved = Apprentice.saved(sess)
|
||||
local p = Apprentice.player(sess)
|
||||
local slots = Apprentice.COUNT - 1
|
||||
local num, which = 0, 0
|
||||
for i = 0, 1 do
|
||||
local a = mix[i + 1]
|
||||
if type(a) == "table" and (a.playerName or "") ~= "" and not alreadySaved(a, saved) then
|
||||
num, which = num + 1, i
|
||||
end
|
||||
end
|
||||
local saveId = tonumber(p.saveId) or 0
|
||||
if num == 1 then
|
||||
saved[saveId + 1 + 1] = Apprentice.copy(mix[which + 1])
|
||||
p.saveId = (saveId + 1) % slots
|
||||
elseif num == 2 then
|
||||
for i = 0, 1 do
|
||||
local idx = (bit.bxor(i, 1) + saveId) % slots + 1
|
||||
saved[idx + 1] = Apprentice.copy(mix[i + 1])
|
||||
end
|
||||
p.saveId = (saveId + 2) % slots
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
Rse.register("apprentice", Apprentice)
|
||||
|
||||
return Apprentice
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
local Rse = require("src.core.game3.rse.init")
|
||||
local D = require("src.core.game3.rse.frontier.trainers")
|
||||
local Util = require("src.core.game3.rse.frontier.util")
|
||||
local MixUtil = require("src.core.game3.rse.record_mix_util")
|
||||
|
||||
local Hall = {}
|
||||
|
||||
local RH = Util.RANKING_HALL
|
||||
local M = D.MODE
|
||||
local COUNT = Util.HALL_RECORDS_COUNT
|
||||
|
||||
local function lvlModes()
|
||||
return D.LVL_MODE_COUNT
|
||||
end
|
||||
|
||||
local function new1P()
|
||||
return { id = { 0, 0, 0, 0 }, name = "", language = 0, winStreak = 0 }
|
||||
end
|
||||
|
||||
local function new2P()
|
||||
return { id1 = { 0, 0, 0, 0 }, id2 = { 0, 0, 0, 0 }, name1 = "", name2 = "", language = 0, winStreak = 0 }
|
||||
end
|
||||
|
||||
function Hall.saved(sess)
|
||||
sess = sess or Rse.session()
|
||||
if type(sess.hallRecords1P) ~= "table" or type(sess.hallRecords2P) ~= "table" then
|
||||
Util.clearRankingHallRecords(sess)
|
||||
end
|
||||
for i = 1, Util.HALL_FACILITIES_COUNT do
|
||||
sess.hallRecords1P[i] = sess.hallRecords1P[i] or {}
|
||||
for j = 1, lvlModes() do
|
||||
sess.hallRecords1P[i][j] = sess.hallRecords1P[i][j] or {}
|
||||
for k = 1, COUNT do sess.hallRecords1P[i][j][k] = sess.hallRecords1P[i][j][k] or new1P() end
|
||||
end
|
||||
end
|
||||
for j = 1, lvlModes() do
|
||||
sess.hallRecords2P[j] = sess.hallRecords2P[j] or {}
|
||||
for k = 1, COUNT do sess.hallRecords2P[j][k] = sess.hallRecords2P[j][k] or new2P() end
|
||||
end
|
||||
return sess.hallRecords1P, sess.hallRecords2P
|
||||
end
|
||||
|
||||
local function streak(r)
|
||||
return tonumber(type(r) == "table" and r.winStreak) or 0
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:1116
|
||||
function Hall.mixExport(sess)
|
||||
sess = sess or Rse.session()
|
||||
local f = Util.frontier(sess)
|
||||
local id = MixUtil.trainerIdBytes(sess)
|
||||
local name = tostring(sess.name or sess.playerName or "")
|
||||
local out = { onePlayer = {}, twoPlayers = {} }
|
||||
for i = 1, Util.HALL_FACILITIES_COUNT do
|
||||
out.onePlayer[i] = {}
|
||||
for j = 1, lvlModes() do
|
||||
out.onePlayer[i][j] = { id = MixUtil.deep(id), language = MixUtil.GAME_LANGUAGE, name = name, winStreak = 0 }
|
||||
end
|
||||
end
|
||||
for j = 1, lvlModes() do
|
||||
out.twoPlayers[j] = {
|
||||
language = MixUtil.GAME_LANGUAGE, id1 = MixUtil.deep(id),
|
||||
id2 = MixUtil.deep(f.opponentTrainerIds and f.opponentTrainerIds[j] or { 0, 0, 0, 0 }),
|
||||
name1 = name, name2 = tostring(f.opponentNames and f.opponentNames[j] or ""), winStreak = 0,
|
||||
}
|
||||
end
|
||||
for lvl = 0, lvlModes() - 1 do
|
||||
local one = function(hall) return out.onePlayer[hall + 1][lvl + 1] end
|
||||
one(RH.TOWER_SINGLES).winStreak = Util.get2(f.towerRecordWinStreaks, M.SINGLES, lvl)
|
||||
one(RH.TOWER_DOUBLES).winStreak = Util.get2(f.towerRecordWinStreaks, M.DOUBLES, lvl)
|
||||
one(RH.TOWER_MULTIS).winStreak = Util.get2(f.towerRecordWinStreaks, M.MULTIS, lvl)
|
||||
one(RH.DOME).winStreak = Util.get2(f.domeRecordWinStreaks, M.SINGLES, lvl)
|
||||
one(RH.PALACE).winStreak = Util.get2(f.palaceRecordWinStreaks, M.SINGLES, lvl)
|
||||
one(RH.ARENA).winStreak = Util.get1(f.arenaRecordStreaks, lvl)
|
||||
one(RH.FACTORY).winStreak = Util.get2(f.factoryRecordWinStreaks, M.SINGLES, lvl)
|
||||
one(RH.PIKE).winStreak = Util.get1(f.pikeRecordStreaks, lvl)
|
||||
one(RH.PYRAMID).winStreak = Util.get1(f.pyramidRecordStreaks, lvl)
|
||||
out.twoPlayers[lvl + 1].winStreak = Util.get2(f.towerRecordWinStreaks, M.LINK_MULTIS, lvl)
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function tid(bytes)
|
||||
return MixUtil.getTrainerId(bytes)
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:1207
|
||||
local function getNewHallRecords(players, myIndex, sess)
|
||||
local saved1, saved2 = Hall.saved(sess)
|
||||
local partners = {}
|
||||
for i = 1, #players do
|
||||
if i ~= myIndex then
|
||||
local h = players[i] and players[i].hallRecords
|
||||
partners[#partners + 1] = type(h) == "table" and h or {}
|
||||
end
|
||||
if #partners == COUNT then break end
|
||||
end
|
||||
local n = #players - 1
|
||||
local mix1, mix2 = {}, {}
|
||||
for i = 1, Util.HALL_FACILITIES_COUNT do
|
||||
mix1[i] = {}
|
||||
for j = 1, lvlModes() do
|
||||
local dst = {}
|
||||
for k = 1, COUNT * 2 do dst[k] = new1P() end
|
||||
for k = 1, COUNT do dst[k] = MixUtil.deep(saved1[i][j][k]) end
|
||||
for k = 1, n do
|
||||
local rec = partners[k] and partners[k].onePlayer and partners[k].onePlayer[i] and partners[k].onePlayer[i][j]
|
||||
rec = type(rec) == "table" and rec or new1P()
|
||||
local repeats = 0
|
||||
for l = 1, COUNT do
|
||||
if tid(dst[l].id) == tid(rec.id) then
|
||||
repeats = repeats + 1
|
||||
if streak(dst[l]) < streak(rec) then dst[l] = MixUtil.deep(rec) end
|
||||
end
|
||||
end
|
||||
if repeats == 0 then dst[k + COUNT] = MixUtil.deep(rec) end
|
||||
end
|
||||
mix1[i][j] = dst
|
||||
end
|
||||
end
|
||||
for j = 1, lvlModes() do
|
||||
local dst = {}
|
||||
for k = 1, COUNT * 2 do dst[k] = new2P() end
|
||||
for k = 1, COUNT do dst[k] = MixUtil.deep(saved2[j][k]) end
|
||||
for k = 1, n do
|
||||
local rec = partners[k] and partners[k].twoPlayers and partners[k].twoPlayers[j]
|
||||
rec = type(rec) == "table" and rec or new2P()
|
||||
local repeats = 0
|
||||
for l = 1, COUNT do
|
||||
if tid(dst[l].id1) == tid(rec.id1) and tid(dst[l].id2) == tid(rec.id2) then
|
||||
repeats = repeats + 1
|
||||
if streak(dst[l]) < streak(rec) then dst[l] = MixUtil.deep(rec) end
|
||||
end
|
||||
end
|
||||
if repeats == 0 then dst[k + COUNT] = MixUtil.deep(rec) end
|
||||
end
|
||||
mix2[j] = dst
|
||||
end
|
||||
return mix1, mix2
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:1286
|
||||
local function fillWinStreakRecords(playerRecords, mixRecords)
|
||||
for i = 1, COUNT do
|
||||
local highest, highestId = 0, nil
|
||||
for j = 1, COUNT * 2 do
|
||||
if streak(mixRecords[j]) > highest then
|
||||
highestId, highest = j, streak(mixRecords[j])
|
||||
end
|
||||
end
|
||||
if highestId then
|
||||
playerRecords[i] = MixUtil.deep(mixRecords[highestId])
|
||||
mixRecords[highestId].winStreak = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:1355
|
||||
function Hall.mixImport(players, sess, myIndex)
|
||||
sess = sess or Rse.session()
|
||||
players = players or {}
|
||||
local mix1, mix2 = getNewHallRecords(players, tonumber(myIndex) or 1, sess)
|
||||
-- pokeemerald/src/record_mixing.c:1342
|
||||
local saved1, saved2 = Hall.saved(sess)
|
||||
for i = 1, Util.HALL_FACILITIES_COUNT do
|
||||
for j = 1, lvlModes() do fillWinStreakRecords(saved1[i][j], mix1[i][j]) end
|
||||
end
|
||||
for j = 1, lvlModes() do fillWinStreakRecords(saved2[j], mix2[j]) end
|
||||
return true
|
||||
end
|
||||
|
||||
Rse.register("rankingHall", Hall)
|
||||
|
||||
return Hall
|
||||
@@ -643,6 +643,96 @@ function Tower.doTowerBattle(ctx, adapters, sess)
|
||||
})
|
||||
end
|
||||
|
||||
-- pokeemerald/include/constants/global.h:97
|
||||
local PLAYER_NAME_LENGTH = 7
|
||||
|
||||
local function nameChar(s, i)
|
||||
return tostring(s or ""):sub(i + 1, i + 1)
|
||||
end
|
||||
|
||||
local function idByte(rec, i)
|
||||
return tonumber(type(rec.trainerId) == "table" and rec.trainerId[i + 1]) or 0
|
||||
end
|
||||
|
||||
-- pokeemerald/src/battle_tower.c:1311
|
||||
function Tower.putNewRecord(sess, newRecord)
|
||||
local f = Util.frontier(sess)
|
||||
local records = f.towerRecords
|
||||
local count = Util.BATTLE_TOWER_RECORD_COUNT
|
||||
for i = 1, count do
|
||||
if type(records[i]) ~= "table" then records[i] = {} end
|
||||
end
|
||||
local function streak(i) return tonumber(records[i + 1].winStreak) or 0 end
|
||||
local i = 0
|
||||
while i < count do
|
||||
local rec, k, j = records[i + 1], 0, 0
|
||||
while j < 4 and idByte(rec, j) == idByte(newRecord, j) do j = j + 1 end
|
||||
if j == 4 then
|
||||
while k < PLAYER_NAME_LENGTH do
|
||||
if nameChar(rec.name, j) ~= nameChar(newRecord.name, j) then break end
|
||||
if nameChar(newRecord.name, j) == "" then
|
||||
k = PLAYER_NAME_LENGTH
|
||||
break
|
||||
end
|
||||
k = k + 1
|
||||
end
|
||||
end
|
||||
if k == PLAYER_NAME_LENGTH then break end
|
||||
i = i + 1
|
||||
end
|
||||
if i < count then
|
||||
records[i + 1] = Util.deepCopy(newRecord)
|
||||
return i
|
||||
end
|
||||
for s = 0, count - 1 do
|
||||
if streak(s) == 0 then
|
||||
records[s + 1] = Util.deepCopy(newRecord)
|
||||
return s
|
||||
end
|
||||
end
|
||||
local slotValues, slotIds = { streak(0) }, { 0 }
|
||||
for s = 1, count - 1 do
|
||||
local j = 1
|
||||
while j <= #slotValues do
|
||||
if streak(s) < slotValues[j] then
|
||||
slotValues, slotIds = { streak(s) }, { s }
|
||||
j = 1
|
||||
break
|
||||
elseif streak(s) > slotValues[j] then
|
||||
break
|
||||
end
|
||||
j = j + 1
|
||||
end
|
||||
if j == #slotValues + 1 then
|
||||
slotValues[#slotValues + 1] = streak(s)
|
||||
slotIds[#slotIds + 1] = s
|
||||
end
|
||||
end
|
||||
local pick = slotIds[(D.rng().Random() % #slotIds) + 1]
|
||||
records[pick + 1] = Util.deepCopy(newRecord)
|
||||
return pick
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:1396
|
||||
function Tower.mixExport(sess)
|
||||
local rec = Util.deepCopy(Util.frontier(sess).towerPlayer or {})
|
||||
return rec
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:650
|
||||
function Tower.mixImport(players, sess, myIndex)
|
||||
sess = sess or Rse.session()
|
||||
local MixUtil = require("src.core.game3.rse.record_mix_util")
|
||||
local partner = MixUtil.partner(players or {}, tonumber(myIndex) or 1)
|
||||
local rec = type(partner) == "table" and partner.battleTowerRecord or nil
|
||||
if type(rec) ~= "table" then return false end
|
||||
rec = Util.deepCopy(rec)
|
||||
local me = players[tonumber(myIndex) or 1]
|
||||
if type(me) == "table" then me.battleTowerRecord = rec end
|
||||
Tower.putNewRecord(sess, rec)
|
||||
return true
|
||||
end
|
||||
|
||||
Rse.register("tower", Tower)
|
||||
|
||||
return Tower
|
||||
|
||||
@@ -339,6 +339,42 @@ function Lady.contestLadyTvData(sess)
|
||||
}
|
||||
end
|
||||
|
||||
function Lady.mixExport(sess)
|
||||
local MixUtil = require("src.core.game3.rse.record_mix_util")
|
||||
return MixUtil.deep(Lady.state(sess))
|
||||
end
|
||||
|
||||
-- pokeemerald/src/lilycove_lady.c:577
|
||||
local function quizClearQuestionForRecordMix(prev, sess)
|
||||
local l = Lady.state(sess)
|
||||
local count = #data().quizQuestions
|
||||
local prevId = tonumber(prev.quiz and prev.quiz.prevQuestionId) or count
|
||||
if prevId < count and l.id == Lady.QUIZ then
|
||||
local q = l.quiz
|
||||
for _ = 1, 4 do
|
||||
if prevId ~= q.questionId then break end
|
||||
q.questionId = Town.random() % count
|
||||
end
|
||||
if prevId == q.questionId then q.questionId = (q.questionId + 1) % count end
|
||||
q.prevQuestionId = prevId
|
||||
end
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:682
|
||||
function Lady.mixImport(players, sess, myIndex)
|
||||
sess = Town.session(sess)
|
||||
local MixUtil = require("src.core.game3.rse.record_mix_util")
|
||||
local partner = MixUtil.partner(players or {}, tonumber(myIndex) or 1)
|
||||
local src = type(partner) == "table" and partner.lilycoveLady or nil
|
||||
if type(src) ~= "table" or src.id == nil then return false end
|
||||
local mine = Lady.state(sess)
|
||||
local prev = mine.id == Lady.QUIZ and MixUtil.deep(mine) or nil
|
||||
sess.lilycoveLady = MixUtil.deep(src)
|
||||
Lady.resetForRecordMix(sess)
|
||||
if prev then quizClearQuestionForRecordMix(prev, sess) end
|
||||
return true
|
||||
end
|
||||
|
||||
local SaveSections = require("src.core.game3.save_sections")
|
||||
-- pokeemerald/src/new_game.c:199
|
||||
SaveSections.register("lilycoveLady", SaveSections.fields({ "lilycoveLady" }, function(sess) Lady.init(sess) end))
|
||||
|
||||
@@ -361,6 +361,31 @@ function OldMan.mixExport(sess)
|
||||
return deep(OldMan.state(sess))
|
||||
end
|
||||
|
||||
-- pokeemerald/src/mauville_old_man.c:858
|
||||
local function sanitizeReceived(m, language)
|
||||
if m.id ~= OldMan.STORYTELLER or language ~= 1 then return end
|
||||
for i = 1, OldMan.NUM_STORYTELLER_TALES do
|
||||
if (tonumber(m.gameStatIDs and m.gameStatIDs[i]) or 0) ~= 0 then
|
||||
m.language = m.language or {}
|
||||
m.language[i] = Town.GAME_LANGUAGE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:629
|
||||
function OldMan.mixImport(players, sess, myIndex)
|
||||
sess = Town.session(sess)
|
||||
local MixUtil = require("src.core.game3.rse.record_mix_util")
|
||||
local partner = MixUtil.partner(players or {}, tonumber(myIndex) or 1)
|
||||
local m = type(partner) == "table" and partner.oldMan or nil
|
||||
if type(m) ~= "table" or m.id == nil then return false end
|
||||
m = MixUtil.deep(m)
|
||||
sanitizeReceived(m, tonumber(partner.language) or Town.GAME_LANGUAGE)
|
||||
sess.oldMan = m
|
||||
OldMan.resetFlag(sess)
|
||||
return true
|
||||
end
|
||||
|
||||
local SaveSections = require("src.core.game3.save_sections")
|
||||
-- pokeemerald/src/new_game.c:191
|
||||
SaveSections.register("oldMan", SaveSections.fields({ "oldMan" }, function(sess) OldMan.set(sess) end))
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
local MixUtil = {}
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:130
|
||||
local ORDERS = {
|
||||
[2] = { { 1, 0 } },
|
||||
[3] = { { 1, 2, 0 }, { 2, 0, 1 } },
|
||||
[4] = {
|
||||
{ 1, 0, 3, 2 }, { 3, 0, 1, 2 }, { 2, 0, 3, 1 },
|
||||
{ 1, 3, 0, 2 }, { 2, 3, 0, 1 }, { 3, 2, 0, 1 },
|
||||
{ 1, 2, 3, 0 }, { 2, 3, 1, 0 }, { 3, 2, 1, 0 },
|
||||
},
|
||||
}
|
||||
MixUtil.ORDERS = ORDERS
|
||||
|
||||
-- pokeemerald/include/constants/global.h:20
|
||||
MixUtil.LANGUAGE_JAPANESE = 1
|
||||
MixUtil.GAME_LANGUAGE = 2
|
||||
|
||||
function MixUtil.deep(v)
|
||||
if type(v) ~= "table" then return v end
|
||||
local out = {}
|
||||
for k, x in pairs(v) do out[k] = MixUtil.deep(x) end
|
||||
return out
|
||||
end
|
||||
|
||||
function MixUtil.num(v)
|
||||
return tonumber(v) or 0
|
||||
end
|
||||
|
||||
-- pokeemerald/src/link.c:326
|
||||
function MixUtil.linkTrainerId(p)
|
||||
if type(p) ~= "table" then return 0 end
|
||||
local full = tonumber(p.linkTrainerId)
|
||||
if full then return full end
|
||||
return (tonumber(p.trainerId) or 0) % 0x10000
|
||||
end
|
||||
|
||||
function MixUtil.sessionLinkTrainerId(sess)
|
||||
if type(sess) ~= "table" then return 0 end
|
||||
local tid = math.floor(tonumber(sess.trainerId or sess.playerId) or 0) % 0x10000
|
||||
local sid = math.floor(tonumber(sess.secretId) or 0) % 0x10000
|
||||
return tid + sid * 0x10000
|
||||
end
|
||||
|
||||
function MixUtil.trainerIdBytes(sess)
|
||||
local full = MixUtil.sessionLinkTrainerId(sess)
|
||||
return { full % 256, math.floor(full / 256) % 256, math.floor(full / 65536) % 256, math.floor(full / 16777216) % 256 }
|
||||
end
|
||||
|
||||
-- pokeemerald/src/new_game.c:72
|
||||
function MixUtil.getTrainerId(bytes)
|
||||
if type(bytes) ~= "table" then return tonumber(bytes) or 0 end
|
||||
return MixUtil.num(bytes[1]) + MixUtil.num(bytes[2]) * 0x100 + MixUtil.num(bytes[3]) * 0x10000
|
||||
+ MixUtil.num(bytes[4]) * 0x1000000
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:604
|
||||
function MixUtil.shuffle(players)
|
||||
local count = #players
|
||||
local out = {}
|
||||
local rows = ORDERS[count]
|
||||
if not rows then
|
||||
for i = 1, count do out[i] = i end
|
||||
return out
|
||||
end
|
||||
local row = rows[(MixUtil.linkTrainerId(players[1]) % #rows) + 1]
|
||||
for i = 1, count do out[i] = row[i] + 1 end
|
||||
return out
|
||||
end
|
||||
|
||||
function MixUtil.partner(players, myIndex)
|
||||
local idx = MixUtil.shuffle(players)
|
||||
local from = idx[myIndex] or myIndex
|
||||
return players[from], from
|
||||
end
|
||||
|
||||
return MixUtil
|
||||
@@ -0,0 +1,116 @@
|
||||
local Rse = require("src.core.game3.rse.init")
|
||||
|
||||
local Gift = {}
|
||||
|
||||
local function sessionOf(sess)
|
||||
return sess or Rse.session()
|
||||
end
|
||||
|
||||
local function blank()
|
||||
return { unk0 = 0, quantity = 0, itemId = 0, checksum = 0 }
|
||||
end
|
||||
|
||||
function Gift.state(sess)
|
||||
sess = sessionOf(sess)
|
||||
if type(sess.recordMixingGift) ~= "table" then sess.recordMixingGift = blank() end
|
||||
return sess.recordMixingGift
|
||||
end
|
||||
|
||||
-- pokeemerald/src/mystery_event_script.c:103
|
||||
function Gift.checksum(g)
|
||||
local item = tonumber(g.itemId) or 0
|
||||
return (tonumber(g.unk0) or 0) % 256 + (tonumber(g.quantity) or 0) % 256 + item % 256 + math.floor(item / 256) % 256
|
||||
end
|
||||
|
||||
-- pokeemerald/src/mystery_event_script.c:115
|
||||
local function isValid(g)
|
||||
local sum = Gift.checksum(g)
|
||||
return (tonumber(g.unk0) or 0) ~= 0 and (tonumber(g.quantity) or 0) ~= 0 and (tonumber(g.itemId) or 0) ~= 0
|
||||
and sum ~= 0 and sum == (tonumber(g.checksum) or 0)
|
||||
end
|
||||
|
||||
-- pokeemerald/src/mystery_event_script.c:130
|
||||
function Gift.clear(sess)
|
||||
sess = sessionOf(sess)
|
||||
sess.recordMixingGift = blank()
|
||||
end
|
||||
|
||||
-- pokeemerald/src/mystery_event_script.c:135
|
||||
function Gift.set(unk, quantity, itemId, sess)
|
||||
sess = sessionOf(sess)
|
||||
if (tonumber(unk) or 0) == 0 or (tonumber(quantity) or 0) == 0 or (tonumber(itemId) or 0) == 0 then
|
||||
Gift.clear(sess)
|
||||
return
|
||||
end
|
||||
local g = Gift.state(sess)
|
||||
g.unk0, g.quantity, g.itemId = tonumber(unk), tonumber(quantity), tonumber(itemId)
|
||||
g.checksum = Gift.checksum(g)
|
||||
end
|
||||
|
||||
-- pokeemerald/src/mystery_event_script.c:150
|
||||
function Gift.take(sess)
|
||||
sess = sessionOf(sess)
|
||||
local g = Gift.state(sess)
|
||||
if not isValid(g) then
|
||||
Gift.clear(sess)
|
||||
return 0
|
||||
end
|
||||
local itemId = tonumber(g.itemId) or 0
|
||||
g.quantity = g.quantity - 1
|
||||
if g.quantity == 0 then Gift.clear(sess) else g.checksum = Gift.checksum(g) end
|
||||
return itemId
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:246
|
||||
function Gift.mixExport(sess, multiplayerId)
|
||||
if (tonumber(multiplayerId) or 0) ~= 0 then return 0 end
|
||||
return Gift.take(sess)
|
||||
end
|
||||
|
||||
local function constant(sess, kind, name)
|
||||
local Constants = require("src.core.game3.constants")
|
||||
local ok, v = pcall(function() return Constants.of(Constants.versionOf(sess)):require(kind, name) end)
|
||||
return ok and v or nil
|
||||
end
|
||||
|
||||
local function pcHas(sess, item)
|
||||
local okS, Storage = pcall(require, "src.core.game3.storage")
|
||||
local storage = okS and Storage.ensure(sess) or nil
|
||||
for _, entry in ipairs(storage and storage.items or {}) do
|
||||
if (tostring(entry.id) == tostring(item) or tonumber(entry.id) == item) and (tonumber(entry.qty) or 0) >= 1 then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- pokeemerald/src/record_mixing.c:969
|
||||
function Gift.mixImport(players, sess, myIndex)
|
||||
sess = sessionOf(sess)
|
||||
players = players or {}
|
||||
local multiplayerId = (tonumber(myIndex) or 1) - 1
|
||||
local p0 = players[1] or {}
|
||||
local item = tonumber(p0.giftItem) or 0
|
||||
local ItemsData = require("src.core.game3.items_data")
|
||||
if multiplayerId == 0 or item == 0 or ItemsData.pocketOf(item) ~= "KEY_ITEMS" then return nil end
|
||||
local Bag = require("src.core.game3.bag")
|
||||
sess.bag = sess.bag or Bag.new()
|
||||
local got = not Bag.has(sess.bag, item, 1) and not pcHas(sess, item) and Bag.add(sess.bag, item, 1)
|
||||
if got then
|
||||
pcall(Rse.setVar, "VAR_TEMP_RECORD_MIX_GIFT_ITEM", item, sess)
|
||||
if item == constant(sess, "items", "ITEM_EON_TICKET") then
|
||||
pcall(Rse.setFlag, "FLAG_ENABLE_SHIP_SOUTHERN_ISLAND", true, sess)
|
||||
end
|
||||
return { item = item, from = p0.name or "" }
|
||||
end
|
||||
pcall(Rse.setVar, "VAR_TEMP_RECORD_MIX_GIFT_ITEM", 0, sess)
|
||||
return { item = 0 }
|
||||
end
|
||||
|
||||
local SaveSections = require("src.core.game3.save_sections")
|
||||
-- pokeemerald/include/global.h:1070
|
||||
SaveSections.register("recordMixingGift", SaveSections.fields({ "recordMixingGift" }, function(sess) Gift.clear(sess) end))
|
||||
|
||||
Rse.register("recordMixingGift", Gift)
|
||||
|
||||
return Gift
|
||||
@@ -1181,14 +1181,249 @@ function SB.setUpFieldMove(ctx)
|
||||
return { ok = true, action = "secret_power", secretPowerScript = label }
|
||||
end
|
||||
|
||||
function SB.mixExport(sess)
|
||||
return {}
|
||||
local function copyBase(b)
|
||||
local out = {}
|
||||
for k, v in pairs(b) do
|
||||
if type(v) == "table" then
|
||||
local t = {}
|
||||
for k2, v2 in pairs(v) do
|
||||
if type(v2) == "table" then
|
||||
local u = {}
|
||||
for k3, v3 in pairs(v2) do u[k3] = v3 end
|
||||
t[k2] = u
|
||||
else
|
||||
t[k2] = v2
|
||||
end
|
||||
end
|
||||
out[k] = t
|
||||
else
|
||||
out[k] = v
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
function SB.mixImport(sess, records)
|
||||
-- pokeemerald/src/record_mixing.c:222
|
||||
function SB.mixExport(sess)
|
||||
sess = session(sess)
|
||||
pcall(SB.setPlayerParty, sess)
|
||||
local out = {}
|
||||
for i, b in ipairs(SB.bases(sess)) do out[i] = copyBase(b) end
|
||||
return out
|
||||
end
|
||||
|
||||
local function on(v)
|
||||
return v == true or v == 1
|
||||
end
|
||||
|
||||
local function nameChars(s)
|
||||
return tostring(s or ""):sub(1, SB.PLAYER_NAME_LENGTH)
|
||||
end
|
||||
|
||||
local function sameTrainerId(a, b)
|
||||
for i = 1, SB.TRAINER_ID_LENGTH do
|
||||
if (tonumber(a.trainerId[i]) or 0) ~= (tonumber(b.trainerId[i]) or 0) then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- pokeemerald/src/secret_base.c:1383
|
||||
local function belongToSamePlayer(a, b)
|
||||
return (a.gender or 0) == (b.gender or 0) and sameTrainerId(a, b) and nameChars(a.trainerName) == nameChars(b.trainerName)
|
||||
end
|
||||
|
||||
-- pokeemerald/src/secret_base.c:1519
|
||||
local function belongsToPlayer(b, sess)
|
||||
if (b.secretBaseId or 0) == 0 then return false end
|
||||
if b.gender ~= playerGender(sess) then return false end
|
||||
if not sameTrainerId(b, { trainerId = trainerIdBytes(sess) }) then return false end
|
||||
return nameChars(b.trainerName) == nameChars(sess.name or sess.playerName)
|
||||
end
|
||||
|
||||
-- pokeemerald/src/secret_base.c:1395
|
||||
local function indexFromId(saved, id)
|
||||
for i = 0, SB.COUNT - 1 do
|
||||
if saved[i + 1].secretBaseId == id then return i end
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
-- pokeemerald/src/secret_base.c:1336
|
||||
local function saveBase(saved, idx, b)
|
||||
saved[idx + 1] = normalize(copyBase(b))
|
||||
saved[idx + 1].registryStatus = SB.NEW
|
||||
end
|
||||
|
||||
-- pokeemerald/src/secret_base.c:1432
|
||||
local function trySaveFriendsBase(saved, b)
|
||||
if (b.secretBaseId or 0) == 0 then return 0 end
|
||||
local index = indexFromId(saved, b.secretBaseId)
|
||||
if index == 0 then return 0 end
|
||||
if index ~= -1 then
|
||||
if on(saved[index + 1].toRegister) then return 0 end
|
||||
if saved[index + 1].registryStatus ~= SB.NEW or on(b.toRegister) then
|
||||
saveBase(saved, index, b)
|
||||
return index
|
||||
end
|
||||
return 0
|
||||
end
|
||||
for i = 1, SB.COUNT - 1 do
|
||||
if saved[i + 1].secretBaseId == 0 then
|
||||
saveBase(saved, i, b)
|
||||
return i
|
||||
end
|
||||
end
|
||||
for i = 1, SB.COUNT - 1 do
|
||||
if saved[i + 1].registryStatus == SB.UNREGISTERED and not on(saved[i + 1].toRegister) then
|
||||
saveBase(saved, i, b)
|
||||
return i
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
-- pokeemerald/src/secret_base.c:1486
|
||||
local function sortByRegistryStatus(saved)
|
||||
for i = 1, SB.COUNT - 2 do
|
||||
for j = i + 1, SB.COUNT - 1 do
|
||||
local a, b = saved[i + 1], saved[j + 1]
|
||||
if (a.registryStatus == SB.UNREGISTERED and b.registryStatus == SB.REGISTERED)
|
||||
or (a.registryStatus == SB.NEW and b.registryStatus ~= SB.NEW) then
|
||||
saved[i + 1], saved[j + 1] = b, a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- pokeemerald/src/secret_base.c:1509
|
||||
local function trySaveFriendsBases(saved, mixer, status)
|
||||
for i = 1, SB.COUNT - 1 do
|
||||
if mixer[i + 1].registryStatus == status then trySaveFriendsBase(saved, mixer[i + 1]) end
|
||||
end
|
||||
end
|
||||
|
||||
-- pokeemerald/src/secret_base.c:1549
|
||||
local function deleteFirstOldBaseFromPlayer(mixers, sess)
|
||||
local done = { false, false, false }
|
||||
for i = 1, SB.COUNT do
|
||||
for m = 1, 3 do
|
||||
if not done[m] and belongsToPlayer(mixers[m][i], sess) then
|
||||
clearBase(mixers[m][i])
|
||||
done[m] = true
|
||||
end
|
||||
end
|
||||
if done[1] and done[2] and done[3] then break end
|
||||
end
|
||||
end
|
||||
|
||||
-- pokeemerald/src/secret_base.c:1595
|
||||
local function clearDuplicateOwned(b, list, idx)
|
||||
for i = 1, SB.COUNT do
|
||||
local other = list[i]
|
||||
if other.secretBaseId ~= 0 and belongToSamePlayer(b, other) then
|
||||
if idx == 0 then
|
||||
clearBase(other)
|
||||
return false
|
||||
end
|
||||
if (b.numSecretBasesReceived or 0) > (other.numSecretBasesReceived or 0) then
|
||||
clearBase(other)
|
||||
return false
|
||||
end
|
||||
other.toRegister = b.toRegister
|
||||
clearBase(b)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- pokeemerald/src/secret_base.c:1627
|
||||
local function clearDuplicateOwnedBases(saved, a, b, c)
|
||||
for i = 1, SB.COUNT - 1 do
|
||||
local mine = saved[i + 1]
|
||||
if mine.secretBaseId ~= 0 then
|
||||
if mine.registryStatus == SB.REGISTERED then mine.toRegister = 1 end
|
||||
if not clearDuplicateOwned(mine, a, i) then
|
||||
if not clearDuplicateOwned(mine, b, i) then clearDuplicateOwned(mine, c, i) end
|
||||
end
|
||||
end
|
||||
end
|
||||
for i = 0, SB.COUNT - 1 do
|
||||
if a[i + 1].secretBaseId ~= 0 then
|
||||
a[i + 1].battledOwnerToday = 0
|
||||
if not clearDuplicateOwned(a[i + 1], b, i) then clearDuplicateOwned(a[i + 1], c, i) end
|
||||
end
|
||||
end
|
||||
for i = 0, SB.COUNT - 1 do
|
||||
if b[i + 1].secretBaseId ~= 0 then
|
||||
b[i + 1].battledOwnerToday = 0
|
||||
clearDuplicateOwned(b[i + 1], c, i)
|
||||
end
|
||||
if c[i + 1].secretBaseId ~= 0 then c[i + 1].battledOwnerToday = 0 end
|
||||
end
|
||||
end
|
||||
|
||||
-- pokeemerald/src/secret_base.c:1696
|
||||
local function saveRecordMixBases(saved, mixers, sess)
|
||||
deleteFirstOldBaseFromPlayer(mixers, sess)
|
||||
clearDuplicateOwnedBases(saved, mixers[1], mixers[2], mixers[3])
|
||||
-- pokeemerald/src/secret_base.c:1684
|
||||
for i = 1, SB.COUNT do
|
||||
for m = 1, 3 do
|
||||
local b = mixers[m][i]
|
||||
if on(b.toRegister) then
|
||||
trySaveFriendsBase(saved, b)
|
||||
clearBase(b)
|
||||
end
|
||||
end
|
||||
end
|
||||
for m = 1, 3 do trySaveFriendsBase(saved, mixers[m][1]) end
|
||||
for m = 1, 3 do trySaveFriendsBases(saved, mixers[m], SB.REGISTERED) end
|
||||
for m = 1, 3 do trySaveFriendsBases(saved, mixers[m], SB.UNREGISTERED) end
|
||||
end
|
||||
|
||||
local function mixerList(list)
|
||||
local out = {}
|
||||
for i = 1, SB.COUNT do
|
||||
local b = type(list) == "table" and list[i] or nil
|
||||
out[i] = normalize(type(b) == "table" and copyBase(b) or nil)
|
||||
out[i].secretBaseId = tonumber(out[i].secretBaseId) or 0
|
||||
out[i].registryStatus = tonumber(out[i].registryStatus) or 0
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- pokeemerald/src/secret_base.c:1731
|
||||
function SB.mixImport(sess, records, myIndex)
|
||||
sess = session(sess)
|
||||
local okF, received = pcall(flag, "FLAG_RECEIVED_SECRET_POWER", sess)
|
||||
if not (okF and received) then return true end
|
||||
records = records or {}
|
||||
local count = #records
|
||||
local slots = {}
|
||||
for i = 0, 3 do slots[i] = i < count and records[i + 1] or nil end
|
||||
local linkIdx = (tonumber(myIndex) or 1) - 1
|
||||
local mixers = {}
|
||||
for k = 1, 3 do mixers[k] = mixerList(slots[(linkIdx + k) % 4]) end
|
||||
local saved = SB.bases(sess)
|
||||
saveRecordMixBases(saved, mixers, sess)
|
||||
for i = 1, SB.COUNT - 1 do
|
||||
if on(saved[i + 1].toRegister) then
|
||||
saved[i + 1].registryStatus = SB.REGISTERED
|
||||
saved[i + 1].toRegister = 0
|
||||
end
|
||||
end
|
||||
sortByRegistryStatus(saved)
|
||||
for i = 1, SB.COUNT - 1 do
|
||||
if saved[i + 1].registryStatus == SB.NEW then saved[i + 1].registryStatus = SB.UNREGISTERED end
|
||||
end
|
||||
local mine = saved[1]
|
||||
if mine.secretBaseId ~= 0 and (mine.numSecretBasesReceived or 0) ~= 0xFFFF then
|
||||
mine.numSecretBasesReceived = (mine.numSecretBasesReceived or 0) + 1
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function SB.reset()
|
||||
SB._curId = 0
|
||||
SB._inFriendBase = false
|
||||
|
||||
@@ -315,10 +315,28 @@ local function gendered_ph(name, ctx)
|
||||
return by.male
|
||||
end
|
||||
|
||||
-- pokeemerald/src/string_util.c:428
|
||||
local function live(field, ctx)
|
||||
local v = ctx and ctx[field]
|
||||
if v ~= nil then return v end
|
||||
local provider = TextIR._provider
|
||||
if not provider then return nil end
|
||||
return provider(field, TextIR.dialect(ctx and ctx.dialect), ctx)
|
||||
end
|
||||
|
||||
local function player_name(ctx)
|
||||
return live("playerName", ctx) or "PLAYER"
|
||||
end
|
||||
|
||||
local function string_var(n, ctx)
|
||||
local sv = live("stringVars", ctx)
|
||||
return (type(sv) == "table" and sv[n]) or ""
|
||||
end
|
||||
|
||||
local function rival_name(ctx)
|
||||
local v = gendered_ph("RIVAL", ctx)
|
||||
if v ~= nil then return v end
|
||||
return (ctx and ctx.rivalName) or "RIVAL"
|
||||
return live("rivalName", ctx) or "RIVAL"
|
||||
end
|
||||
|
||||
local function expand_seg(seg, ctx)
|
||||
@@ -329,12 +347,11 @@ local function expand_seg(seg, ctx)
|
||||
local dyn = ctx and ctx.dynamic
|
||||
return (dyn and dyn[seg.n]) or ""
|
||||
elseif t == "player" then
|
||||
return (ctx and ctx.playerName) or "PLAYER"
|
||||
return player_name(ctx)
|
||||
elseif t == "rival" then
|
||||
return rival_name(ctx)
|
||||
elseif t == "strvar" then
|
||||
local sv = ctx and ctx.stringVars
|
||||
return (sv and sv[seg.n]) or ""
|
||||
return string_var(seg.n, ctx)
|
||||
elseif t == "tag" then
|
||||
return seg.tag
|
||||
elseif t == "bph" then
|
||||
@@ -350,19 +367,16 @@ local function expand_seg(seg, ctx)
|
||||
local code = tonumber(seg.code)
|
||||
local name = seg.name
|
||||
if code == 0x01 or name == "PLAYER" then
|
||||
return (ctx and ctx.playerName) or "PLAYER"
|
||||
return player_name(ctx)
|
||||
end
|
||||
if code == 0x06 or name == "RIVAL" then
|
||||
return rival_name(ctx)
|
||||
end
|
||||
if code and code >= 0x02 and code <= 0x04 then
|
||||
local sv = ctx and ctx.stringVars
|
||||
return (sv and sv[code - 1]) or ""
|
||||
return string_var(code - 1, ctx)
|
||||
end
|
||||
if name == "STR_VAR_1" or name == "STR_VAR_2" or name == "STR_VAR_3" then
|
||||
local n = tonumber(name:sub(-1)) or 1
|
||||
local sv = ctx and ctx.stringVars
|
||||
return (sv and sv[n]) or ""
|
||||
return string_var(tonumber(name:sub(-1)) or 1, ctx)
|
||||
end
|
||||
if name and (name == "FONT_MALE" or name == "FONT_FEMALE" or name == "FONT_NORMAL"
|
||||
or name:find("^COLOR") or name:find("^SHADOW") or name:find("^HIGHLIGHT") or name:find("^BG")) then
|
||||
|
||||
@@ -222,6 +222,28 @@ function Trainers.foeFromId(trainerId)
|
||||
}
|
||||
end
|
||||
|
||||
local DIALOG_TEXT_KEYS = {
|
||||
intro = "introTextKey", defeat = "defeatTextKey",
|
||||
victory = "victoryTextKey", notEnough = "notEnoughTextKey",
|
||||
}
|
||||
|
||||
local function live_dialogs(t)
|
||||
local d = t.dialogs or {}
|
||||
local Sp = package.loaded["src.core.game3.scripting.space"]
|
||||
local vm = Sp and Sp.vm
|
||||
if not (vm and vm.getText) then return d end
|
||||
local out
|
||||
for field, keyName in pairs(DIALOG_TEXT_KEYS) do
|
||||
local key = t[keyName]
|
||||
local ir = key and vm:getText(key)
|
||||
if type(ir) == "table" then
|
||||
out = out or setmetatable({}, { __index = d })
|
||||
out[field] = ir
|
||||
end
|
||||
end
|
||||
return out or d
|
||||
end
|
||||
|
||||
--- ROM-derived trainer presentation info (class / name / pic / partySize / dialogs).
|
||||
-- opts.rivalName replaces the placeholder "TERRY" of the rival and champion
|
||||
-- classes when provided.
|
||||
@@ -247,7 +269,7 @@ function Trainers.info(trainerId, opts)
|
||||
ai = t.ai,
|
||||
items = t.items,
|
||||
party = t.party,
|
||||
dialogs = t.dialogs,
|
||||
dialogs = live_dialogs(t),
|
||||
}
|
||||
|
||||
-- pokefirered/src/battle_message.c:2078 names these classes by the player's
|
||||
@@ -263,7 +285,7 @@ end
|
||||
--- Get dialog texts table: { intro, defeat, victory, notEnough }
|
||||
function Trainers.dialogs(trainerId)
|
||||
local t = Trainers.get(trainerId)
|
||||
return t and t.dialogs or {}
|
||||
return t and live_dialogs(t) or {}
|
||||
end
|
||||
|
||||
--- FRLG intro string pieces for a trainer battle.
|
||||
|
||||
@@ -531,6 +531,26 @@ section({ name = "trainerHill", fields = {}, template = { "trainerHill", "traine
|
||||
section({ name = "apprentice", fields = {}, template = { "playerApprentice", "apprentices" } })
|
||||
section({ name = "lilycoveLady", fields = {}, template = { "lilycoveLady" } })
|
||||
section({ name = "oldMan", fields = {}, template = { "oldMan" } })
|
||||
|
||||
-- pokeemerald/include/global.h:746
|
||||
section({
|
||||
name = "recordMixingGift",
|
||||
fields = { "recordMixingGift" },
|
||||
read = function(x)
|
||||
local b = x.L.RSE.sb1.recordMixingGift
|
||||
return { recordMixingGift = { checksum = u32(x.sb1, b), unk0 = u8(x.sb1, b + 4), quantity = u8(x.sb1, b + 5),
|
||||
itemId = u16(x.sb1, b + 6) } }
|
||||
end,
|
||||
write = function(x, v)
|
||||
local b = x.L.RSE.sb1.recordMixingGift
|
||||
local g = type(v.recordMixingGift) == "table" and v.recordMixingGift or {}
|
||||
x.w1:fill(b, 16, 0)
|
||||
x.w1:w32(b, num(g.checksum) % U32)
|
||||
x.w1:w8(b + 4, num(g.unk0) % 256)
|
||||
x.w1:w8(b + 5, num(g.quantity) % 256)
|
||||
x.w1:w16(b + 6, num(g.itemId) % 65536)
|
||||
end,
|
||||
})
|
||||
section({ name = "lottery", fields = {}, vars = true })
|
||||
|
||||
local function context(codec, blocks, w1, w2)
|
||||
|
||||
@@ -41,6 +41,21 @@ TextIR.setContextProvider(function(kind, dialect, ctx)
|
||||
local s = liveSession()
|
||||
return s and s.gender or nil
|
||||
end
|
||||
if kind == "playerName" then
|
||||
local s = liveSession()
|
||||
local name = s and (s.name or s.playerName)
|
||||
return (type(name) == "string" and name ~= "") and name or nil
|
||||
end
|
||||
if kind == "rivalName" or kind == "stringVars" then
|
||||
local Sp = package.loaded["src.core.game3.scripting.space"]
|
||||
local vm = Sp and Sp.vm
|
||||
if not vm then return nil end
|
||||
if kind == "stringVars" then return vm.ctx and vm.ctx.stringVars end
|
||||
local a = vm.adapters
|
||||
local r = a and a.rivalName
|
||||
if type(r) == "function" then r = r() end
|
||||
return r or (vm.ctx and vm.ctx.rivalName)
|
||||
end
|
||||
if kind ~= "placeholders" or not dialect.placeholders then return nil end
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local s = liveSession()
|
||||
|
||||
@@ -919,7 +919,18 @@ BEATS[#BEATS + 1] = { id = "champion", run = function(game)
|
||||
local track = labelWatch(labels)
|
||||
local rivalShot, birchShot = false, false
|
||||
local ratingText
|
||||
local settle = { limit = 120000, onBattleFrame = battleShot(game, "26_wallace_battle"),
|
||||
local wallaceShot, kudos = battleShot(game, "26_wallace_battle"), nil
|
||||
local function onBattleFrame(st, phase)
|
||||
wallaceShot(st, phase)
|
||||
local M = require("src.ui.game3.message")
|
||||
if not kudos and M.isOpen() and messageText():find("Kudos to you", 1, true) then
|
||||
if M.isTyping() then M.skipReveal() end
|
||||
kudos = messageText()
|
||||
d.note("wallace defeat: " .. kudos)
|
||||
d.shot(game, "26b_wallace_kudos")
|
||||
end
|
||||
end
|
||||
local settle = { limit = 120000, onBattleFrame = onBattleFrame,
|
||||
watch = function()
|
||||
mug()
|
||||
track()
|
||||
@@ -950,6 +961,9 @@ BEATS[#BEATS + 1] = { id = "champion", run = function(game)
|
||||
d.check(d.ran("EverGrandeCity_ChampionsRoom_EventScript_EnterRoom"), "the player walks up to Wallace on entry")
|
||||
d.check(seen.key == "champion", "Wallace's mugshot battle transition (" .. tostring(seen.key) .. ")")
|
||||
d.check(lastBattleWon(tid("TRAINER_WALLACE")), "Champion Wallace battle won through the battle UI")
|
||||
local pname = S.session().name or ""
|
||||
d.check(kudos ~= nil and pname ~= "" and kudos:find("Kudos to you, " .. pname .. "!", 1, true) ~= nil,
|
||||
"Wallace's defeat line names the player (" .. tostring(kudos) .. ")")
|
||||
d.check(labels.EverGrandeCity_ChampionsRoom_EventScript_MayAdvice and rivalShot, "May runs in after the battle")
|
||||
local dexCaught = 0
|
||||
for _ in pairs(S.session().dex.caught or {}) do dexCaught = dexCaught + 1 end
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
local U = require("tests.drivers.util")
|
||||
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/game3_wild_exp_flow"
|
||||
|
||||
return function(game)
|
||||
io.stdout:setvbuf("line")
|
||||
local fails = 0
|
||||
local function result(ok, label)
|
||||
print((ok and "PASS " or "FAIL ") .. label)
|
||||
if not ok then fails = fails + 1 end
|
||||
return ok
|
||||
end
|
||||
local function finish()
|
||||
print(string.format("[driver] game3_wild_exp_flow: %d failure(s)", fails))
|
||||
love.event.quit(fails == 0 and 0 or 1)
|
||||
U.wait(10)
|
||||
end
|
||||
|
||||
for _ = 1, 900 do
|
||||
if game.phase == "boot" and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
game:_handleBootAction({ action = "new_game", name = "BRENDAN", gender = 0 })
|
||||
U.wait(240)
|
||||
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local Party = require("src.core.game3.party")
|
||||
local BattleBridge = require("src.core.game3.battle_bridge")
|
||||
local Battle = require("src.core.game3.battle")
|
||||
local Ui = require("src.core.game3.battle.ui")
|
||||
local Message = require("src.ui.game3.message")
|
||||
local StatGrowth = require("src.ui.game3.stat_growth")
|
||||
local Anim = require("src.core.game3.battle.anim")
|
||||
local Audio = require("src.core.game3.audio")
|
||||
local Map = require("src.core.game3.map")
|
||||
local Encounters = require("src.core.game3.encounters")
|
||||
local Pokemon = require("src.core.game3.pokemon")
|
||||
local Experience = require("src.core.game3.battle.experience")
|
||||
local Constants = require("src.core.game3.constants")
|
||||
local session = Runtime.getSession()
|
||||
if not result(session ~= nil, "field session exists") then return finish() end
|
||||
local C = Constants.of(Constants.versionOf(session))
|
||||
print("[driver] version " .. tostring(C.game))
|
||||
Encounters.onStep = function() return nil end
|
||||
if C.game == "emerald" then
|
||||
Map.load(nil, game, "EM_ROUTE101", { x = 7, y = 13, facing = "down" })
|
||||
else
|
||||
Map.load(nil, game, "FR_ROUTE1", { x = 10, y = 20, facing = "down" })
|
||||
end
|
||||
U.wait(40)
|
||||
|
||||
local MUDKIP = C.species.byName.SPECIES_MUDKIP
|
||||
local learnLv, learnMove
|
||||
for lv = 6, 40 do
|
||||
local mv = Pokemon.movesLearnedAt(MUDKIP, lv)
|
||||
if mv and #mv > 0 then learnLv, learnMove = lv, mv[1] break end
|
||||
end
|
||||
if not result(learnLv ~= nil, "Mudkip learnset has a move between LV6 and LV40 (" .. tostring(learnLv) .. ")") then
|
||||
return finish()
|
||||
end
|
||||
session.party = {}
|
||||
Party.giveMon(session, MUDKIP, learnLv - 1, "")
|
||||
local mon = session.party[1]
|
||||
local mv = function(n) return C.moves.byName["MOVE_" .. n] end
|
||||
mon.moves = {}
|
||||
for _, n in ipairs({ "TACKLE", "GROWL", "PROTECT", "WATER_GUN", "SCRATCH" }) do
|
||||
if mv(n) ~= learnMove and #mon.moves < 4 then mon.moves[#mon.moves + 1] = mv(n) end
|
||||
end
|
||||
mon.pp = { 35, 40, 10, 10 }
|
||||
mon.maxPp = { 35, 40, 10, 10 }
|
||||
mon.exp = Experience.expForLevel(mon, learnLv) - 1
|
||||
print(string.format("[driver] Mudkip LV%d exp %d, learns %s at LV%d", mon.level, mon.exp,
|
||||
tostring(C:name("moves", learnMove, "MOVE_")), learnLv))
|
||||
|
||||
local VS_WILD = C.songs.byName.MUS_VS_WILD
|
||||
local VICTORY_WILD = C.songs.byName.MUS_VICTORY_WILD
|
||||
local outcome
|
||||
local ok = BattleBridge.startWild(Runtime._mod, game,
|
||||
{ species = C.species.byName.SPECIES_ZIGZAGOON, level = 5 }, { done = function(r) outcome = r end })
|
||||
if not result(ok == true, "wild Zigzagoon battle started") then return finish() end
|
||||
for _ = 1, 3000 do
|
||||
if Battle.isActive() then break end
|
||||
U.wait(1)
|
||||
end
|
||||
if not result(Battle.isActive(), "battle active") then return finish() end
|
||||
|
||||
local f = 0
|
||||
local ev = {}
|
||||
local function mark(name)
|
||||
if not ev[name] then
|
||||
ev[name] = f
|
||||
print(string.format("[driver] f=%d %s song=%s", f, name, tostring(Audio._currentSong and Audio._currentSong.id)))
|
||||
end
|
||||
end
|
||||
local songAt = {}
|
||||
local waitF = 0
|
||||
local statShot, expShot, learnShot = false, false, false
|
||||
local gainedOpenFrames, gainedPrinted = 0, false
|
||||
local endedWhileGained = false
|
||||
local foeLowered = false
|
||||
while (Battle.isActive() or outcome == nil) and f < 30000 do
|
||||
f = f + 1
|
||||
local song = Audio._currentSong and Audio._currentSong.id
|
||||
local page = Message.isOpen() and Message.currentPage() or ""
|
||||
local phase = Battle._phase
|
||||
if song == VICTORY_WILD then mark("victory_song") end
|
||||
if page:find("fainted") then
|
||||
mark("fainted_open")
|
||||
songAt.fainted = songAt.fainted or song
|
||||
if Message.isWaiting() then mark("fainted_waiting") end
|
||||
end
|
||||
if page:find("EXP%. Points") then
|
||||
mark("gained_open")
|
||||
songAt.gained = songAt.gained or song
|
||||
gainedOpenFrames = gainedOpenFrames + 1
|
||||
if Message.isWaiting() then
|
||||
gainedPrinted = true
|
||||
mark("gained_printed")
|
||||
if not expShot then
|
||||
expShot = U.still(game, DIR .. "/01_gained_exp.png")
|
||||
end
|
||||
end
|
||||
if phase == "ending" or phase == "fade_out" then endedWhileGained = true end
|
||||
elseif ev.gained_open and not ev.gained_closed then
|
||||
mark("gained_closed")
|
||||
end
|
||||
if page:find("grew to") then mark("grew_open") end
|
||||
if page:find("trying to") or page:find("learn") then mark("learn_text") end
|
||||
if phase == "ending" or phase == "fade_out" then mark("battle_ending") end
|
||||
|
||||
if StatGrowth.isOpen() then
|
||||
mark("stat_box")
|
||||
if not statShot then
|
||||
U.wait(20)
|
||||
statShot = U.still(game, DIR .. "/02_level_up_box.png")
|
||||
end
|
||||
U.wait(30)
|
||||
U.tap(game, "a")
|
||||
elseif Ui.choiceActive() then
|
||||
mark("learn_prompt")
|
||||
if not learnShot then
|
||||
U.wait(10)
|
||||
learnShot = U.still(game, DIR .. "/03_learn_prompt.png")
|
||||
end
|
||||
U.wait(20)
|
||||
local lg = Ui.log()
|
||||
local last = tostring(lg[#lg] or "")
|
||||
U.tap(game, last:find("Stop") and "a" or "b")
|
||||
elseif phase == "command" and Ui._mode == "menu" and not Anim.busy() then
|
||||
local st = Battle.getState()
|
||||
if st and st.enemy and st.enemy.mon and not foeLowered then
|
||||
st.enemy.mon.hp = 1
|
||||
foeLowered = true
|
||||
end
|
||||
U.wait(10) U.tap(game, "a") U.wait(10) U.tap(game, "a") U.wait(4)
|
||||
elseif Message.isWaiting() and Ui.dialogPending() then
|
||||
waitF = waitF + 1
|
||||
if waitF >= 90 then
|
||||
waitF = 0
|
||||
U.tap(game, "a")
|
||||
else
|
||||
U.wait(1)
|
||||
end
|
||||
else
|
||||
waitF = 0
|
||||
U.wait(1)
|
||||
end
|
||||
end
|
||||
|
||||
result(outcome == "win", "battle outcome win (" .. tostring(outcome) .. ")")
|
||||
result(ev.fainted_waiting ~= nil and ev.gained_open ~= nil and ev.gained_open - ev.fainted_waiting >= 90,
|
||||
"fainted!\\p waits for a button press before getexp (" .. tostring(ev.fainted_waiting) .. " -> " .. tostring(ev.gained_open) .. ")")
|
||||
result(songAt.fainted == VS_WILD, "MUS_VS_WILD still playing on the fainted message (" .. tostring(songAt.fainted) .. ")")
|
||||
result(ev.victory_song ~= nil and ev.gained_open ~= nil and ev.victory_song <= ev.gained_open,
|
||||
"MUS_VICTORY_WILD starts before the gained EXP text (" .. tostring(ev.victory_song) .. " <= " .. tostring(ev.gained_open) .. ")")
|
||||
result(songAt.gained == VICTORY_WILD, "MUS_VICTORY_WILD playing under the gained EXP text")
|
||||
result(gainedPrinted, "gained EXP text printed in full")
|
||||
result(gainedOpenFrames >= 60, "gained EXP text on screen " .. gainedOpenFrames .. " frames")
|
||||
result(not endedWhileGained, "battle did not start ending while the gained EXP text was up")
|
||||
result(ev.grew_open ~= nil and ev.grew_open > (ev.gained_open or math.huge), "grew to LV text after the gained EXP text")
|
||||
result(ev.stat_box ~= nil and ev.stat_box >= (ev.grew_open or math.huge), "level-up stat box shown after grew to LV")
|
||||
result(ev.learn_prompt ~= nil and ev.learn_prompt > (ev.stat_box or math.huge), "move-learn prompt after the level-up box")
|
||||
result(ev.battle_ending ~= nil and ev.battle_ending > (ev.learn_prompt or math.huge)
|
||||
and ev.battle_ending > (ev.gained_closed or math.huge), "battle ends after the EXP / level / learn flow")
|
||||
result(expShot and statShot and learnShot, "gained / level-up / learn prompt shots")
|
||||
local m = session.party[1]
|
||||
result(m and m.level == learnLv, "Mudkip reached LV" .. learnLv .. " (" .. tostring(m and m.level) .. ")")
|
||||
return finish()
|
||||
end
|
||||
@@ -0,0 +1,120 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check, eq = T.check, T.eq
|
||||
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local Profile = require("src.core.game3.profile")
|
||||
Profile.reset()
|
||||
GameVersion.set("emerald")
|
||||
|
||||
local Dataset = require("src.core.game3.dataset")
|
||||
local cache = Dataset.cache()
|
||||
local function loadRel(rel)
|
||||
local src = cache and cache.read and cache:read(rel)
|
||||
if type(src) ~= "string" then return nil end
|
||||
local chunk = load(src, "@" .. rel, "t", { setmetatable = setmetatable, pairs = pairs, ipairs = ipairs })
|
||||
local ok, v = pcall(chunk)
|
||||
return ok and v or nil
|
||||
end
|
||||
local manifest = loadRel("data/generated/gba/pokemon/battle/manifest.lua")
|
||||
local text = manifest and manifest.layout == "rse" and loadRel("data/generated/gba/scripts/text.lua")
|
||||
if type(text) ~= "table" then
|
||||
print("emerald_player_placeholder_test: skipped (no Emerald cache; set POKEPORT_IDENTITY)")
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
require("src.import.gba.versions").select("emerald")
|
||||
local C = require("src.core.game3.constants").of("emerald")
|
||||
local TextIR = require("src.core.game3.scripting.text_ir")
|
||||
local Trainers = require("src.core.game3.scripting.trainers")
|
||||
local Space = require("src.core.game3.scripting.space")
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local Message = require("src.ui.game3.message")
|
||||
local BattleText = require("src.core.game3.battle.battle_text")
|
||||
|
||||
local placeholders = loadRel("data/generated/gba/text/placeholders.lua")
|
||||
local liveProvider = TextIR._provider
|
||||
TextIR.setContextProvider(function(kind, dialect, ctx)
|
||||
if kind == "placeholders" then return placeholders end
|
||||
return liveProvider(kind, dialect, ctx)
|
||||
end)
|
||||
|
||||
local sess = { version = "emerald", name = "QUINCY", gender = 0, trainerId = 1, money = 0, party = {} }
|
||||
local prevSession, prevVm = Runtime.session, Space.vm
|
||||
Runtime.session = sess
|
||||
Space.vm = {
|
||||
ctx = { stringVars = { "ZIGZAGOON", "SECOND", "" } },
|
||||
adapters = {},
|
||||
getText = function(_, key) return text[key] end,
|
||||
}
|
||||
|
||||
local function shown(t, opts)
|
||||
Message.show(t, opts or { frame = "battle", battle = true })
|
||||
local s = table.concat(Message._pages or {}, " ")
|
||||
Message.close()
|
||||
return (s:gsub("[\n\f\v]", " "))
|
||||
end
|
||||
|
||||
local function has(s, needle) return type(s) == "string" and s:find(needle, 1, true) ~= nil end
|
||||
|
||||
local WALLACE = C.trainers and C.trainers.TRAINER_WALLACE or 335
|
||||
local baked = Trainers.get(WALLACE).dialogs.defeat
|
||||
check(has(baked, "Kudos to you, PLAYER!"), "cache dialogs.defeat is the flattened import string")
|
||||
|
||||
local d = Trainers.dialogs(WALLACE)
|
||||
eq(type(d.defeat), "table", "Wallace defeat resolves to IR through the live script text")
|
||||
local wallace = shown(d.defeat)
|
||||
check(has(wallace, "Kudos to you, QUINCY!"), "Wallace defeat names the player: " .. wallace)
|
||||
check(not has(wallace, "PLAYER"), "Wallace defeat has no literal PLAYER")
|
||||
check(has(shown(Trainers.info(WALLACE).dialogs.intro), "QUINCY"), "Wallace intro via Trainers.info names the player")
|
||||
|
||||
for _, tid in ipairs({ 269, 519, 520, 529, 664, 804 }) do
|
||||
local dl = Trainers.dialogs(tid)
|
||||
for _, field in ipairs({ "intro", "defeat" }) do
|
||||
local ir = dl[field]
|
||||
if type(ir) == "table" then
|
||||
local s = shown(ir)
|
||||
local wantsPlayer = false
|
||||
for _, seg in ipairs(ir) do if seg.t == "player" then wantsPlayer = true end end
|
||||
if wantsPlayer then
|
||||
check(has(s, "QUINCY") and not has(s, "PLAYER"), ("trainer %d %s names the player: %s"):format(tid, field, s))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
check(has(TextIR.toPlain(d.defeat), "QUINCY"), "toPlain without ctx falls back to the live player")
|
||||
check(has(TextIR.toPlain(d.defeat, { playerName = "ZED" }), "ZED"), "explicit ctx playerName still wins")
|
||||
|
||||
local rivalIr, strIr
|
||||
for _, ir in pairs(text) do
|
||||
if type(ir) == "table" then
|
||||
for _, seg in ipairs(ir) do
|
||||
if not rivalIr and seg.t == "rival" then rivalIr = ir end
|
||||
if not strIr and seg.t == "strvar" and seg.n == 1 then strIr = ir end
|
||||
end
|
||||
end
|
||||
if rivalIr and strIr then break end
|
||||
end
|
||||
if rivalIr then
|
||||
check(has(shown(rivalIr), "MAY"), "{RIVAL} is MAY for a male player")
|
||||
sess.gender = 1
|
||||
check(has(shown(rivalIr), "BRENDAN"), "{RIVAL} is BRENDAN for a female player")
|
||||
sess.gender = 0
|
||||
end
|
||||
if strIr then
|
||||
check(has(shown(strIr), "ZIGZAGOON"), "{STR_VAR_1} reads the live script string var")
|
||||
end
|
||||
|
||||
local vs = shown(BattleText.get("STRINGID_PLAYERGOTMONEY", { playerName = "QUINCY", buff1 = "9900" }))
|
||||
check(has(vs, "QUINCY") and has(vs, "9900"), "battle prize string names the player: " .. vs)
|
||||
local money = shown(require("src.core.game3.battle.prize").moneyMessage(sess.name, 9900))
|
||||
check(has(money, "QUINCY got"), "prize money message names the player: " .. money)
|
||||
|
||||
Space.vm = nil
|
||||
eq(Trainers.dialogs(WALLACE).defeat, baked, "without a script vm the cache string is returned unchanged")
|
||||
|
||||
Runtime.session, Space.vm = prevSession, prevVm
|
||||
TextIR.setContextProvider(liveProvider)
|
||||
T.finish("emerald_player_placeholder_test")
|
||||
@@ -0,0 +1,337 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
love = love or require("tests.love_stub")
|
||||
|
||||
local T = require("tests.harness")
|
||||
T.verbose = true
|
||||
local check, eq = T.check, T.eq
|
||||
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local Profile = require("src.core.game3.profile")
|
||||
Profile.reset()
|
||||
GameVersion.set("emerald")
|
||||
|
||||
local Dataset = require("src.core.game3.dataset")
|
||||
local emCache = Dataset.cache()
|
||||
local meta = emCache and emCache.read and emCache:read("data/generated/gba/meta.json")
|
||||
if not (type(meta) == "string" and meta:find('"version":"emerald"', 1, true)) then
|
||||
print("emerald_record_mix_test: skipped (no Emerald cache; set POKEPORT_IDENTITY)")
|
||||
os.exit(0)
|
||||
end
|
||||
require("src.import.gba.versions").select("emerald")
|
||||
local Pokemon = require("src.core.game3.pokemon")
|
||||
Pokemon.install(nil)
|
||||
|
||||
local Json = require("src.link.Json")
|
||||
local Wire = require("src.link.Wire")
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local Schema = require("src.core.game3.save_schema_firered")
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local Space = require("src.core.game3.scripting.space")
|
||||
local Rse = require("src.core.game3.rse.init")
|
||||
local RecordMix = require("src.core.game3.link.record_mix")
|
||||
local MixUtil = require("src.core.game3.rse.record_mix_util")
|
||||
local OldMan = require("src.core.game3.rse.old_man")
|
||||
local Lady = require("src.core.game3.rse.lilycove_lady")
|
||||
local SB = require("src.core.game3.rse.secret_base")
|
||||
local Tv = require("src.core.game3.rse.tv")
|
||||
local Dex = require("src.core.game3.dex")
|
||||
local Daycare = require("src.core.game3.daycare")
|
||||
local DaycareMail = require("src.core.game3.rse.daycare_mail_mix")
|
||||
local Gift = require("src.core.game3.rse.record_mixing_gift")
|
||||
local FUtil = require("src.core.game3.rse.frontier.util")
|
||||
local Apprentice = require("src.core.game3.rse.frontier.apprentice")
|
||||
local Bag = require("src.core.game3.bag")
|
||||
local EM = require("src.core.game3.constants").of("emerald")
|
||||
|
||||
local ORANGE_MAIL = EM:require("items", "ITEM_ORANGE_MAIL")
|
||||
local EON_TICKET = EM:require("items", "ITEM_EON_TICKET")
|
||||
local SEEDOT = EM:require("species", "SPECIES_SEEDOT")
|
||||
local MUDKIP = EM:require("species", "SPECIES_MUDKIP")
|
||||
|
||||
local stores = {}
|
||||
local function use(s)
|
||||
Space.store = stores[s]
|
||||
end
|
||||
|
||||
local function makePlayer(idx, name, tid, gender)
|
||||
local s = Schema.newGame({ version = "emerald", name = name, rngSeed = tid })
|
||||
s.trainerId, s.secretId, s.gender = tid, 0, gender
|
||||
s.party = { { species = MUDKIP, level = 20, nickname = "", heldItem = 0 } }
|
||||
s.dex = s.dex or Dex.new()
|
||||
Dex.setSeen(s.dex, SEEDOT)
|
||||
Dex.setSeen(s.dex, MUDKIP)
|
||||
stores[s] = Flags.newStore()
|
||||
use(s)
|
||||
Rse.setFlag("FLAG_RECEIVED_SECRET_POWER", true, s)
|
||||
|
||||
local b = SB.base(s, 0)
|
||||
b.secretBaseId, b.trainerName, b.gender, b.language = 10 * idx + 1, name, gender, 2
|
||||
b.trainerId = MixUtil.trainerIdBytes(s)
|
||||
|
||||
local m = OldMan.set(s)
|
||||
if m.id == OldMan.STORYTELLER then
|
||||
m.gameStatIDs[1], m.trainerNames[1], m.statValues[1] = 1, name, 100 * idx
|
||||
elseif m.id == OldMan.GIDDY then
|
||||
m.randomWords[1] = 100 + idx
|
||||
elseif m.id == OldMan.BARD then
|
||||
m.playerName = name
|
||||
end
|
||||
|
||||
local l = Lady.init(s)
|
||||
local which = l.quiz or l.favor or l.contest
|
||||
which.playerName = name
|
||||
|
||||
local dc = Daycare.stateOf(s)
|
||||
Daycare.setMon(dc, 1, { species = SEEDOT, level = 5, heldItem = 0 })
|
||||
dc.mail = {}
|
||||
|
||||
local f = FUtil.frontier(s)
|
||||
f.towerPlayer = { trainerId = MixUtil.trainerIdBytes(s), name = name, winStreak = 20 * idx, lvlMode = 0,
|
||||
facilityClass = 1, party = { { species = MUDKIP, level = 50 } }, language = 2 }
|
||||
FUtil.set2(f.towerRecordWinStreaks, 0, 0, 20 * idx)
|
||||
FUtil.set2(f.towerRecordWinStreaks, 3, 0, 5 * idx)
|
||||
|
||||
local a = Apprentice.saved(s)[1]
|
||||
a.playerName, a.playerId, a.number, a.id, a.lvlMode = name, MixUtil.trainerIdBytes(s), idx, idx, 1
|
||||
return s
|
||||
end
|
||||
|
||||
local function giveShow(s)
|
||||
local saved = Tv.random
|
||||
Tv.random = function() return 0 end
|
||||
Tv.onBattleEnd(s, { playerMon1Species = MUDKIP, caughtMonSpecies = SEEDOT, caughtMonNick = "ACORN",
|
||||
catchAttempts = { [3] = 2 }, lastUsedItem = EM:require("items", "ITEM_POKE_BALL"), lastOpponentSpecies = SEEDOT },
|
||||
Tv.B_OUTCOME_CAUGHT, {})
|
||||
Tv.random = saved
|
||||
end
|
||||
|
||||
local function wire(packet, spot)
|
||||
local msg = Wire.sanitize(Json.decode(Json.encode({ type = RecordMix.MSG.PACKET, spot = spot,
|
||||
packet = RecordMix.toWire(packet) })))
|
||||
return msg and RecordMix.fromWire(msg.packet) or nil
|
||||
end
|
||||
|
||||
local function mix(players)
|
||||
local packets, wired = {}, {}
|
||||
for i, s in ipairs(players) do
|
||||
use(s)
|
||||
packets[i] = RecordMix.packet(s, i - 1)
|
||||
wired[i] = wire(packets[i], i - 1)
|
||||
end
|
||||
local applied = {}
|
||||
for i, s in ipairs(players) do
|
||||
local list = {}
|
||||
for j = 1, #players do list[j] = j == i and packets[i] or wired[j] end
|
||||
use(s)
|
||||
applied[i] = RecordMix.receive(s, list, i)
|
||||
end
|
||||
return applied, packets, wired
|
||||
end
|
||||
|
||||
local function hasBase(s, id)
|
||||
for i = 2, SB.COUNT do
|
||||
if SB.base(s, i - 1).secretBaseId == id then return SB.base(s, i - 1) end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function towerRecordNamed(s, name)
|
||||
for _, r in ipairs(FUtil.frontier(s).towerRecords) do
|
||||
if r.name == name then return r end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function apprenticeNamed(s, name)
|
||||
for i, a in ipairs(s.apprentices or {}) do
|
||||
if i > 1 and a.playerName == name then return a end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function tvHasCaught(s)
|
||||
for i = 0, Tv.TV_SHOWS_COUNT - 1 do
|
||||
local show = s.tvShows[i]
|
||||
if show and show.kind == Tv.TVSHOW_POKEMON_TODAY_CAUGHT and show.active == true then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function reload(s)
|
||||
local save = SaveData.decode(SaveData.encode(Schema.toSaveTable(s)))
|
||||
return Schema.fromSaveTable(save)
|
||||
end
|
||||
|
||||
local function giftVar(s)
|
||||
use(s)
|
||||
return Rse.var("VAR_TEMP_RECORD_MIX_GIFT_ITEM", s)
|
||||
end
|
||||
|
||||
print("[test] 1. ShufflePlayerIndices (pokeemerald/src/record_mixing.c:604)")
|
||||
do
|
||||
local two = MixUtil.shuffle({ { trainerId = 5 }, {} })
|
||||
eq(two[1] .. two[2], "21", "2 players swap")
|
||||
local three0 = MixUtil.shuffle({ { linkTrainerId = 4 }, {}, {} })
|
||||
eq(three0[1] .. three0[2] .. three0[3], "231", "3 players, even leader id: 0<-1, 1<-2, 2<-0")
|
||||
local three1 = MixUtil.shuffle({ { linkTrainerId = 7 }, {}, {} })
|
||||
eq(three1[1] .. three1[2] .. three1[3], "312", "3 players, odd leader id: 0<-2, 1<-0, 2<-1")
|
||||
local four = MixUtil.shuffle({ { linkTrainerId = 9 * 3 + 4 }, {}, {}, {} })
|
||||
eq(four[1] .. four[2] .. four[3] .. four[4], "3412", "4 players use sPlayerIdxOrders_4Player[id % 9]")
|
||||
end
|
||||
|
||||
print("[test] 2. two players mix every record")
|
||||
do
|
||||
local A = makePlayer(1, "MAY", 1116, 1)
|
||||
local B = makePlayer(2, "BRENDAN", 2228, 0)
|
||||
giveShow(B)
|
||||
local mail = { message = { itemId = ORANGE_MAIL, words = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, playerName = "MAY",
|
||||
trainerId = 1116, species = SEEDOT }, otName = "MAY", monName = "SEEDOT" }
|
||||
Daycare.stateOf(A).mail[1] = mail
|
||||
Gift.set(1, 1, EON_TICKET, A)
|
||||
eq(OldMan.current(A), OldMan.STORYTELLER, "MAY has the storyteller")
|
||||
eq(OldMan.current(B), OldMan.GIDDY, "BRENDAN has Giddy")
|
||||
eq(Lady.id(A), Lady.QUIZ, "MAY has the quiz lady")
|
||||
eq(Lady.id(B), Lady.FAVOR, "BRENDAN has the favor lady")
|
||||
|
||||
local applied, packets, wired = mix({ A, B })
|
||||
eq(packets[1].giftItem, EON_TICKET, "multiplayer id 0 sends its record mixing gift")
|
||||
eq(packets[2].giftItem, 0, "other players send no gift")
|
||||
check(wired[2] and wired[2].daycareMail and wired[2].apprentices and wired[2].hallRecords
|
||||
and wired[2].battleTowerRecord and wired[2].lilycoveLady and wired[2].oldMan, "every record crosses the wire")
|
||||
for _, key in ipairs({ "secretBases", "tvShows", "pokeNews", "oldMan", "dewfordTrends", "daycareMail",
|
||||
"battleTower", "giftItem", "lilycoveLady", "apprentices", "hallRecords" }) do
|
||||
check(applied[1][key] and applied[2][key], "both sides ran the " .. key .. " receiver")
|
||||
end
|
||||
|
||||
eq(OldMan.current(A), OldMan.GIDDY, "MAY now has BRENDAN's Giddy")
|
||||
eq(A.oldMan.randomWords[1], 102, "with BRENDAN's tale words")
|
||||
eq(OldMan.current(B), OldMan.STORYTELLER, "BRENDAN now has MAY's storyteller")
|
||||
eq(B.oldMan.trainerNames[1], "MAY", "with MAY's story")
|
||||
eq(B.oldMan.alreadyRecorded, false, "ResetMauvilleOldManFlag ran")
|
||||
|
||||
eq(Lady.id(A), Lady.FAVOR, "MAY now has the favor lady")
|
||||
eq(A.lilycoveLady.favor.playerName, "BRENDAN", "carrying BRENDAN's data")
|
||||
eq(A.lilycoveLady.favor.state, Lady.STATE_READY, "ResetLilycoveLadyForRecordMix ran")
|
||||
eq(Lady.id(B), Lady.QUIZ, "BRENDAN now has the quiz lady")
|
||||
eq(B.lilycoveLady.quiz.playerName, "MAY", "carrying MAY's quiz")
|
||||
|
||||
local bb = hasBase(A, 21)
|
||||
check(bb ~= nil, "MAY registered BRENDAN's secret base")
|
||||
eq(bb and bb.trainerName, "BRENDAN", "owned by BRENDAN")
|
||||
eq(bb and bb.registryStatus, SB.UNREGISTERED, "NEW is cleared back to UNREGISTERED")
|
||||
check(hasBase(B, 11) ~= nil, "BRENDAN registered MAY's secret base")
|
||||
eq(SB.base(A, 0).numSecretBasesReceived, 1, "numSecretBasesReceived counts the mix")
|
||||
|
||||
check(tvHasCaught(A), "BRENDAN's Pokemon Today show airs on MAY's TV")
|
||||
|
||||
eq(Daycare.stateOf(A).mail[1], nil, "MAY's daycare mail swapped away")
|
||||
local bm = Daycare.stateOf(B).mail[1]
|
||||
eq(bm and bm.message.itemId, ORANGE_MAIL, "BRENDAN's daycare SEEDOT now carries MAY's mail")
|
||||
eq(bm and bm.otName, "MAY", "with MAY as the writer")
|
||||
|
||||
local tr = towerRecordNamed(A, "BRENDAN")
|
||||
eq(tr and tr.winStreak, 40, "BRENDAN's tower record joined MAY's towerRecords")
|
||||
eq(towerRecordNamed(B, "MAY") and towerRecordNamed(B, "MAY").winStreak, 20, "and MAY's joined BRENDAN's")
|
||||
|
||||
check(Bag.has(B.bag, EON_TICKET, 1), "BRENDAN received MAY's EON TICKET")
|
||||
eq(giftVar(B), EON_TICKET, "VAR_TEMP_RECORD_MIX_GIFT_ITEM holds it")
|
||||
use(B)
|
||||
check(Rse.flag("FLAG_ENABLE_SHIP_SOUTHERN_ISLAND", B), "FLAG_ENABLE_SHIP_SOUTHERN_ISLAND is set")
|
||||
check(not Bag.has(A.bag, EON_TICKET, 1), "the sender keeps no copy")
|
||||
eq(Gift.state(A).quantity, 0, "the one-use gift is spent")
|
||||
|
||||
local ap = apprenticeNamed(A, "BRENDAN")
|
||||
check(ap ~= nil, "MAY saved BRENDAN's apprentice")
|
||||
eq(A.playerApprentice.saveId, 1, "playerApprentice.saveId advanced")
|
||||
check(apprenticeNamed(B, "MAY") ~= nil, "BRENDAN saved MAY's apprentice")
|
||||
|
||||
local h = A.hallRecords1P[1][1][1]
|
||||
eq(h.name, "BRENDAN", "MAY's Ranking Hall shows BRENDAN")
|
||||
eq(h.winStreak, 40, "with his tower singles streak")
|
||||
eq(B.hallRecords1P[1][1][1].name, "MAY", "BRENDAN's Ranking Hall shows MAY")
|
||||
eq(A.hallRecords2P[1][1].name1 .. "/" .. A.hallRecords2P[1][1].winStreak, "BRENDAN/10", "link multi hall row filled")
|
||||
|
||||
print("[test] 3. the mixed records survive save and reload")
|
||||
local A2, B2 = reload(A), reload(B)
|
||||
eq(A2.oldMan and A2.oldMan.id, OldMan.GIDDY, "old man")
|
||||
eq(A2.lilycoveLady and A2.lilycoveLady.favor and A2.lilycoveLady.favor.playerName, "BRENDAN", "Lilycove lady")
|
||||
check(hasBase(A2, 21) ~= nil, "secret base registry")
|
||||
check(tvHasCaught(A2), "TV show")
|
||||
local bm2 = Daycare.stateOf(B2).mail[1]
|
||||
eq(bm2 and bm2.message.itemId, ORANGE_MAIL, "daycare mail")
|
||||
eq(towerRecordNamed(A2, "BRENDAN") and towerRecordNamed(A2, "BRENDAN").winStreak, 40, "tower record")
|
||||
check(Bag.has(B2.bag, EON_TICKET, 1), "gift item")
|
||||
check(apprenticeNamed(A2, "BRENDAN") ~= nil, "apprentice")
|
||||
eq(A2.playerApprentice and A2.playerApprentice.saveId, 1, "apprentice save id")
|
||||
eq(A2.hallRecords1P[1][1][1].name, "BRENDAN", "ranking hall")
|
||||
local g2 = reload(A)
|
||||
eq(g2.recordMixingGift and g2.recordMixingGift.itemId, 0, "record mixing gift block")
|
||||
end
|
||||
|
||||
print("[test] 4. three players follow pret's partner table")
|
||||
do
|
||||
local A = makePlayer(1, "MAY", 1116, 1)
|
||||
local B = makePlayer(2, "BRENDAN", 2228, 0)
|
||||
local C = makePlayer(3, "CHRIS", 3340, 0)
|
||||
giveShow(C)
|
||||
local mail = { message = { itemId = ORANGE_MAIL, words = { 9, 9, 9, 9, 9, 9, 9, 9, 9 }, playerName = "MAY",
|
||||
trainerId = 1116, species = SEEDOT }, otName = "MAY", monName = "SEEDOT" }
|
||||
Daycare.stateOf(A).mail[1] = mail
|
||||
Gift.set(1, 2, EON_TICKET, A)
|
||||
eq(OldMan.current(C), OldMan.BARD, "CHRIS has the bard")
|
||||
eq(Lady.id(C), Lady.CONTEST, "CHRIS has the contest lady")
|
||||
local order = MixUtil.shuffle({ { linkTrainerId = MixUtil.sessionLinkTrainerId(A) }, {}, {} })
|
||||
eq(order[1] .. order[2] .. order[3], "231", "MAY's even id picks {1, 2, 0}")
|
||||
local _, packets = mix({ A, B, C })
|
||||
local randSum = DaycareMail.randSum(packets[1].tvShows)
|
||||
|
||||
eq(OldMan.current(A), OldMan.GIDDY, "MAY <- BRENDAN's Giddy")
|
||||
eq(OldMan.current(B), OldMan.BARD, "BRENDAN <- CHRIS's bard")
|
||||
eq(B.oldMan.playerName, "CHRIS", "the bard keeps CHRIS's song credit")
|
||||
eq(OldMan.current(C), OldMan.STORYTELLER, "CHRIS <- MAY's storyteller")
|
||||
eq(Lady.id(A), Lady.FAVOR, "MAY <- favor lady")
|
||||
eq(Lady.id(B), Lady.CONTEST, "BRENDAN <- contest lady")
|
||||
eq(B.lilycoveLady.contest.playerName, "CHRIS", "with CHRIS's contest data")
|
||||
eq(Lady.id(C), Lady.QUIZ, "CHRIS <- quiz lady")
|
||||
eq(C.lilycoveLady.quiz.playerName, "MAY", "with MAY's quiz")
|
||||
|
||||
check(hasBase(A, 21) and hasBase(A, 31), "MAY registered both partners' bases")
|
||||
check(hasBase(B, 11) and hasBase(B, 31), "BRENDAN registered both partners' bases")
|
||||
check(hasBase(C, 11) and hasBase(C, 21), "CHRIS registered both partners' bases")
|
||||
|
||||
check((tvHasCaught(A) and 1 or 0) + (tvHasCaught(B) and 1 or 0) == 1, "CHRIS's show airs on exactly one partner's TV (tv.c:3449)")
|
||||
|
||||
local swap = ({ { 0, 1 }, { 1, 2 }, { 2, 0 } })[randSum % 3 + 1]
|
||||
local holder = (swap[1] == 0 and swap[2]) or (swap[2] == 0 and swap[1]) or 0
|
||||
local holders = {}
|
||||
for i, s in ipairs({ A, B, C }) do
|
||||
local m = Daycare.stateOf(s).mail[1]
|
||||
if m and m.message.itemId == ORANGE_MAIL then holders[#holders + 1] = i - 1 end
|
||||
end
|
||||
eq(#holders, 1, "MAY's mail exists exactly once after the swap")
|
||||
eq(holders[1], holder, "sDaycareMailSwapIds_3Player[" .. (randSum % 3) .. "] decides who holds it")
|
||||
|
||||
eq(towerRecordNamed(A, "BRENDAN") and towerRecordNamed(A, "BRENDAN").winStreak, 40, "MAY <- BRENDAN's tower record")
|
||||
eq(towerRecordNamed(B, "CHRIS") and towerRecordNamed(B, "CHRIS").winStreak, 60, "BRENDAN <- CHRIS's tower record")
|
||||
eq(towerRecordNamed(C, "MAY") and towerRecordNamed(C, "MAY").winStreak, 20, "CHRIS <- MAY's tower record")
|
||||
check(towerRecordNamed(A, "CHRIS") == nil, "MAY does not take CHRIS's tower record")
|
||||
|
||||
check(Bag.has(B.bag, EON_TICKET, 1) and Bag.has(C.bag, EON_TICKET, 1), "both partners get the leader's gift")
|
||||
|
||||
check(apprenticeNamed(A, "BRENDAN") ~= nil, "MAY <- BRENDAN's apprentice")
|
||||
check(apprenticeNamed(B, "CHRIS") ~= nil, "BRENDAN <- CHRIS's apprentice")
|
||||
check(apprenticeNamed(C, "MAY") ~= nil, "CHRIS <- MAY's apprentice")
|
||||
|
||||
local hall = A.hallRecords1P[1][1]
|
||||
eq(hall[1].name .. "/" .. hall[1].winStreak, "CHRIS/60", "MAY's hall ranks CHRIS first")
|
||||
eq(hall[2].name .. "/" .. hall[2].winStreak, "BRENDAN/40", "then BRENDAN")
|
||||
local hallC = C.hallRecords1P[1][1]
|
||||
eq(hallC[1].name .. "/" .. hallC[2].name, "BRENDAN/MAY", "CHRIS ranks BRENDAN over MAY")
|
||||
|
||||
local C2 = reload(C)
|
||||
eq(C2.oldMan.id, OldMan.STORYTELLER, "CHRIS's old man survives reload")
|
||||
eq(C2.hallRecords1P[1][1][1].name, "BRENDAN", "and his hall records")
|
||||
check(hasBase(C2, 11) and hasBase(C2, 21), "and both bases")
|
||||
end
|
||||
|
||||
T.finish("emerald_record_mix")
|
||||
Reference in New Issue
Block a user