From 4570e33edf3f72184024dcd9369618ec68ce8158 Mon Sep 17 00:00:00 2001 From: Alula Date: Tue, 22 Sep 2020 01:53:46 +0200 Subject: [PATCH] implement save loading --- src/inventory.rs | 8 +- src/live_debugger.rs | 2 + src/main.rs | 4 +- src/player.rs | 6 +- src/player_hit.rs | 4 + src/profile.rs | 191 +++++++++++++++++++++++++++++++++++++++ src/scene/game_scene.rs | 2 + src/scene/title_scene.rs | 37 ++++++-- src/text_script.rs | 1 - 9 files changed, 242 insertions(+), 13 deletions(-) create mode 100644 src/profile.rs diff --git a/src/inventory.rs b/src/inventory.rs index f73b426..2c804d7 100644 --- a/src/inventory.rs +++ b/src/inventory.rs @@ -8,8 +8,8 @@ pub struct Item(u16, u16); #[derive(Clone)] pub struct Inventory { - current_item: u16, - current_weapon: u16, + pub current_item: u16, + pub current_weapon: u16, items: Vec, weapons: Vec, } @@ -61,7 +61,7 @@ impl Inventory { self.items.iter().any(|item| item.0 == item_id) } - pub fn add_weapon(&mut self, weapon_id: WeaponType, max_ammo: u16) { + pub fn add_weapon(&mut self, weapon_id: WeaponType, max_ammo: u16) -> &mut Weapon { if !self.has_weapon(weapon_id) { self.weapons.push(Weapon::new( weapon_id, @@ -71,6 +71,8 @@ impl Inventory { max_ammo, )); } + + self.weapons.iter_mut().find(|w| w.wtype == weapon_id).unwrap() } pub fn remove_weapon(&mut self, wtype: WeaponType) { diff --git a/src/live_debugger.rs b/src/live_debugger.rs index 70cf8c2..9891da0 100644 --- a/src/live_debugger.rs +++ b/src/live_debugger.rs @@ -174,6 +174,8 @@ impl LiveDebugger { assert_eq!(self.event_ids.len(), self.events.len()); if let Some(&event_num) = self.event_ids.get(self.selected_event as usize) { + state.control_flags.set_flag_x01(true); + state.control_flags.set_interactions_disabled(true); state.textscript_vm.start_script(event_num); } } diff --git a/src/main.rs b/src/main.rs index 64c23a6..3612367 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,6 +53,7 @@ mod npc; mod physics; mod player; mod player_hit; +mod profile; mod rng; mod scene; mod shared_game_state; @@ -169,7 +170,8 @@ pub fn main() -> GameResult { .resizable(true) .min_dimensions(320.0, 240.0) .dimensions(854.0, 480.0)) - .add_resource_path(resource_dir); + .add_resource_path(resource_dir) + .add_resource_path(path::PathBuf::from(str!("./"))); let (ctx, event_loop) = &mut cb.build()?; ctx.filesystem.mount_vfs(Box::new(BuiltinFS::new())); diff --git a/src/player.rs b/src/player.rs index d979e9c..57fbc6b 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,7 +1,8 @@ use std::clone::Clone; use num_derive::FromPrimitive; -use num_traits::{clamp, FromPrimitive}; +use num_traits::clamp; +use num_traits::FromPrimitive; use crate::caret::CaretType; use crate::common::{Condition, Equipment, Flag}; @@ -9,7 +10,6 @@ use crate::common::{Direction, Rect}; use crate::entity::GameEntity; use crate::frame::Frame; use crate::ggez::{Context, GameResult}; -use crate::inventory::Inventory; use crate::shared_game_state::SharedGameState; #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)] @@ -526,6 +526,8 @@ impl Player { if self.life == 0 { state.sound_manager.play_sfx(17); self.cond.0 = 0; + state.control_flags.set_flag_x01(true); + state.control_flags.set_interactions_disabled(true); state.textscript_vm.start_script(40); } } diff --git a/src/player_hit.rs b/src/player_hit.rs index 9de0b84..71f4ac8 100644 --- a/src/player_hit.rs +++ b/src/player_hit.rs @@ -187,6 +187,8 @@ impl Player { } if npc.npc_flags.interactable() && !state.control_flags.interactions_disabled() && flags.0 != 0 && self.cond.interacted() { + state.control_flags.set_flag_x01(true); + state.control_flags.set_interactions_disabled(true); state.textscript_vm.start_script(npc.event_num); self.cond.set_interacted(false); self.vel_x = 0; @@ -194,6 +196,8 @@ impl Player { } if npc.npc_flags.event_when_touched() && !state.control_flags.interactions_disabled() && flags.0 != 0 { + state.control_flags.set_flag_x01(true); + state.control_flags.set_interactions_disabled(true); state.textscript_vm.start_script(npc.event_num); } diff --git a/src/profile.rs b/src/profile.rs new file mode 100644 index 0000000..339242c --- /dev/null +++ b/src/profile.rs @@ -0,0 +1,191 @@ +use std::io; + +use byteorder::{BE, LE, ReadBytesExt}; +use num_traits::{clamp, FromPrimitive}; + +use crate::common::{Direction, FadeState}; +use crate::ggez::{Context, GameResult}; +use crate::ggez::GameError::ResourceLoadError; +use crate::player::ControlMode; +use crate::scene::game_scene::GameScene; +use crate::shared_game_state::SharedGameState; +use crate::str; +use crate::weapon::{WeaponType, WeaponLevel}; + +pub struct WeaponData { + pub weapon_id: u32, + pub level: u32, + pub exp: u32, + pub max_ammo: u32, + pub ammo: u32, +} + +pub struct TeleporterSlotData { + pub index: u32, + pub event_num: u32, +} + +pub struct GameProfile { + pub current_map: u32, + pub current_song: u32, + pub pos_x: i32, + pub pos_y: i32, + pub direction: Direction, + pub max_life: u16, + pub stars: u16, + pub life: u16, + pub current_weapon: u32, + pub current_item: u32, + pub equipment: u32, + pub control_mode: u32, + pub counter: u32, + pub weapon_data: [WeaponData; 8], + pub items: [u32; 32], + pub teleporter_slots: [TeleporterSlotData; 8], + pub flags: [u8; 1000], +} + +impl GameProfile { + pub fn apply(&self, state: &mut SharedGameState, game_scene: &mut GameScene, ctx: &mut Context) { + state.fade_state = FadeState::Visible; + state.control_flags.set_flag_x01(true); + state.control_flags.set_control_enabled(true); + + state.sound_manager.play_song(self.current_song as usize, &state.constants, ctx); + + game_scene.inventory.current_weapon = self.current_weapon as u16; + game_scene.inventory.current_item = self.current_item as u16; + for weapon in self.weapon_data.iter() { + if weapon.weapon_id == 0 { continue; } + let weapon_type: Option = FromPrimitive::from_u8(weapon.weapon_id as u8); + + if let Some(wtype) = weapon_type { + let w = game_scene.inventory.add_weapon(wtype, weapon.max_ammo as u16); + w.ammo = weapon.ammo as u16; + w.level = match weapon.level { + 2 => {WeaponLevel::Level2} + 3 => {WeaponLevel::Level3} + _ => {WeaponLevel::Level1} + }; + w.experience = weapon.exp as u16; + } + } + + for item in self.items.iter().copied() { + game_scene.inventory.get_item(item as u16); + } + + for (idx, &flags) in self.flags.iter().enumerate() { + if flags & 0b00000001 != 0 { state.game_flags.set(idx * 8, true); } + if flags & 0b00000010 != 0 { state.game_flags.set(idx * 8 + 1, true); } + if flags & 0b00000100 != 0 { state.game_flags.set(idx * 8 + 2, true); } + if flags & 0b00001000 != 0 { state.game_flags.set(idx * 8 + 3, true); } + if flags & 0b00010000 != 0 { state.game_flags.set(idx * 8 + 4, true); } + if flags & 0b00100000 != 0 { state.game_flags.set(idx * 8 + 5, true); } + if flags & 0b01000000 != 0 { state.game_flags.set(idx * 8 + 6, true); } + if flags & 0b10000000 != 0 { state.game_flags.set(idx * 8 + 7, true); } + } + + game_scene.player.equip.0 = self.equipment as u16; + game_scene.player.cond.0 = 0x80; + + game_scene.player.x = self.pos_x as isize; + game_scene.player.y = self.pos_y as isize; + + game_scene.player.control_mode = if self.control_mode == 1 { ControlMode::IronHead } else { ControlMode::Normal }; + game_scene.player.direction = self.direction; + game_scene.player.life = self.life; + game_scene.player.max_life = self.max_life; + game_scene.player.stars = clamp(self.stars, 0, 3) as u8; + } + + pub fn load_from_save(mut data: R) -> GameResult { + // Do041220 + if data.read_u64::()? != 0x446f303431323230 { + return Err(ResourceLoadError(str!("Invalid magic"))); + } + + let current_map = data.read_u32::()?; + let current_song = data.read_u32::()?; + let pos_x = data.read_i32::()?; + let pos_y = data.read_i32::()?; + let direction = data.read_u32::()?; + let max_life = data.read_u16::()?; + let stars = data.read_u16::()?; + let life = data.read_u16::()?; + let _ = data.read_u16::()?; // ??? + let current_weapon = data.read_u32::()?; + let current_item = data.read_u32::()?; + let equipment = data.read_u32::()?; + let move_mode = data.read_u32::()?; + let counter = data.read_u32::()?; + let mut weapon_data = [ + WeaponData { weapon_id: 0, level: 0, exp: 0, max_ammo: 0, ammo: 0 }, + WeaponData { weapon_id: 0, level: 0, exp: 0, max_ammo: 0, ammo: 0 }, + WeaponData { weapon_id: 0, level: 0, exp: 0, max_ammo: 0, ammo: 0 }, + WeaponData { weapon_id: 0, level: 0, exp: 0, max_ammo: 0, ammo: 0 }, + WeaponData { weapon_id: 0, level: 0, exp: 0, max_ammo: 0, ammo: 0 }, + WeaponData { weapon_id: 0, level: 0, exp: 0, max_ammo: 0, ammo: 0 }, + WeaponData { weapon_id: 0, level: 0, exp: 0, max_ammo: 0, ammo: 0 }, + WeaponData { weapon_id: 0, level: 0, exp: 0, max_ammo: 0, ammo: 0 }, + ]; + let mut items = [0u32; 32]; + let mut teleporter_slots = [ + TeleporterSlotData { index: 0, event_num: 0 }, + TeleporterSlotData { index: 0, event_num: 0 }, + TeleporterSlotData { index: 0, event_num: 0 }, + TeleporterSlotData { index: 0, event_num: 0 }, + TeleporterSlotData { index: 0, event_num: 0 }, + TeleporterSlotData { index: 0, event_num: 0 }, + TeleporterSlotData { index: 0, event_num: 0 }, + TeleporterSlotData { index: 0, event_num: 0 }, + ]; + + for weap in weapon_data.iter_mut() { + weap.weapon_id = data.read_u32::()?; + weap.level = data.read_u32::()?; + weap.exp = data.read_u32::()?; + weap.max_ammo = data.read_u32::()?; + weap.ammo = data.read_u32::()?; + } + + for item in items.iter_mut() { + *item = data.read_u32::()?; + } + + for slot in teleporter_slots.iter_mut() { + slot.index = data.read_u32::()?; + slot.event_num = data.read_u32::()?; + } + + let mut something = [0u8; 0x80]; + data.read_exact(&mut something)?; + + if data.read_u32::()? != 0x464c4147 { + return Err(ResourceLoadError(str!("Invalid FLAG signature"))); + } + + let mut flags = [0u8; 1000]; + data.read_exact(&mut flags)?; + + Ok(GameProfile { + current_map, + current_song, + pos_x, + pos_y, + direction: Direction::from_int(direction as usize).unwrap_or(Direction::Left), + max_life, + stars, + life, + current_weapon, + current_item, + equipment, + control_mode: move_mode, + counter, + weapon_data, + items, + teleporter_slots, + flags, + }) + } +} diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index f66beea..929d262 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -621,6 +621,8 @@ impl GameScene { } if self.player.cond.alive() && npc.npc_flags.event_when_killed() { + state.control_flags.set_flag_x01(true); + state.control_flags.set_interactions_disabled(true); state.textscript_vm.start_script(npc.event_num); } else { npc.cond.set_explode_die(true); diff --git a/src/scene/title_scene.rs b/src/scene/title_scene.rs index b28a1ae..a27b6c6 100644 --- a/src/scene/title_scene.rs +++ b/src/scene/title_scene.rs @@ -1,7 +1,8 @@ use crate::common::{FadeState, Rect}; -use crate::ggez::{Context, GameResult, graphics}; +use crate::ggez::{Context, filesystem, GameResult, graphics}; use crate::ggez::graphics::Color; use crate::menu::{Menu, MenuEntry, MenuSelectionResult}; +use crate::profile::GameProfile; use crate::scene::game_scene::GameScene; use crate::scene::Scene; use crate::shared_game_state::SharedGameState; @@ -13,6 +14,7 @@ enum CurrentMenu { MainMenu, OptionMenu, StartGame, + LoadGame, } pub struct TitleScene { @@ -95,8 +97,8 @@ impl Scene for TitleScene { fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { if self.tick == 0 { state.sound_manager.play_song(24, &state.constants, ctx)?; - self.main_menu.push_entry(MenuEntry::Active("Load game".to_string())); self.main_menu.push_entry(MenuEntry::Active("New game".to_string())); + self.main_menu.push_entry(MenuEntry::Active("Load game".to_string())); self.main_menu.push_entry(MenuEntry::Active("Options".to_string())); self.main_menu.push_entry(MenuEntry::Disabled("Editor".to_string())); self.main_menu.push_entry(MenuEntry::Active("Quit".to_string())); @@ -122,10 +124,9 @@ impl Scene for TitleScene { self.current_menu = CurrentMenu::StartGame; } MenuSelectionResult::Selected(1, _) => { - state.reset(); state.sound_manager.play_song(0, &state.constants, ctx)?; self.tick = 1; - self.current_menu = CurrentMenu::StartGame; + self.current_menu = CurrentMenu::LoadGame; } MenuSelectionResult::Selected(2, _) => { self.current_menu = CurrentMenu::OptionMenu; @@ -160,6 +161,30 @@ impl Scene for TitleScene { self.start_game(state, ctx)?; } } + CurrentMenu::LoadGame => { + if self.tick == 30 { + if let Ok(data) = filesystem::open(ctx, "/Profile.dat") { + match GameProfile::load_from_save(data) { + Ok(profile) => { + state.reset(); + let mut next_scene = GameScene::new(state, ctx, profile.current_map as usize)?; + + profile.apply(state, &mut next_scene, ctx); + + state.next_scene = Some(Box::new(next_scene)); + return Ok(()); + } + Err(e) => { + log::warn!("Failed to load save game, starting new one: {}", e); + } + } + } else { + log::warn!("No save game found, starting new one..."); + } + + self.start_game(state, ctx)?; + } + } } self.tick += 1; @@ -168,7 +193,7 @@ impl Scene for TitleScene { } fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { - if self.current_menu == CurrentMenu::StartGame { + if self.current_menu == CurrentMenu::StartGame || self.current_menu == CurrentMenu::LoadGame { graphics::clear(ctx, Color::from_rgb(0, 0, 0)); return Ok(()); } @@ -195,7 +220,7 @@ impl Scene for TitleScene { match self.current_menu { CurrentMenu::MainMenu => { self.main_menu.draw(state, ctx)?; } CurrentMenu::OptionMenu => { self.option_menu.draw(state, ctx)?; } - CurrentMenu::StartGame => {} + _ => {} } Ok(()) diff --git a/src/text_script.rs b/src/text_script.rs index b7eea90..c892d04 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -528,7 +528,6 @@ impl TextScriptVM { state.control_flags.set_control_enabled(false); game_scene.player.up = false; - game_scene.player.down = false; game_scene.player.shock_counter = 0; exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);