-- The Viridian Trainer House: the one battle a day against CAL in the -- basement's TRAINING HALL (maps/TrainerHouseB1F.asm). -- -- The conversation itself is script bytecode and stays in the cache: the -- coord_event on the doorway cell runs TrainerHouseReceptionistScript, which -- checks ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY, asks `special TrainerHouse` -- whose opponent it is, walks the player into the room and starts the battle. -- What that script needs from the port is the three compiled routines behind -- it, and all three are about the SAME question: whether a Mystery Gift trade -- has left a custom trainer in SRAM. -- -- TrainerHouse engine/events/specials.asm -- reads -- sMysteryGiftTrainerHouseFlag into wScriptVar, so -- the script picks CAL2 (the visitor) or CAL3 (the -- house's own trainer). -- ReadTrainerParty engine/battle/read_trainer_party.asm -- CAL2 is -- the ONLY trainer in the game whose party does not -- come from data/trainers/parties.asm. Its `.cal2` -- arm reads sMysteryGiftTrainer as a -- TRAINERTYPE_MOVES party instead. -- GetTrainerName same file -- and CAL is the only class whose name -- is not read from the parties table either: with -- the flag set it is copied out of -- sMysteryGiftPartnerName. -- -- MYSTERY GIFT IS OUT OF SCOPE (it is one of the six peripheral stubs in -- src/script/gen2/Specials.lua: the trade rides the Game Boy's infrared port -- into a second cartridge, and there is no second cartridge here). So the -- flag is permanently clear, which is exactly the state of a cartridge that -- has never been linked, and every one of the three routines above takes its -- own no-custom-data arm. That fallback is what this module ports: -- -- * the script asks once before the walk-in and once at the battle, gets -- FALSE both times, and fights CAL3 -- MEGANIUM, TYPHLOSION and FERALIGATR -- at level 50, the strongest of the three CAL rows; -- * a CAL2 lookup that reaches here anyway is answered with CAL3 rather than -- with parties.asm's CAL (2) row, because that row is DEAD DATA on the -- cart: `ReadTrainerParty` branches to SRAM before it ever indexes the -- table, so handing back BAYLEEF/QUILAVA/CROCONAW at level 30 would be a -- team no cartridge ever fields; -- * the name is the parties table's own "CAL". -- -- The once-a-day gate is not here: ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY is a -- wDailyFlags1 bit like Kurt's, so the script's own setflag is the whole of -- the write and src/core/gen2/Apricorns.lua's daily reset is the whole of the -- clear. The id below is for readers and for the test that pins the pair. local Trainers = require("src.world.gen2.Trainers") local TrainerHouse = {} -- constants/trainer_constants.asm: the CAL class and its three members. CAL1 -- is the Route 27 battle, CAL2 the Mystery Gift visitor, CAL3 the house's own. TrainerHouse.CAL = 12 TrainerHouse.CAL1, TrainerHouse.CAL2, TrainerHouse.CAL3 = 1, 2, 3 -- constants/engine_flags.asm index 86, wDailyFlags1 bit -- DAILYFLAGS1_FOUGHT_IN_TRAINER_HALL_TODAY. Cleared by -- Apricorns.dailyReset, which wipes both daily bytes whole. TrainerHouse.ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY = 86 -- sMysteryGiftTrainerHouseFlag (ram/sram.asm), the byte a completed Mystery -- Gift trade leaves behind. STUB, and a deliberate one: nothing in this port -- can set it, because nothing in this port can run the infrared trade that -- writes it (engine/link/mystery_gift.asm). Kept as a function rather than as -- a constant `false` so the day Mystery Gift lands there is one place to teach -- about save.mysteryGift, and so the two readers below cannot drift apart. function TrainerHouse.hasCustomTrainer(save) local gift = type(save) == "table" and save.mysteryGift or nil return (gift and gift.trainerHouse) and true or false end -- ReadTrainerParty's `cp CAL / cp CAL2` pair, as the question a caller with a -- class and a member can ask: which member should actually be loaded. Only -- CAL2 is ever redirected, and only when there is no custom trainer to redirect -- it to -- with one in SRAM the cart reads the party out of SRAM and this -- would have nothing to say about it either. function TrainerHouse.resolveMember(save, class, member) if class == TrainerHouse.CAL and member == TrainerHouse.CAL2 and not TrainerHouse.hasCustomTrainer(save) then return TrainerHouse.CAL3 end return member end -- GetTrainerName's CAL arm. Returns the name the SRAM copy would have -- supplied, or nil for "fall through to the parties table", which is what -- `.not_cal2` does. nil rather than "CAL" on purpose: the caller already has -- the table, and inventing the answer here would hide a lookup that failed. function TrainerHouse.customName(save, class) if class ~= TrainerHouse.CAL then return nil end if not TrainerHouse.hasCustomTrainer(save) then return nil end local gift = save and save.mysteryGift return gift and gift.partnerName or nil end -- The pair of lookups the World hands to the VM, with the CAL2 redirect -- applied. `trainerData` is the cache's trainers table. function TrainerHouse.lookup(trainerData, save, class, member) return Trainers.lookup(trainerData, class, TrainerHouse.resolveMember(save, class, member)) end function TrainerHouse.name(trainerData, save, class, member) local custom = TrainerHouse.customName(save, class) if custom then return custom end local entry = TrainerHouse.lookup(trainerData, save, class, member) return entry and entry.name or nil end -- The daily gate, for a reader holding nothing but a save file. The script -- owns both sides of it in game (checkflag / setflag), so neither of these has -- a call site in the engine: they exist so the test can state the rule, and so -- a future rematch feature has one name for the bit rather than the number 86 -- written out again. function TrainerHouse.foughtToday(save) local flags = type(save) == "table" and save.engineFlags or nil return (flags and flags[TrainerHouse.ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY]) == true end return TrainerHouse