state: a trainer's challenge is the cartridge's until its battle is over, in the macros' own scene
This commit is contained in:
parent
16bc65b5a8
commit
0e366cd4e4
3 changed files with 84 additions and 2 deletions
|
|
@ -616,6 +616,28 @@ mod tests {
|
||||||
assert_eq!(palette.observe(&mut wram, &NoLedger).scene, SceneId::Unknown);
|
assert_eq!(palette.observe(&mut wram, &NoLedger).scene, SceneId::Unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_frames_between_a_trainers_text_and_its_battle_deal_no_pad_and_record_no_ground() {
|
||||||
|
// Row 61, Viridian Forest. A trainer who saw the fly: its challenge text closes onto five
|
||||||
|
// frames that read as an ordinary overworld -- no box, no script bit, `wCurOpponent`
|
||||||
|
// still zero -- before the battle is decided. The pad was dealt there and the push-back
|
||||||
|
// the fly's walk had earned when the trainer took the joypad was written there, walling
|
||||||
|
// the one free tile of the corridor to the north gate for the session.
|
||||||
|
let mut wram = Wram::overworld();
|
||||||
|
wram.set(crate::pokemon_red::symbols::ram::wStatusFlags7, 1 << 3);
|
||||||
|
let mut palette = PokemonPalette::new(7);
|
||||||
|
let engaged = palette.observe(&mut wram, &NoLedger);
|
||||||
|
assert_eq!(engaged.scene, SceneId::Unknown, "the cartridge's, between text and battle");
|
||||||
|
assert!(engaged.bindings.is_empty(), "nothing to press: {:?}", engaged.bindings);
|
||||||
|
assert_eq!(palette.stood(), 0, "and no ground recorded from it");
|
||||||
|
|
||||||
|
// `.battleOccurred` clears the bit and the overworld is the fly's again.
|
||||||
|
wram.set(crate::pokemon_red::symbols::ram::wStatusFlags7, 0);
|
||||||
|
let own = palette.observe(&mut wram, &NoLedger);
|
||||||
|
assert_eq!(own.scene, SceneId::Overworld);
|
||||||
|
assert_eq!(palette.stood(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_teleport_pad_is_not_a_tear() {
|
fn a_teleport_pad_is_not_a_tear() {
|
||||||
// Saffron Gym and two Silph Co. floors warp to themselves. Standing on a pad whose
|
// Saffron Gym and two Silph Co. floors warp to themselves. Standing on a pad whose
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,11 @@ pub mod poke {
|
||||||
/// zero in the overworld, non-zero from the frame a trainer's challenge closes to the end of
|
/// zero in the overworld, non-zero from the frame a trainer's challenge closes to the end of
|
||||||
/// the battle, including the 219 frames of the battle transition in between.
|
/// the battle, including the 219 frames of the battle transition in between.
|
||||||
pub const CUR_OPPONENT: u16 = super::ram::wBattleType - 1;
|
pub const CUR_OPPONENT: u16 = super::ram::wBattleType - 1;
|
||||||
|
/// `constants/ram_constants.asm`: `wStatusFlags7` bit 3, `BIT_TRAINER_BATTLE` (row 61). Set by
|
||||||
|
/// `CheckFightingMapTrainers` (`home/trainers.asm`) on the frame a trainer sees the player,
|
||||||
|
/// cleared at `.battleOccurred` (`home/overworld.asm`) once the battle is over -- before the
|
||||||
|
/// blackout check, so a lost battle clears it too. Nothing else writes it.
|
||||||
|
pub const TRAINER_BATTLE_STATUS7: u8 = 1 << 3;
|
||||||
|
|
||||||
/// `constants/battle_constants.asm`: the non-volatile status byte.
|
/// `constants/battle_constants.asm`: the non-volatile status byte.
|
||||||
pub const SLP_MASK: u8 = 0b111;
|
pub const SLP_MASK: u8 = 0b111;
|
||||||
|
|
@ -1841,8 +1846,14 @@ impl<'a> PokeState<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameState for PokeState<'_> {
|
impl GameState for PokeState<'_> {
|
||||||
|
/// [`super::scene::detect`], except that an overworld frame inside a trainer's challenge is
|
||||||
|
/// the cartridge's ([`trainer_engaged`], row 61): section 12.13's `Unknown` with no text box,
|
||||||
|
/// an empty pad the fly waits out, and no frame a held push-back is decided on.
|
||||||
fn scene(&mut self) -> Scene {
|
fn scene(&mut self) -> Scene {
|
||||||
super::scene::detect(self.memory)
|
match super::scene::detect(self.memory) {
|
||||||
|
Scene::Overworld if trainer_engaged(self.memory) => Scene::Unknown,
|
||||||
|
scene => scene,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn player(&mut self) -> Option<Player> {
|
fn player(&mut self) -> Option<Player> {
|
||||||
|
|
@ -1912,6 +1923,24 @@ impl GameState for PokeState<'_> {
|
||||||
|
|
||||||
/// The cartridge tables on their defaults, and the exploration ledger wired through.
|
/// The cartridge tables on their defaults, and the exploration ledger wired through.
|
||||||
///
|
///
|
||||||
|
/// Whether a trainer who saw the player is between its "!" and the end of its battle (row 61).
|
||||||
|
///
|
||||||
|
/// `DisplayEnemyTrainerTextAndStartBattle` (`home/trainers.asm`) clears `wJoyIgnore` before the
|
||||||
|
/// challenge text and calls `StartTrainerBattle`, which writes `wCurOpponent`, only after that
|
||||||
|
/// text's close-down has redrawn the map. Measured in Viridian Forest: five frames with the box
|
||||||
|
/// gone, every bit [`controllable`] reads clear and `wCurOpponent` still zero, and then the
|
||||||
|
/// battle. The macro seam read them as an overworld the fly owned, so the push-back a walk earned
|
||||||
|
/// when the trainer took the joypad (row 58's held entry) was written on the first of them: the
|
||||||
|
/// one free tile beside the trainer, in the only corridor to the forest's north gate, walled for
|
||||||
|
/// the session.
|
||||||
|
///
|
||||||
|
/// **The macros' reading only.** [`controllable`] and [`super::scene::detect`] are shared with
|
||||||
|
/// the reward adapter (the talk payout's "ready" test, the feed's scene), which this row does not
|
||||||
|
/// change; [`PokeState`]'s own `scene` and `scripted` read this beside them.
|
||||||
|
pub fn trainer_engaged(memory: &mut dyn MemoryReader) -> bool {
|
||||||
|
read(memory, ram::wStatusFlags7) & poke::TRAINER_BATTLE_STATUS7 != 0
|
||||||
|
}
|
||||||
|
|
||||||
/// `pokemon_red/macros/cartridge.rs` defaults every [`MacroState`] method and every default
|
/// `pokemon_red/macros/cartridge.rs` defaults every [`MacroState`] method and every default
|
||||||
/// *narrows* what the palette offers, so the executor runs over live WRAM with no overrides at
|
/// *narrows* what the palette offers, so the executor runs over live WRAM with no overrides at
|
||||||
/// all and each one turned on later widens it without changing a signature. Two are still on
|
/// all and each one turned on later widens it without changing a signature. Two are still on
|
||||||
|
|
@ -1923,7 +1952,7 @@ impl GameState for PokeState<'_> {
|
||||||
/// taken rather than at the nearest door (`docs/design/macros.md` section 3).
|
/// taken rather than at the nearest door (`docs/design/macros.md` section 3).
|
||||||
impl MacroState for PokeState<'_> {
|
impl MacroState for PokeState<'_> {
|
||||||
fn scripted(&mut self) -> bool {
|
fn scripted(&mut self) -> bool {
|
||||||
!controllable(self.memory)
|
!controllable(self.memory) || trainer_engaged(self.memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_open(&mut self) -> bool {
|
fn text_open(&mut self) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -1222,3 +1222,34 @@ fn a_refusal_that_cannot_be_read_is_not_reported() {
|
||||||
read.set(ram::wEnemyMonStatMods + 1, 1);
|
read.set(ram::wEnemyMonStatMods + 1, 1);
|
||||||
assert!(PokeState::new(&mut read).move_without_effect(TAIL_WHIP.0));
|
assert!(PokeState::new(&mut read).move_without_effect(TAIL_WHIP.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_trainers_challenge_is_the_cartridges_until_its_battle_is_over() {
|
||||||
|
use crate::pokemon_red::macros::state::GameState;
|
||||||
|
// Row 61. A trainer who saw the fly: its text closes onto five frames with every bit
|
||||||
|
// `controllable` reads clear and `wCurOpponent` still zero, then the battle. The macros read
|
||||||
|
// them as the cartridge's; the shared readings the reward adapter and the feed use do not
|
||||||
|
// move.
|
||||||
|
let mut wram = Wram::overworld();
|
||||||
|
assert_eq!(PokeState::new(&mut wram).scene(), Scene::Overworld);
|
||||||
|
assert!(!PokeState::new(&mut wram).scripted());
|
||||||
|
|
||||||
|
wram.set(ram::wStatusFlags7, poke::TRAINER_BATTLE_STATUS7);
|
||||||
|
assert!(trainer_engaged(&mut wram));
|
||||||
|
assert_eq!(PokeState::new(&mut wram).scene(), Scene::Unknown, "between the text and the battle");
|
||||||
|
assert!(PokeState::new(&mut wram).scripted(), "and the fly is not its own master");
|
||||||
|
assert!(controllable(&mut wram), "the adapter's gate is unchanged");
|
||||||
|
assert_eq!(crate::pokemon_red::scene::detect(&mut wram), Scene::Overworld, "and the feed's scene");
|
||||||
|
|
||||||
|
// The challenge's own text is still a conversation to advance.
|
||||||
|
wram.dialogue_box();
|
||||||
|
assert_eq!(PokeState::new(&mut wram).scene(), Scene::Dialog);
|
||||||
|
|
||||||
|
// `.battleOccurred` clears the bit; the other bits of the byte are not a challenge:
|
||||||
|
// `BIT_NO_MAP_MUSIC` after a rival, `BIT_USE_CUR_MAP_SCRIPT` from a trainer talked to.
|
||||||
|
let mut after = Wram::overworld();
|
||||||
|
after.set(ram::wStatusFlags7, (1 << 1) | (1 << 4) | (1 << 7));
|
||||||
|
assert!(!trainer_engaged(&mut after));
|
||||||
|
assert_eq!(PokeState::new(&mut after).scene(), Scene::Overworld);
|
||||||
|
assert!(!PokeState::new(&mut after).scripted());
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue