mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-01-10 13:07:17 +00:00
implement save loading
This commit is contained in:
parent
dd9ac33185
commit
4570e33edf
|
@ -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<Item>,
|
||||
weapons: Vec<Weapon>,
|
||||
}
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
191
src/profile.rs
Normal file
191
src/profile.rs
Normal file
|
@ -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<WeaponType> = 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<R: io::Read>(mut data: R) -> GameResult<GameProfile> {
|
||||
// Do041220
|
||||
if data.read_u64::<BE>()? != 0x446f303431323230 {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
}
|
||||
|
||||
let current_map = data.read_u32::<LE>()?;
|
||||
let current_song = data.read_u32::<LE>()?;
|
||||
let pos_x = data.read_i32::<LE>()?;
|
||||
let pos_y = data.read_i32::<LE>()?;
|
||||
let direction = data.read_u32::<LE>()?;
|
||||
let max_life = data.read_u16::<LE>()?;
|
||||
let stars = data.read_u16::<LE>()?;
|
||||
let life = data.read_u16::<LE>()?;
|
||||
let _ = data.read_u16::<LE>()?; // ???
|
||||
let current_weapon = data.read_u32::<LE>()?;
|
||||
let current_item = data.read_u32::<LE>()?;
|
||||
let equipment = data.read_u32::<LE>()?;
|
||||
let move_mode = data.read_u32::<LE>()?;
|
||||
let counter = data.read_u32::<LE>()?;
|
||||
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::<LE>()?;
|
||||
weap.level = data.read_u32::<LE>()?;
|
||||
weap.exp = data.read_u32::<LE>()?;
|
||||
weap.max_ammo = data.read_u32::<LE>()?;
|
||||
weap.ammo = data.read_u32::<LE>()?;
|
||||
}
|
||||
|
||||
for item in items.iter_mut() {
|
||||
*item = data.read_u32::<LE>()?;
|
||||
}
|
||||
|
||||
for slot in teleporter_slots.iter_mut() {
|
||||
slot.index = data.read_u32::<LE>()?;
|
||||
slot.event_num = data.read_u32::<LE>()?;
|
||||
}
|
||||
|
||||
let mut something = [0u8; 0x80];
|
||||
data.read_exact(&mut something)?;
|
||||
|
||||
if data.read_u32::<BE>()? != 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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue