macros: a push-back is a refusal once the overworld has stayed the fly's for half a second, not on the first frame of it
This commit is contained in:
parent
d85ee5026f
commit
4108fec5ce
2 changed files with 68 additions and 9 deletions
|
|
@ -149,6 +149,12 @@ const ANSWER_REOPEN_FRAMES: u32 = 24;
|
||||||
/// animation is about three.
|
/// animation is about three.
|
||||||
const HEAL_WAIT_FRAMES: u32 = 360;
|
const HEAL_WAIT_FRAMES: u32 = 360;
|
||||||
|
|
||||||
|
/// Frames running the overworld has to be the fly's before a push-back is written as a refusal
|
||||||
|
/// ([`MacroMachine::observe_push`], row 59). A trainer's challenge text closes onto five frames of
|
||||||
|
/// overworld before the battle is decided; six times that is still half a second, and a refusal
|
||||||
|
/// the cartridge really made loses nothing by being written half a second late.
|
||||||
|
pub const PUSH_SETTLE_FRAMES: u32 = 30;
|
||||||
|
|
||||||
/// How a macro ended, i.e. the `outcome` field of the `macro` feed event (section 5: "outcome =
|
/// How a macro ended, i.e. the `outcome` field of the `macro` feed event (section 5: "outcome =
|
||||||
/// done/blocked/timeout/refused").
|
/// done/blocked/timeout/refused").
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
@ -662,6 +668,9 @@ pub struct MacroMachine {
|
||||||
/// the overworld is a refusal and is written as one; a battle is a battle, and nothing about
|
/// the overworld is a refusal and is written as one; a battle is a battle, and nothing about
|
||||||
/// the target or the ground is learned from it.
|
/// the target or the ground is learned from it.
|
||||||
pending_push: Vec<PendingPush>,
|
pending_push: Vec<PendingPush>,
|
||||||
|
/// How many frames running the overworld has been the fly's while [`Self::pending_push`]
|
||||||
|
/// waits: the push-back is decided at [`PUSH_SETTLE_FRAMES`] (row 59).
|
||||||
|
pending_push_calm: u32,
|
||||||
/// A finished `TALK`'s target, waiting to be taken into the session's talked ledger.
|
/// A finished `TALK`'s target, waiting to be taken into the session's talked ledger.
|
||||||
///
|
///
|
||||||
/// The machine records rather than keeps: the ledger is the driver's
|
/// The machine records rather than keeps: the ledger is the driver's
|
||||||
|
|
@ -696,6 +705,7 @@ impl MacroMachine {
|
||||||
pending_talk: None,
|
pending_talk: None,
|
||||||
pending_answer: None,
|
pending_answer: None,
|
||||||
pending_push: Vec::new(),
|
pending_push: Vec::new(),
|
||||||
|
pending_push_calm: 0,
|
||||||
talked: None,
|
talked: None,
|
||||||
rng: if seed == 0 { 1 } else { seed },
|
rng: if seed == 0 { 1 } else { seed },
|
||||||
}
|
}
|
||||||
|
|
@ -979,6 +989,7 @@ impl MacroMachine {
|
||||||
self.pending_answer = None;
|
self.pending_answer = None;
|
||||||
// Nor the cartridge refusing a step: the frames it happened in are being thrown away too.
|
// Nor the cartridge refusing a step: the frames it happened in are being thrown away too.
|
||||||
self.pending_push.clear();
|
self.pending_push.clear();
|
||||||
|
self.pending_push_calm = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the fly is standing somewhere other than where the running macro began.
|
/// Whether the fly is standing somewhere other than where the running macro began.
|
||||||
|
|
@ -1036,17 +1047,32 @@ impl MacroMachine {
|
||||||
|
|
||||||
/// One frame after the cartridge took the joypad from a macro: decide what it was (row 58).
|
/// One frame after the cartridge took the joypad from a macro: decide what it was (row 58).
|
||||||
///
|
///
|
||||||
/// Back in the overworld with the buttons the fly's again: a refusal, written exactly as
|
/// Back in the overworld with the buttons the fly's again, for [`PUSH_SETTLE_FRAMES`] running:
|
||||||
/// section 12.4 and row 37 always wrote it. A battle: a trainer's challenge, and it teaches the
|
/// a refusal, written exactly as section 12.4 and row 37 always wrote it. A battle: a
|
||||||
/// ledgers nothing. Anything else -- the text, the walk, the frames between -- is still the
|
/// trainer's challenge, and it teaches the ledgers nothing. Anything else -- the text, the
|
||||||
/// cartridge's, and the decision waits.
|
/// walk, the frames between -- is still the cartridge's, and the decision waits.
|
||||||
|
///
|
||||||
|
/// The window is row 59's. A trainer's challenge text closes onto five frames of an ordinary
|
||||||
|
/// overworld -- no text, no script, no joypad bit, `wCurOpponent` still clear -- before
|
||||||
|
/// `StartTrainerBattle` runs (`home/trainers.asm`: it follows `DisplayTextID`, whose
|
||||||
|
/// close-down redraws the map first). Decided on the first of them, Route 3's first trainer
|
||||||
|
/// walled (11, 6), the one gap between the road's west end and the rest of it, for the
|
||||||
|
/// session, and the fly walked between Pewter City and that end for hours.
|
||||||
fn observe_push(&mut self, state: &mut dyn MacroState) {
|
fn observe_push(&mut self, state: &mut dyn MacroState) {
|
||||||
if self.pending_push.is_empty() {
|
if self.pending_push.is_empty() {
|
||||||
|
self.pending_push_calm = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match class(state.scene()) {
|
match class(state.scene()) {
|
||||||
Class::Battle | Class::ForcedSwitch => self.pending_push.clear(),
|
Class::Battle | Class::ForcedSwitch => {
|
||||||
|
self.pending_push.clear();
|
||||||
|
self.pending_push_calm = 0;
|
||||||
|
}
|
||||||
|
Class::Overworld if self.pending_push_calm + 1 < PUSH_SETTLE_FRAMES => {
|
||||||
|
self.pending_push_calm += 1;
|
||||||
|
}
|
||||||
Class::Overworld => {
|
Class::Overworld => {
|
||||||
|
self.pending_push_calm = 0;
|
||||||
// Every macro the script ended while it held the joypad -- the walk it interrupted
|
// Every macro the script ended while it held the joypad -- the walk it interrupted
|
||||||
// and any press made into its text -- in the order they ended.
|
// and any press made into its text -- in the order they ended.
|
||||||
for pending in std::mem::take(&mut self.pending_push) {
|
for pending in std::mem::take(&mut self.pending_push) {
|
||||||
|
|
@ -1058,7 +1084,7 @@ impl MacroMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => self.pending_push_calm = 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -854,14 +854,16 @@ fn settle(machine: &mut MacroMachine, world: &mut World) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The cartridge gives the joypad back in the overworld: one frame of it, observed, and whatever
|
/// The cartridge gives the joypad back in the overworld: the settle window of it, observed, and
|
||||||
/// it decided taken into the ledgers (row 58).
|
/// whatever it decided taken into the ledgers (rows 58 and 59).
|
||||||
fn hand_back(machine: &mut MacroMachine, world: &mut World) {
|
fn hand_back(machine: &mut MacroMachine, world: &mut World) {
|
||||||
world.scene = Scene::Overworld;
|
world.scene = Scene::Overworld;
|
||||||
world.scripted = false;
|
world.scripted = false;
|
||||||
world.scripted_at = None;
|
world.scripted_at = None;
|
||||||
world.switch = None;
|
world.switch = None;
|
||||||
|
for _ in 0..super::executor::PUSH_SETTLE_FRAMES {
|
||||||
machine.observe_frame(world);
|
machine.observe_frame(world);
|
||||||
|
}
|
||||||
settle(machine, world);
|
settle(machine, world);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5428,6 +5430,37 @@ fn facing_one_of_the_rungs_people_is_the_arrival() {
|
||||||
assert!(on_the_pad(&mut world, MacroKind::GoObjective), "turned away, the walk is back");
|
assert!(on_the_pad(&mut world, MacroKind::GoObjective), "turned away, the walk is back");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_challenge_closing_onto_a_few_frames_of_overworld_is_still_a_challenge() {
|
||||||
|
// Row 59, Route 3's first trainer. The challenge text closes, and for five frames the screen
|
||||||
|
// is an ordinary overworld with nothing set -- no text, no script, no joypad bit, the battle
|
||||||
|
// not yet decided -- before `StartTrainerBattle` runs. Decided on the first of them, the walk
|
||||||
|
// the trainer interrupted walled (11, 6) for the session: the one gap east on Route 3.
|
||||||
|
let mut world = World::room().at(3, 3);
|
||||||
|
world.map = 0x00;
|
||||||
|
world.connections = Connections { north: true, south: false, east: false, west: false };
|
||||||
|
world.switch = Some((4, Scene::Dialog));
|
||||||
|
world.scripted_at = Some(4);
|
||||||
|
let north = TargetKey::Exit(ExitId::Edge(Edge::North));
|
||||||
|
let mut machine = MacroMachine::new(0x1234_5678);
|
||||||
|
assert_eq!(run_with(&mut machine, &mut world, MacroKind::GoRoute), Ok(MacroAbort::Done));
|
||||||
|
|
||||||
|
// The text closes onto five frames of overworld, and then the battle is decided.
|
||||||
|
world.scene = Scene::Overworld;
|
||||||
|
world.scripted = false;
|
||||||
|
world.scripted_at = None;
|
||||||
|
world.switch = None;
|
||||||
|
for _ in 0..5 {
|
||||||
|
machine.observe_frame(&mut world);
|
||||||
|
}
|
||||||
|
world.scene = Scene::Battle { own_turn: false, forced_switch: false };
|
||||||
|
machine.observe_frame(&mut world);
|
||||||
|
settle(&mut machine, &mut world);
|
||||||
|
hand_back(&mut machine, &mut world);
|
||||||
|
assert!(!world.targets.blocked(world.map, north), "a challenge is not the road refusing");
|
||||||
|
assert!(world.pushes.is_empty(), "and the gap it was walking through is still ground");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_trainer_walking_up_teaches_the_ledgers_nothing() {
|
fn a_trainer_walking_up_teaches_the_ledgers_nothing() {
|
||||||
// The other half of the gym. A walk toward the leader crossed the Jr. Trainer's line of sight;
|
// The other half of the gym. A walk toward the leader crossed the Jr. Trainer's line of sight;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue