add skip flags, fix pre-save 'do you want to retry'

This commit is contained in:
Alula 2021-05-02 02:09:54 +02:00
parent 0db2e02181
commit 4f34e33b57
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
3 changed files with 73 additions and 22 deletions

View File

@ -222,6 +222,7 @@ impl Scene for TitleScene {
}, },
CurrentMenu::StartGame => { CurrentMenu::StartGame => {
if self.tick == 10 { if self.tick == 10 {
state.reset_skip_flags();
state.start_new_game(ctx)?; state.start_new_game(ctx)?;
} }
} }

View File

@ -30,6 +30,7 @@ use crate::stage::StageData;
use crate::str; use crate::str;
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM}; use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
use crate::texture_set::TextureSet; use crate::texture_set::TextureSet;
use bitvec::array::BitArray;
#[derive(PartialEq, Eq, Copy, Clone)] #[derive(PartialEq, Eq, Copy, Clone)]
pub enum TimingMode { pub enum TimingMode {
@ -89,6 +90,7 @@ pub struct SharedGameState {
pub timing_mode: TimingMode, pub timing_mode: TimingMode,
pub control_flags: ControlFlags, pub control_flags: ControlFlags,
pub game_flags: BitVec, pub game_flags: BitVec,
pub skip_flags: BitVec,
pub fade_state: FadeState, pub fade_state: FadeState,
/// RNG used by game state, using it for anything else might cause unintended side effects and break replays. /// RNG used by game state, using it for anything else might cause unintended side effects and break replays.
pub game_rng: XorShift, pub game_rng: XorShift,
@ -160,6 +162,7 @@ impl SharedGameState {
timing_mode: TimingMode::_50Hz, timing_mode: TimingMode::_50Hz,
control_flags: ControlFlags(0), control_flags: ControlFlags(0),
game_flags: bitvec::bitvec![0; 8000], game_flags: bitvec::bitvec![0; 8000],
skip_flags: bitvec::bitvec![0; 64],
fade_state: FadeState::Hidden, fade_state: FadeState::Hidden,
game_rng: XorShift::new(0), game_rng: XorShift::new(0),
effect_rng: XorShift::new(123), effect_rng: XorShift::new(123),
@ -364,4 +367,24 @@ impl SharedGameState {
false false
} }
} }
pub fn reset_skip_flags(&mut self) {
self.skip_flags = bitvec::bitvec![0; 64];
}
pub fn set_skip_flag(&mut self, id: usize, value: bool) {
if id < self.skip_flags.len() {
self.skip_flags.set(id, value);
} else {
log::warn!("Attempted to set an out-of-bounds skip flag {}:", id);
}
}
pub fn get_skip_flag(&self, id: usize) -> bool {
if let Some(flag) = self.skip_flags.get(id) {
*flag
} else {
false
}
}
} }

View File

@ -262,16 +262,18 @@ pub enum OpCode {
SSS, SSS,
// ---- Cave Story+ specific opcodes ---- // ---- Cave Story+ specific opcodes ----
/// <ACHxxxx, triggers a Steam achievement. /// <ACHxxxx, triggers a Steam achievement. No-op in EGS/Humble Bundle version.
ACH, ACH,
// ---- Cave Story+ (Switch) specific opcodes ---- // ---- Cave Story+ (Switch) specific opcodes ----
/// <HM2, HMC for non-executor player. /// <HM2, HMC only for executor player.
HM2, HM2,
/// <2MVxxxx, Put another player near the player who executed the event. /// <2MVxxxx, Put another player near the player who executed the event.
/// 0000 - puts player on left side of executor player /// 0000 - puts player on left side of executor player
/// 0001 - puts player on right side of executor player /// 0001 - puts player on right side of executor player
/// other values - Unknown purpose for now /// 0002-0010 - unused
/// 0011.. - the first 3 digits are distance in pixels, the last digit is a flag
/// - if it's 1 put the player on right side of the player, otherwise put it on left
#[strum(serialize = "2MV")] #[strum(serialize = "2MV")]
S2MV, S2MV,
/// <INJxxxx:yyyy:zzzz, Jumps to event zzzz if amount of item xxxx equals yyyy /// <INJxxxx:yyyy:zzzz, Jumps to event zzzz if amount of item xxxx equals yyyy
@ -279,7 +281,7 @@ pub enum OpCode {
/// <I+Nxxxx:yyyy, Adds item xxxx with maximum amount of yyyy /// <I+Nxxxx:yyyy, Adds item xxxx with maximum amount of yyyy
#[strum(serialize = "I+N")] #[strum(serialize = "I+N")]
IpN, IpN,
/// <FF-xxxx:yyyy, Set flags in range xxxx-yyyy to false /// <FF-xxxx:yyyy, Sets first flag in range xxxx-yyyy to false
#[strum(serialize = "FF-")] #[strum(serialize = "FF-")]
FFm, FFm,
/// <PSHxxxx, Pushes text script state to stack and starts event xxxx /// <PSHxxxx, Pushes text script state to stack and starts event xxxx
@ -757,7 +759,8 @@ impl TextScriptVM {
break; break;
} }
TextScriptExecutionState::Reset => { TextScriptExecutionState::Reset => {
state.start_intro(ctx)?; state.reset();
state.start_new_game(ctx)?;
break; break;
} }
} }
@ -937,8 +940,13 @@ impl TextScriptVM {
exec_state = TextScriptExecutionState::WaitInput(event, cursor.position() as u32, 0); exec_state = TextScriptExecutionState::WaitInput(event, cursor.position() as u32, 0);
} }
OpCode::FLp | OpCode::FLm => { OpCode::FLp | OpCode::FLm => {
let flag_num = read_cur_varint(&mut cursor)? as usize; let flag_num = read_cur_varint(&mut cursor)? as u16;
state.game_flags.set(flag_num, op == OpCode::FLp); state.set_flag(flag_num as usize, op == OpCode::FLp);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::SKp | OpCode::SKm => {
let flag_num = read_cur_varint(&mut cursor)? as u16;
state.set_skip_flag(flag_num as usize, op == OpCode::SKp);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
} }
OpCode::FFm => { OpCode::FFm => {
@ -1023,6 +1031,17 @@ impl TextScriptVM {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
} }
} }
OpCode::SKJ => {
let flag_id = read_cur_varint(&mut cursor)? as u16;
let event_num = read_cur_varint(&mut cursor)? as u16;
if state.get_skip_flag(flag_id as usize) {
state.textscript_vm.clear_text_box();
exec_state = TextScriptExecutionState::Running(event_num, 0);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
}
OpCode::EVE => { OpCode::EVE => {
let event_num = read_cur_varint(&mut cursor)? as u16; let event_num = read_cur_varint(&mut cursor)? as u16;
@ -1035,7 +1054,7 @@ impl TextScriptVM {
let saved_state = TextScriptExecutionState::Running(event, cursor.position() as u32); let saved_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
state.textscript_vm.stack.push(saved_state); state.textscript_vm.stack.push(saved_state);
// TODO: it's a jump but we don't know if it cleans the textbox yet. state.textscript_vm.clear_text_box();
exec_state = TextScriptExecutionState::Running(event_num, 0); exec_state = TextScriptExecutionState::Running(event_num, 0);
} }
OpCode::POP => { OpCode::POP => {
@ -1231,11 +1250,30 @@ impl TextScriptVM {
partner.x = executor.x + if param == 0 { -16 * 0x200 } else { 16 * 0x200 }; partner.x = executor.x + if param == 0 { -16 * 0x200 } else { 16 * 0x200 };
partner.y = executor.y; partner.y = executor.y;
} }
_ => { 2..=10 => {
log::warn!("stub: <2MV unknown param"); log::warn!("<2MV unknown param");
}
// what the fuck
i => {
let distance = i as i32 / 10;
partner.vel_x = 0;
partner.vel_y = 0;
partner.x = executor.x + if (param % 10) == 1 { distance * 0x200 } else { -distance * 0x200 };
partner.y = executor.y;
} }
} }
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
npc.x = partner.x;
npc.y = partner.y;
game_scene.npc_list.spawn(0x100, npc.clone())?;
game_scene.npc_list.spawn(0x100, npc.clone())?;
game_scene.npc_list.spawn(0x100, npc.clone())?;
game_scene.npc_list.spawn(0x100, npc)?;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
} }
OpCode::UNI => { OpCode::UNI => {
@ -1657,8 +1695,6 @@ impl TextScriptVM {
} }
// One operand codes // One operand codes
OpCode::MPp OpCode::MPp
| OpCode::SKm
| OpCode::SKp
| OpCode::UNJ | OpCode::UNJ
| OpCode::MPJ | OpCode::MPJ
| OpCode::XX1 | OpCode::XX1
@ -1669,15 +1705,6 @@ impl TextScriptVM {
log::warn!("unimplemented opcode: {:?} {}", op, par_a); log::warn!("unimplemented opcode: {:?} {}", op, par_a);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
// Two operand codes
OpCode::SKJ => {
let par_a = read_cur_varint(&mut cursor)?;
let par_b = read_cur_varint(&mut cursor)?;
log::warn!("unimplemented opcode: {:?} {} {}", op, par_a, par_b);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
} }
} }