mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-03-24 19:09:22 +00:00
Initial challenge replay support
This commit is contained in:
parent
0387a450ce
commit
15010e54c2
|
@ -7,8 +7,8 @@ use crate::framework::error::GameResult;
|
|||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::inventory::Inventory;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::weapon::{WeaponLevel, WeaponType};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
|
@ -62,13 +62,12 @@ impl InventoryUI {
|
|||
inventory.get_item_idx(self.selected_item as usize).map(|i| i.0 + 6000).unwrap_or(6000)
|
||||
}
|
||||
|
||||
fn exit(&mut self, state: &mut SharedGameState, player: &mut Player, inventory: &mut Inventory) {
|
||||
fn exit(&mut self, state: &mut SharedGameState, _player: &mut Player, inventory: &mut Inventory) {
|
||||
self.focus = InventoryFocus::None;
|
||||
inventory.current_item = 0;
|
||||
self.text_y_pos = 16;
|
||||
state.textscript_vm.reset();
|
||||
state.textscript_vm.set_mode(ScriptMode::Map);
|
||||
player.controller.update_trigger();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ pub mod inventory;
|
|||
pub mod map_system;
|
||||
pub mod nikumaru;
|
||||
pub mod number_popup;
|
||||
pub mod replay;
|
||||
pub mod stage_select;
|
||||
pub mod text_boxes;
|
||||
pub mod tilemap;
|
||||
|
|
|
@ -25,7 +25,7 @@ impl NikumaruCounter {
|
|||
}
|
||||
|
||||
fn load_time(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult<u32> {
|
||||
if let Ok(mut data) = filesystem::user_open(ctx, state.get_290_filename()) {
|
||||
if let Ok(mut data) = filesystem::user_open(ctx, [state.get_rec_filename(), ".rec".to_string()].join("")) {
|
||||
let mut ticks: [u32; 4] = [0; 4];
|
||||
|
||||
for iter in 0..=3 {
|
||||
|
@ -54,9 +54,11 @@ impl NikumaruCounter {
|
|||
}
|
||||
|
||||
fn save_time(&mut self, new_time: u32, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if let Ok(mut data) =
|
||||
filesystem::open_options(ctx, state.get_290_filename(), OpenOptions::new().write(true).create(true))
|
||||
{
|
||||
if let Ok(mut data) = filesystem::open_options(
|
||||
ctx,
|
||||
[state.get_rec_filename(), ".rec".to_string()].join(""),
|
||||
OpenOptions::new().write(true).create(true),
|
||||
) {
|
||||
let mut ticks: [u32; 4] = [new_time; 4];
|
||||
let mut random_list: [u8; 4] = [0; 4];
|
||||
|
||||
|
@ -90,12 +92,13 @@ impl NikumaruCounter {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save_counter(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
pub fn save_counter(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult<bool> {
|
||||
let old_record = self.load_time(state, ctx)? as usize;
|
||||
if self.tick < old_record || old_record == 0 {
|
||||
self.save_time(self.tick as u32, state, ctx)?;
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(())
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
165
src/components/replay.rs
Normal file
165
src/components/replay.rs
Normal file
|
@ -0,0 +1,165 @@
|
|||
use std::io::{Cursor, Read};
|
||||
|
||||
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
||||
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::framework::keyboard::ScanCode;
|
||||
use crate::framework::vfs::OpenOptions;
|
||||
use crate::input::replay_player_controller::{KeyState, ReplayController};
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::{ReplayState, SharedGameState};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Replay {
|
||||
replay_version: u16,
|
||||
keylist: Vec<u16>,
|
||||
last_input: KeyState,
|
||||
rng_seed: u64,
|
||||
pub controller: ReplayController,
|
||||
tick: usize,
|
||||
resume_tick: usize,
|
||||
}
|
||||
|
||||
impl Replay {
|
||||
pub fn new() -> Replay {
|
||||
Replay {
|
||||
replay_version: 0,
|
||||
keylist: Vec::new(),
|
||||
last_input: KeyState(0),
|
||||
rng_seed: 0,
|
||||
controller: ReplayController::new(),
|
||||
tick: 0,
|
||||
resume_tick: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_recording(&mut self, state: &mut SharedGameState) {
|
||||
self.rng_seed = state.game_rng.dump_state();
|
||||
}
|
||||
|
||||
pub fn stop_recording(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
state.replay_state = ReplayState::None;
|
||||
self.write_replay(state, ctx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_playback(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
state.replay_state = ReplayState::Playback;
|
||||
self.read_replay(state, ctx)?;
|
||||
state.game_rng.load_state(self.rng_seed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_replay(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if let Ok(mut file) = filesystem::open_options(
|
||||
ctx,
|
||||
[state.get_rec_filename(), ".rep".to_string()].join(""),
|
||||
OpenOptions::new().write(true).create(true),
|
||||
) {
|
||||
file.write_u16::<LE>(0)?; // Space for versioning replay files
|
||||
file.write_u64::<LE>(self.rng_seed)?;
|
||||
for input in &self.keylist {
|
||||
file.write_u16::<LE>(*input)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_replay(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if let Ok(mut file) = filesystem::user_open(ctx, [state.get_rec_filename(), ".rep".to_string()].join("")) {
|
||||
self.replay_version = file.read_u16::<LE>()?;
|
||||
self.rng_seed = file.read_u64::<LE>()?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)?;
|
||||
|
||||
let count = data.len() / 2;
|
||||
let mut inputs = Vec::new();
|
||||
let mut f = Cursor::new(data);
|
||||
|
||||
for _ in 0..count {
|
||||
inputs.push(f.read_u16::<LE>()?);
|
||||
}
|
||||
|
||||
self.keylist = inputs;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&mut Context, &mut Player)> for Replay {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (ctx, player): (&mut Context, &mut Player)) -> GameResult {
|
||||
match state.replay_state {
|
||||
ReplayState::Recording => {
|
||||
// This mimics the KeyState bitfield
|
||||
let inputs = player.controller.move_left() as u16
|
||||
+ ((player.controller.move_right() as u16) << 1)
|
||||
+ ((player.controller.move_up() as u16) << 2)
|
||||
+ ((player.controller.move_down() as u16) << 3)
|
||||
+ ((player.controller.trigger_map() as u16) << 4)
|
||||
+ ((player.controller.trigger_inventory() as u16) << 5)
|
||||
+ (((player.controller.jump() || player.controller.trigger_menu_ok()) as u16) << 6)
|
||||
+ (((player.controller.shoot() || player.controller.trigger_menu_back()) as u16) << 7)
|
||||
+ ((player.controller.next_weapon() as u16) << 8)
|
||||
+ ((player.controller.prev_weapon() as u16) << 9)
|
||||
+ ((player.controller.trigger_menu_ok() as u16) << 11)
|
||||
+ ((player.controller.skip() as u16) << 12)
|
||||
+ ((player.controller.strafe() as u16) << 13);
|
||||
|
||||
self.keylist.push(inputs);
|
||||
}
|
||||
ReplayState::Playback => {
|
||||
let pause = ctx.keyboard_context.is_key_pressed(ScanCode::Escape) && (self.tick - self.resume_tick > 3);
|
||||
|
||||
let next_input = if pause { 1 << 10 } else { *self.keylist.get(self.tick).unwrap_or(&0) };
|
||||
|
||||
self.controller.state = KeyState(next_input);
|
||||
self.controller.old_state = self.last_input;
|
||||
player.controller = Box::new(self.controller);
|
||||
|
||||
if !pause {
|
||||
self.last_input = KeyState(next_input);
|
||||
self.tick += 1;
|
||||
} else {
|
||||
self.resume_tick = self.tick;
|
||||
};
|
||||
|
||||
if self.tick >= self.keylist.len() {
|
||||
state.replay_state = ReplayState::None;
|
||||
player.controller = state.settings.create_player1_controller();
|
||||
}
|
||||
}
|
||||
ReplayState::None => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
let x = state.canvas_size.0 - 32.0;
|
||||
let y = 8.0 + if state.settings.fps_counter { 12.0 } else { 0.0 };
|
||||
|
||||
match state.replay_state {
|
||||
ReplayState::None => {}
|
||||
ReplayState::Playback => {
|
||||
state.font.draw_text_with_shadow(
|
||||
"PLAY".chars(),
|
||||
x,
|
||||
y,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
ReplayState::Recording => {
|
||||
state.font.draw_text_with_shadow("REC".chars(), x, y, &state.constants, &mut state.texture_set, ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -2,5 +2,6 @@ pub mod combined_menu_controller;
|
|||
pub mod dummy_player_controller;
|
||||
pub mod keyboard_player_controller;
|
||||
pub mod player_controller;
|
||||
pub mod replay_player_controller;
|
||||
pub mod touch_controls;
|
||||
pub mod touch_player_controller;
|
||||
|
|
207
src/input/replay_player_controller.rs
Normal file
207
src/input/replay_player_controller.rs
Normal file
|
@ -0,0 +1,207 @@
|
|||
use crate::bitfield;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::input::player_controller::PlayerController;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
bitfield! {
|
||||
#[allow(unused)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct KeyState(u16);
|
||||
impl Debug;
|
||||
|
||||
pub left, set_left: 0;
|
||||
pub right, set_right: 1;
|
||||
pub up, set_up: 2;
|
||||
pub down, set_down: 3;
|
||||
pub map, set_map: 4;
|
||||
pub inventory, set_inventory: 5;
|
||||
pub jump, set_jump: 6;
|
||||
pub shoot, set_shoot: 7;
|
||||
pub next_weapon, set_next_weapon: 8;
|
||||
pub prev_weapon, set_prev_weapon: 9;
|
||||
pub escape, set_escape: 10;
|
||||
pub enter, set_enter: 11;
|
||||
pub skip, set_skip: 12;
|
||||
pub strafe, set_strafe: 13;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ReplayController {
|
||||
//target: TargetPlayer,
|
||||
pub state: KeyState,
|
||||
pub old_state: KeyState,
|
||||
trigger: KeyState,
|
||||
}
|
||||
|
||||
impl ReplayController {
|
||||
pub fn new() -> ReplayController {
|
||||
ReplayController {
|
||||
//target,
|
||||
state: KeyState(0),
|
||||
old_state: KeyState(0),
|
||||
trigger: KeyState(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlayerController for ReplayController {
|
||||
fn update(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_trigger(&mut self) {
|
||||
let mut trigger = self.state.0 ^ self.old_state.0;
|
||||
trigger &= self.state.0;
|
||||
self.old_state = self.state;
|
||||
self.trigger = KeyState(trigger);
|
||||
}
|
||||
|
||||
fn move_up(&self) -> bool {
|
||||
self.state.up()
|
||||
}
|
||||
|
||||
fn move_left(&self) -> bool {
|
||||
self.state.left()
|
||||
}
|
||||
|
||||
fn move_down(&self) -> bool {
|
||||
self.state.down()
|
||||
}
|
||||
|
||||
fn move_right(&self) -> bool {
|
||||
self.state.right()
|
||||
}
|
||||
|
||||
fn prev_weapon(&self) -> bool {
|
||||
self.state.prev_weapon()
|
||||
}
|
||||
|
||||
fn next_weapon(&self) -> bool {
|
||||
self.state.next_weapon()
|
||||
}
|
||||
|
||||
fn map(&self) -> bool {
|
||||
self.state.map()
|
||||
}
|
||||
|
||||
fn inventory(&self) -> bool {
|
||||
self.state.inventory()
|
||||
}
|
||||
|
||||
fn jump(&self) -> bool {
|
||||
self.state.jump()
|
||||
}
|
||||
|
||||
fn shoot(&self) -> bool {
|
||||
self.state.shoot()
|
||||
}
|
||||
|
||||
fn skip(&self) -> bool {
|
||||
self.state.skip()
|
||||
}
|
||||
|
||||
fn strafe(&self) -> bool {
|
||||
self.state.strafe()
|
||||
}
|
||||
|
||||
fn trigger_up(&self) -> bool {
|
||||
self.trigger.up()
|
||||
}
|
||||
|
||||
fn trigger_left(&self) -> bool {
|
||||
self.trigger.left()
|
||||
}
|
||||
|
||||
fn trigger_down(&self) -> bool {
|
||||
self.trigger.down()
|
||||
}
|
||||
|
||||
fn trigger_right(&self) -> bool {
|
||||
self.trigger.right()
|
||||
}
|
||||
|
||||
fn trigger_prev_weapon(&self) -> bool {
|
||||
self.trigger.prev_weapon()
|
||||
}
|
||||
|
||||
fn trigger_next_weapon(&self) -> bool {
|
||||
self.trigger.next_weapon()
|
||||
}
|
||||
|
||||
fn trigger_map(&self) -> bool {
|
||||
self.trigger.map()
|
||||
}
|
||||
|
||||
fn trigger_inventory(&self) -> bool {
|
||||
self.trigger.inventory()
|
||||
}
|
||||
|
||||
fn trigger_jump(&self) -> bool {
|
||||
self.trigger.jump()
|
||||
}
|
||||
|
||||
fn trigger_shoot(&self) -> bool {
|
||||
self.trigger.shoot()
|
||||
}
|
||||
|
||||
fn trigger_skip(&self) -> bool {
|
||||
self.trigger.skip()
|
||||
}
|
||||
|
||||
fn trigger_strafe(&self) -> bool {
|
||||
self.trigger.strafe()
|
||||
}
|
||||
|
||||
fn trigger_menu_ok(&self) -> bool {
|
||||
self.trigger.jump() || self.trigger.enter()
|
||||
}
|
||||
|
||||
fn trigger_menu_back(&self) -> bool {
|
||||
self.trigger.shoot() || self.trigger.escape()
|
||||
}
|
||||
|
||||
fn trigger_menu_pause(&self) -> bool {
|
||||
self.trigger.escape()
|
||||
}
|
||||
|
||||
fn look_up(&self) -> bool {
|
||||
self.state.up()
|
||||
}
|
||||
|
||||
fn look_left(&self) -> bool {
|
||||
self.state.left()
|
||||
}
|
||||
|
||||
fn look_down(&self) -> bool {
|
||||
self.state.down()
|
||||
}
|
||||
|
||||
fn look_right(&self) -> bool {
|
||||
self.state.right()
|
||||
}
|
||||
|
||||
fn move_analog_x(&self) -> f64 {
|
||||
if self.state.left() && self.state.right() {
|
||||
0.0
|
||||
} else if self.state.left() {
|
||||
-1.0
|
||||
} else if self.state.right() {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
fn move_analog_y(&self) -> f64 {
|
||||
if self.state.up() && self.state.down() {
|
||||
0.0
|
||||
} else if self.state.up() {
|
||||
-1.0
|
||||
} else if self.state.down() {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ use crate::components::hud::HUD;
|
|||
use crate::components::inventory::InventoryUI;
|
||||
use crate::components::map_system::MapSystem;
|
||||
use crate::components::nikumaru::NikumaruCounter;
|
||||
use crate::components::replay::Replay;
|
||||
use crate::components::stage_select::StageSelect;
|
||||
use crate::components::text_boxes::TextBoxes;
|
||||
use crate::components::tilemap::{TileLayer, Tilemap};
|
||||
|
@ -44,7 +45,7 @@ use crate::scene::title_scene::TitleScene;
|
|||
use crate::scene::Scene;
|
||||
use crate::scripting::tsc::credit_script::CreditScriptVM;
|
||||
use crate::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
|
||||
use crate::shared_game_state::{SharedGameState, TileSize};
|
||||
use crate::shared_game_state::{ReplayState, SharedGameState, TileSize};
|
||||
use crate::stage::{BackgroundType, Stage, StageTexturePaths};
|
||||
use crate::texture_set::SpriteBatch;
|
||||
use crate::weapon::bullet::BulletManager;
|
||||
|
@ -83,6 +84,7 @@ pub struct GameScene {
|
|||
pub intro_mode: bool,
|
||||
pub pause_menu: PauseMenu,
|
||||
pub stage_textures: Rc<RefCell<StageTexturePaths>>,
|
||||
pub replay: Replay,
|
||||
map_name_counter: u16,
|
||||
skip_counter: u16,
|
||||
inventory_dim: f32,
|
||||
|
@ -170,6 +172,7 @@ impl GameScene {
|
|||
map_name_counter: 0,
|
||||
skip_counter: 0,
|
||||
inventory_dim: 0.0,
|
||||
replay: Replay::new(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1621,10 +1624,22 @@ impl Scene for GameScene {
|
|||
self.pause_menu.init(state, ctx)?;
|
||||
self.whimsical_star.init(&self.player1);
|
||||
|
||||
if state.mod_path.is_some() && state.replay_state == ReplayState::Recording {
|
||||
self.replay.start_recording(state);
|
||||
}
|
||||
|
||||
if state.mod_path.is_some() && state.replay_state == ReplayState::Playback {
|
||||
self.replay.start_playback(state, ctx)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if !self.pause_menu.is_paused() && state.replay_state == ReplayState::Playback {
|
||||
self.replay.tick(state, (ctx, &mut self.player1))?;
|
||||
}
|
||||
|
||||
self.player1.controller.update(state, ctx)?;
|
||||
self.player1.controller.update_trigger();
|
||||
self.player2.controller.update(state, ctx)?;
|
||||
|
@ -1658,6 +1673,10 @@ impl Scene for GameScene {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
if state.replay_state == ReplayState::Recording {
|
||||
self.replay.tick(state, (ctx, &mut self.player1))?;
|
||||
}
|
||||
|
||||
match state.textscript_vm.state {
|
||||
TextScriptExecutionState::Running(_, _)
|
||||
| TextScriptExecutionState::WaitTicks(_, _, _)
|
||||
|
@ -1983,6 +2002,8 @@ impl Scene for GameScene {
|
|||
self.draw_debug_outlines(state, ctx)?;
|
||||
}
|
||||
|
||||
self.replay.draw(state, ctx, &self.frame)?;
|
||||
|
||||
self.pause_menu.draw(state, ctx)?;
|
||||
|
||||
//draw_number(state.canvas_size.0 - 8.0, 8.0, timer::fps(ctx) as usize, Alignment::Right, state, ctx)?;
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::menu::settings_menu::SettingsMenu;
|
|||
use crate::menu::{Menu, MenuEntry, MenuSelectionResult};
|
||||
use crate::scene::jukebox_scene::JukeboxScene;
|
||||
use crate::scene::Scene;
|
||||
use crate::shared_game_state::{GameDifficulty, MenuCharacter, SharedGameState, TileSize};
|
||||
use crate::shared_game_state::{GameDifficulty, MenuCharacter, ReplayState, SharedGameState, TileSize};
|
||||
use crate::stage::{BackgroundType, NpcType, Stage, StageData, StageTexturePaths, Tileset};
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
|
@ -168,6 +168,7 @@ impl Scene for TitleScene {
|
|||
|
||||
self.confirm_menu.push_entry(MenuEntry::Disabled("".to_owned()));
|
||||
self.confirm_menu.push_entry(MenuEntry::Active("Start".to_owned()));
|
||||
self.confirm_menu.push_entry(MenuEntry::Disabled("No Replay".to_owned()));
|
||||
self.confirm_menu.push_entry(MenuEntry::Active("< Back".to_owned()));
|
||||
self.confirm_menu.selected = 1;
|
||||
|
||||
|
@ -177,6 +178,8 @@ impl Scene for TitleScene {
|
|||
self.nikumaru_rec.load_counter(state, ctx)?;
|
||||
self.update_menu_cursor(state, ctx)?;
|
||||
|
||||
state.replay_state = ReplayState::None;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -272,6 +275,11 @@ impl Scene for TitleScene {
|
|||
self.confirm_menu.width =
|
||||
(state.font.text_width(mod_name.chars(), &state.constants).max(50.0) + 32.0) as u16;
|
||||
self.confirm_menu.entries[0] = MenuEntry::Disabled(mod_name);
|
||||
self.confirm_menu.entries[2] = if state.has_replay_data(ctx) {
|
||||
MenuEntry::Active("Replay Best".to_owned())
|
||||
} else {
|
||||
MenuEntry::Disabled("No Replay".to_owned())
|
||||
};
|
||||
self.nikumaru_rec.load_counter(state, ctx)?;
|
||||
self.current_menu = CurrentMenu::ChallengeConfirmMenu;
|
||||
}
|
||||
|
@ -288,10 +296,17 @@ impl Scene for TitleScene {
|
|||
CurrentMenu::ChallengeConfirmMenu => match self.confirm_menu.tick(&mut self.controller, state) {
|
||||
MenuSelectionResult::Selected(1, _) => {
|
||||
state.difficulty = GameDifficulty::Normal;
|
||||
state.replay_state = ReplayState::Recording;
|
||||
state.reload_resources(ctx)?;
|
||||
state.start_new_game(ctx)?;
|
||||
}
|
||||
MenuSelectionResult::Selected(2, _) | MenuSelectionResult::Canceled => {
|
||||
MenuSelectionResult::Selected(2, _) => {
|
||||
state.difficulty = GameDifficulty::Normal;
|
||||
state.replay_state = ReplayState::Playback;
|
||||
state.reload_resources(ctx)?;
|
||||
state.start_new_game(ctx)?;
|
||||
}
|
||||
MenuSelectionResult::Selected(3, _) | MenuSelectionResult::Canceled => {
|
||||
self.current_menu = CurrentMenu::ChallengesMenu;
|
||||
}
|
||||
_ => (),
|
||||
|
|
|
@ -26,6 +26,7 @@ use crate::scene::title_scene::TitleScene;
|
|||
use crate::scripting::tsc::bytecode_utils::read_cur_varint;
|
||||
use crate::scripting::tsc::encryption::decrypt_tsc;
|
||||
use crate::scripting::tsc::opcodes::TSCOpCode;
|
||||
use crate::shared_game_state::ReplayState;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::weapon::WeaponType;
|
||||
|
||||
|
@ -1121,6 +1122,7 @@ impl TextScriptVM {
|
|||
new_scene.player2.flags.set_hit_bottom_wall(false);
|
||||
new_scene.frame.wait = game_scene.frame.wait;
|
||||
new_scene.nikumaru = game_scene.nikumaru;
|
||||
new_scene.replay = game_scene.replay.clone();
|
||||
|
||||
let skip = state.textscript_vm.flags.cutscene_skip();
|
||||
state.control_flags.set_tick_world(true);
|
||||
|
@ -1688,7 +1690,11 @@ impl TextScriptVM {
|
|||
);
|
||||
}
|
||||
TSCOpCode::STC => {
|
||||
game_scene.nikumaru.save_counter(state, ctx)?;
|
||||
let new_record = game_scene.nikumaru.save_counter(state, ctx)?;
|
||||
|
||||
if new_record && state.replay_state == ReplayState::Recording {
|
||||
game_scene.replay.stop_recording(state, ctx)?;
|
||||
}
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
|
|
|
@ -134,6 +134,13 @@ pub enum MenuCharacter {
|
|||
Sue,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum ReplayState {
|
||||
None,
|
||||
Recording,
|
||||
Playback,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum TileSize {
|
||||
Tile8x8,
|
||||
|
@ -201,6 +208,7 @@ pub struct SharedGameState {
|
|||
pub settings: Settings,
|
||||
pub save_slot: usize,
|
||||
pub difficulty: GameDifficulty,
|
||||
pub replay_state: ReplayState,
|
||||
pub shutdown: bool,
|
||||
}
|
||||
|
||||
|
@ -298,6 +306,7 @@ impl SharedGameState {
|
|||
settings,
|
||||
save_slot: 1,
|
||||
difficulty: GameDifficulty::Normal,
|
||||
replay_state: ReplayState::None,
|
||||
shutdown: false,
|
||||
})
|
||||
}
|
||||
|
@ -591,15 +600,19 @@ impl SharedGameState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_290_filename(&self) -> String {
|
||||
pub fn get_rec_filename(&self) -> String {
|
||||
if let Some(mod_path) = &self.mod_path {
|
||||
let name = self.mod_list.get_name_from_path(mod_path.to_string());
|
||||
return format!("/{}.rec", name);
|
||||
return format!("/{}", name);
|
||||
} else {
|
||||
return "/290.rec".to_string();
|
||||
return "/290".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_replay_data(&self, ctx: &mut Context) -> bool {
|
||||
filesystem::user_exists(ctx, [self.get_rec_filename(), ".rep".to_string()].join(""))
|
||||
}
|
||||
|
||||
pub fn get_damage(&self, hp: i32) -> i32 {
|
||||
match self.difficulty {
|
||||
GameDifficulty::Easy => cmp::max(hp / 2, 1),
|
||||
|
|
Loading…
Reference in a new issue