Add game saving support
This commit is contained in:
parent
eb94343df3
commit
451c3671d7
|
@ -44,8 +44,9 @@ const CONFIG_NAME: &str = "/conf.toml";
|
|||
#[derive(Debug)]
|
||||
pub struct Filesystem {
|
||||
vfs: vfs::OverlayFS,
|
||||
user_vfs: vfs::OverlayFS,
|
||||
//resources_path: path::PathBuf,
|
||||
user_config_path: path::PathBuf,
|
||||
//user_config_path: path::PathBuf,
|
||||
user_data_path: path::PathBuf,
|
||||
}
|
||||
|
||||
|
@ -112,9 +113,11 @@ impl Filesystem {
|
|||
|
||||
// Set up VFS to merge resource path, root path, and zip path.
|
||||
let mut overlay = vfs::OverlayFS::new();
|
||||
// User data VFS.
|
||||
let mut user_overlay = vfs::OverlayFS::new();
|
||||
|
||||
let user_data_path;
|
||||
let user_config_path;
|
||||
//let user_config_path;
|
||||
// let mut resources_path;
|
||||
// let mut resources_zip_path;
|
||||
|
||||
|
@ -155,22 +158,23 @@ impl Filesystem {
|
|||
{
|
||||
user_data_path = project_dirs.data_local_dir();
|
||||
log::trace!("User-local data path: {:?}", user_data_path);
|
||||
let physfs = vfs::PhysicalFS::new(&user_data_path, true);
|
||||
overlay.push_back(Box::new(physfs));
|
||||
let physfs = vfs::PhysicalFS::new(&user_data_path, false);
|
||||
user_overlay.push_back(Box::new(physfs));
|
||||
}
|
||||
|
||||
// Writeable local dir, ~/.config/whatever/
|
||||
// Save game dir is read-write
|
||||
{
|
||||
/*{
|
||||
user_config_path = project_dirs.config_dir();
|
||||
log::trace!("User-local configuration path: {:?}", user_config_path);
|
||||
let physfs = vfs::PhysicalFS::new(&user_config_path, false);
|
||||
overlay.push_back(Box::new(physfs));
|
||||
}
|
||||
}*/
|
||||
|
||||
let fs = Filesystem {
|
||||
vfs: overlay,
|
||||
user_config_path: user_config_path.to_path_buf(),
|
||||
user_vfs: user_overlay,
|
||||
//user_config_path: user_config_path.to_path_buf(),
|
||||
user_data_path: user_data_path.to_path_buf(),
|
||||
};
|
||||
|
||||
|
@ -183,6 +187,12 @@ impl Filesystem {
|
|||
self.vfs.open(path.as_ref()).map(|f| File::VfsFile(f))
|
||||
}
|
||||
|
||||
/// Opens the given `path` from user directory and returns the resulting `File`
|
||||
/// in read-only mode.
|
||||
pub(crate) fn user_open<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
|
||||
self.user_vfs.open(path.as_ref()).map(|f| File::VfsFile(f))
|
||||
}
|
||||
|
||||
/// Opens a file in the user directory with the given
|
||||
/// [`filesystem::OpenOptions`](struct.OpenOptions.html).
|
||||
/// Note that even if you open a file read-write, it can only
|
||||
|
@ -192,7 +202,7 @@ impl Filesystem {
|
|||
path: P,
|
||||
options: OpenOptions,
|
||||
) -> GameResult<File> {
|
||||
self.vfs
|
||||
self.user_vfs
|
||||
.open_options(path.as_ref(), options)
|
||||
.map(|f| File::VfsFile(f))
|
||||
.map_err(|e| {
|
||||
|
@ -206,26 +216,31 @@ impl Filesystem {
|
|||
|
||||
/// Creates a new file in the user directory and opens it
|
||||
/// to be written to, truncating it if it already exists.
|
||||
pub(crate) fn create<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
|
||||
self.vfs.create(path.as_ref()).map(|f| File::VfsFile(f))
|
||||
pub(crate) fn user_create<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
|
||||
self.user_vfs.create(path.as_ref()).map(|f| File::VfsFile(f))
|
||||
}
|
||||
|
||||
/// Create an empty directory in the user dir
|
||||
/// with the given name. Any parents to that directory
|
||||
/// that do not exist will be created.
|
||||
pub(crate) fn create_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
|
||||
self.vfs.mkdir(path.as_ref())
|
||||
pub(crate) fn user_create_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
|
||||
self.user_vfs.mkdir(path.as_ref())
|
||||
}
|
||||
|
||||
/// Deletes the specified file in the user dir.
|
||||
pub(crate) fn delete<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
|
||||
self.vfs.rm(path.as_ref())
|
||||
pub(crate) fn user_delete<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
|
||||
self.user_vfs.rm(path.as_ref())
|
||||
}
|
||||
|
||||
/// Deletes the specified directory in the user dir,
|
||||
/// and all its contents!
|
||||
pub(crate) fn delete_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
|
||||
self.vfs.rmrf(path.as_ref())
|
||||
pub(crate) fn user_delete_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
|
||||
self.user_vfs.rmrf(path.as_ref())
|
||||
}
|
||||
|
||||
/// Check whether a file or directory in the user directory exists.
|
||||
pub(crate) fn user_exists<P: AsRef<path::Path>>(&self, path: P) -> bool {
|
||||
self.user_vfs.exists(path.as_ref())
|
||||
}
|
||||
|
||||
/// Check whether a file or directory exists.
|
||||
|
@ -233,6 +248,14 @@ impl Filesystem {
|
|||
self.vfs.exists(path.as_ref())
|
||||
}
|
||||
|
||||
/// Check whether a path points at a file.
|
||||
pub(crate) fn user_is_file<P: AsRef<path::Path>>(&self, path: P) -> bool {
|
||||
self.user_vfs
|
||||
.metadata(path.as_ref())
|
||||
.map(|m| m.is_file())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Check whether a path points at a file.
|
||||
pub(crate) fn is_file<P: AsRef<path::Path>>(&self, path: P) -> bool {
|
||||
self.vfs
|
||||
|
@ -241,6 +264,14 @@ impl Filesystem {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Check whether a path points at a directory.
|
||||
pub(crate) fn user_is_dir<P: AsRef<path::Path>>(&self, path: P) -> bool {
|
||||
self.user_vfs
|
||||
.metadata(path.as_ref())
|
||||
.map(|m| m.is_dir())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Check whether a path points at a directory.
|
||||
pub(crate) fn is_dir<P: AsRef<path::Path>>(&self, path: P) -> bool {
|
||||
self.vfs
|
||||
|
@ -249,6 +280,20 @@ impl Filesystem {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Returns a list of all files and directories in the user directory,
|
||||
/// in no particular order.
|
||||
///
|
||||
/// Lists the base directory if an empty path is given.
|
||||
pub(crate) fn user_read_dir<P: AsRef<path::Path>>(
|
||||
&mut self,
|
||||
path: P,
|
||||
) -> GameResult<Box<dyn Iterator<Item=path::PathBuf>>> {
|
||||
let itr = self.user_vfs.read_dir(path.as_ref())?.map(|fname| {
|
||||
fname.expect("Could not read file in read_dir()? Should never happen, I hope!")
|
||||
});
|
||||
Ok(Box::new(itr))
|
||||
}
|
||||
|
||||
/// Returns a list of all files and directories in the resource directory,
|
||||
/// in no particular order.
|
||||
///
|
||||
|
@ -311,38 +356,6 @@ impl Filesystem {
|
|||
pub(crate) fn mount_vfs(&mut self, vfs: Box<dyn vfs::VFS>) {
|
||||
self.vfs.push_back(vfs);
|
||||
}
|
||||
|
||||
/// Looks for a file named `/conf.toml` in any resource directory and
|
||||
/// loads it if it finds it.
|
||||
/// If it can't read it for some reason, returns an error.
|
||||
pub(crate) fn read_config(&mut self) -> GameResult<conf::Conf> {
|
||||
let conf_path = path::Path::new(CONFIG_NAME);
|
||||
if self.is_file(conf_path) {
|
||||
let mut file = self.open(conf_path)?;
|
||||
let c = conf::Conf::from_toml_file(&mut file)?;
|
||||
Ok(c)
|
||||
} else {
|
||||
Err(GameError::ConfigError(String::from(
|
||||
"Config file not found",
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes a `Conf` object and saves it to the user directory,
|
||||
/// overwriting any file already there.
|
||||
pub(crate) fn write_config(&mut self, conf: &conf::Conf) -> GameResult<()> {
|
||||
let conf_path = path::Path::new(CONFIG_NAME);
|
||||
let mut file = self.create(conf_path)?;
|
||||
conf.to_toml_file(&mut file)?;
|
||||
if self.is_file(conf_path) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(GameError::ConfigError(format!(
|
||||
"Failed to write config file at {}",
|
||||
conf_path.to_string_lossy()
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens the given path and returns the resulting `File`
|
||||
|
@ -351,9 +364,13 @@ pub fn open<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult<File
|
|||
ctx.filesystem.open(path)
|
||||
}
|
||||
|
||||
/// Opens the given path in the user directory and returns the resulting `File`
|
||||
/// in read-only mode.
|
||||
pub fn user_open<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult<File> {
|
||||
ctx.filesystem.user_open(path)
|
||||
}
|
||||
|
||||
/// Opens a file in the user directory with the given `filesystem::OpenOptions`.
|
||||
/// Note that even if you open a file read-only, it can only access
|
||||
/// files in the user directory.
|
||||
pub fn open_options<P: AsRef<path::Path>>(
|
||||
ctx: &mut Context,
|
||||
path: P,
|
||||
|
@ -364,26 +381,52 @@ pub fn open_options<P: AsRef<path::Path>>(
|
|||
|
||||
/// Creates a new file in the user directory and opens it
|
||||
/// to be written to, truncating it if it already exists.
|
||||
pub fn create<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult<File> {
|
||||
ctx.filesystem.create(path)
|
||||
pub fn user_create<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult<File> {
|
||||
ctx.filesystem.user_create(path)
|
||||
}
|
||||
|
||||
/// Create an empty directory in the user dir
|
||||
/// with the given name. Any parents to that directory
|
||||
/// that do not exist will be created.
|
||||
pub fn create_dir<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult {
|
||||
ctx.filesystem.create_dir(path.as_ref())
|
||||
pub fn user_create_dir<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult {
|
||||
ctx.filesystem.user_create_dir(path.as_ref())
|
||||
}
|
||||
|
||||
/// Deletes the specified file in the user dir.
|
||||
pub fn delete<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult {
|
||||
ctx.filesystem.delete(path.as_ref())
|
||||
pub fn user_delete<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult {
|
||||
ctx.filesystem.user_delete(path.as_ref())
|
||||
}
|
||||
|
||||
/// Deletes the specified directory in the user dir,
|
||||
/// and all its contents!
|
||||
pub fn delete_dir<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult {
|
||||
ctx.filesystem.delete_dir(path.as_ref())
|
||||
pub fn user_delete_dir<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult {
|
||||
ctx.filesystem.user_delete_dir(path.as_ref())
|
||||
}
|
||||
|
||||
/// Check whether a file or directory exists.
|
||||
pub fn user_exists<P: AsRef<path::Path>>(ctx: &Context, path: P) -> bool {
|
||||
ctx.filesystem.user_exists(path.as_ref())
|
||||
}
|
||||
|
||||
/// Check whether a path points at a file.
|
||||
pub fn user_is_file<P: AsRef<path::Path>>(ctx: &Context, path: P) -> bool {
|
||||
ctx.filesystem.user_is_file(path)
|
||||
}
|
||||
|
||||
/// Check whether a path points at a directory.
|
||||
pub fn user_is_dir<P: AsRef<path::Path>>(ctx: &Context, path: P) -> bool {
|
||||
ctx.filesystem.user_is_dir(path)
|
||||
}
|
||||
|
||||
/// Returns a list of all files and directories in the user directory,
|
||||
/// in no particular order.
|
||||
///
|
||||
/// Lists the base directory if an empty path is given.
|
||||
pub fn user_read_dir<P: AsRef<path::Path>>(
|
||||
ctx: &mut Context,
|
||||
path: P,
|
||||
) -> GameResult<Box<dyn Iterator<Item=path::PathBuf>>> {
|
||||
ctx.filesystem.user_read_dir(path)
|
||||
}
|
||||
|
||||
/// Check whether a file or directory exists.
|
||||
|
@ -443,19 +486,6 @@ pub fn mount_vfs(ctx: &mut Context, vfs: Box<dyn vfs::VFS>) {
|
|||
ctx.filesystem.mount_vfs(vfs)
|
||||
}
|
||||
|
||||
/// Looks for a file named `/conf.toml` in any resource directory and
|
||||
/// loads it if it finds it.
|
||||
/// If it can't read it for some reason, returns an error.
|
||||
pub fn read_config(ctx: &mut Context) -> GameResult<conf::Conf> {
|
||||
ctx.filesystem.read_config()
|
||||
}
|
||||
|
||||
/// Takes a `Conf` object and saves it to the user directory,
|
||||
/// overwriting any file already there.
|
||||
pub fn write_config(ctx: &mut Context, conf: &conf::Conf) -> GameResult {
|
||||
ctx.filesystem.write_config(conf)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::{Read, Write};
|
||||
|
@ -474,7 +504,8 @@ mod tests {
|
|||
ofs.push_front(Box::new(physfs));
|
||||
Filesystem {
|
||||
vfs: ofs,
|
||||
user_config_path: path,
|
||||
user_vfs: ofs,
|
||||
//user_config_path: path,
|
||||
user_data_path: path
|
||||
}
|
||||
}
|
||||
|
|
|
@ -241,7 +241,7 @@ impl Image {
|
|||
) -> GameResult {
|
||||
use std::io;
|
||||
let data = self.to_rgba8(ctx)?;
|
||||
let f = filesystem::create(ctx, path)?;
|
||||
let f = filesystem::user_create(ctx, path)?;
|
||||
let writer = &mut io::BufWriter::new(f);
|
||||
let color_format = image::ColorType::RGBA(8);
|
||||
match format {
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::weapon::{Weapon, WeaponLevel, WeaponType};
|
|||
|
||||
#[derive(Clone, Copy)]
|
||||
/// (id, amount)
|
||||
pub struct Item(u16, u16);
|
||||
pub struct Item(pub u16, pub u16);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Inventory {
|
||||
|
@ -70,6 +70,10 @@ impl Inventory {
|
|||
self.items.iter_mut().by_ref().find(|item| item.0 == item_id)
|
||||
}
|
||||
|
||||
pub fn get_item_idx(&self, idx: usize) -> Option<&Item> {
|
||||
self.items.get(idx)
|
||||
}
|
||||
|
||||
pub fn has_item(&self, item_id: u16) -> bool {
|
||||
self.items.iter().any(|item| item.0 == item_id)
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ impl Game {
|
|||
if let Some(scene) = self.scene.as_mut() {
|
||||
match self.state.timing_mode {
|
||||
TimingMode::_50Hz | TimingMode::_60Hz => {
|
||||
while self.start_time.elapsed().as_nanos() >= self.next_tick && self.loops < 3 {
|
||||
while self.start_time.elapsed().as_nanos() >= self.next_tick && self.loops < 10 {
|
||||
if (self.state.settings.speed - 1.0).abs() < 0.01 {
|
||||
self.next_tick += self.state.timing_mode.get_delta() as u128;
|
||||
} else {
|
||||
|
@ -255,13 +255,12 @@ static BACKENDS: [Backend; 4] = [
|
|||
fn init_ctx<P: Into<path::PathBuf> + Clone>(event_loop: &winit::event_loop::EventLoopWindowTarget<()>, resource_dir: P) -> GameResult<Context> {
|
||||
for backend in BACKENDS.iter() {
|
||||
let mut ctx = ContextBuilder::new("doukutsu-rs")
|
||||
.window_setup(WindowSetup::default().title("Cave Story (doukutsu-rs)"))
|
||||
.window_setup(WindowSetup::default().title("Cave Story ~ Doukutsu Monogatari (doukutsu-rs)"))
|
||||
.window_mode(WindowMode::default()
|
||||
.resizable(true)
|
||||
.min_dimensions(320.0, 240.0)
|
||||
.dimensions(854.0, 480.0))
|
||||
.add_resource_path(resource_dir.clone())
|
||||
.add_resource_path(path::PathBuf::from(str!("./")))
|
||||
.backend(*backend)
|
||||
.build(event_loop);
|
||||
|
||||
|
|
142
src/profile.rs
142
src/profile.rs
|
@ -1,6 +1,6 @@
|
|||
use std::io;
|
||||
|
||||
use byteorder::{BE, LE, ReadBytesExt};
|
||||
use byteorder::{BE, LE, ReadBytesExt, WriteBytesExt};
|
||||
use num_traits::{clamp, FromPrimitive};
|
||||
|
||||
use crate::common::{Direction, FadeState};
|
||||
|
@ -107,6 +107,142 @@ impl GameProfile {
|
|||
game_scene.player.stars = clamp(self.stars, 0, 3) as u8;
|
||||
}
|
||||
|
||||
pub fn dump(state: &mut SharedGameState, game_scene: &mut GameScene) -> GameProfile {
|
||||
let current_map = game_scene.stage_id as u32;
|
||||
let current_song = state.sound_manager.current_song() as u32;
|
||||
let pos_x = game_scene.player.x as i32;
|
||||
let pos_y = game_scene.player.y as i32;
|
||||
let direction = game_scene.player.direction;
|
||||
let max_life = game_scene.player.max_life;
|
||||
let stars = game_scene.player.stars as u16;
|
||||
let life = game_scene.player.life;
|
||||
let current_weapon = game_scene.inventory.current_weapon as u32;
|
||||
let current_item = game_scene.inventory.current_item as u32;
|
||||
let equipment = game_scene.player.equip.0 as u32;
|
||||
let control_mode = game_scene.player.control_mode as u32;
|
||||
let counter = 0; // TODO
|
||||
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 (idx, weap) in weapon_data.iter_mut().enumerate() {
|
||||
if let Some(weapon) = game_scene.inventory.get_weapon(idx) {
|
||||
weap.weapon_id = weapon.wtype as u32;
|
||||
weap.level = weapon.level as u32;
|
||||
weap.exp = weapon.experience as u32;
|
||||
weap.max_ammo = weapon.max_ammo as u32;
|
||||
weap.ammo = weapon.ammo as u32;
|
||||
}
|
||||
}
|
||||
|
||||
for (idx, item) in items.iter_mut().enumerate() {
|
||||
if let Some(sitem) = game_scene.inventory.get_item_idx(idx) {
|
||||
*item = sitem.0 as u32;
|
||||
}
|
||||
}
|
||||
|
||||
for (idx, slot) in teleporter_slots.iter_mut().enumerate() {
|
||||
if let Some(&(index, event_num)) = state.teleporter_slots.get(idx) {
|
||||
slot.index = index as u32;
|
||||
slot.event_num = event_num as u32;
|
||||
}
|
||||
}
|
||||
|
||||
let mut bidx = 0;
|
||||
let mut flags = [0u8; 1000];
|
||||
for bits in state.game_flags.as_slice() {
|
||||
let bytes = bits.to_le_bytes();
|
||||
for b in bytes.iter() {
|
||||
if let Some(out) = flags.get_mut(bidx) {
|
||||
*out = *b;
|
||||
}
|
||||
bidx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
GameProfile {
|
||||
current_map,
|
||||
current_song,
|
||||
pos_x,
|
||||
pos_y,
|
||||
direction,
|
||||
max_life,
|
||||
stars,
|
||||
life,
|
||||
current_weapon,
|
||||
current_item,
|
||||
equipment,
|
||||
control_mode,
|
||||
counter,
|
||||
weapon_data,
|
||||
items,
|
||||
teleporter_slots,
|
||||
flags,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_save<W: io::Write>(&self, mut data: W) -> GameResult {
|
||||
data.write_u64::<BE>(0x446f303431323230)?;
|
||||
|
||||
data.write_u32::<LE>(self.current_map)?;
|
||||
data.write_u32::<LE>(self.current_song)?;
|
||||
data.write_i32::<LE>(self.pos_x)?;
|
||||
data.write_i32::<LE>(self.pos_y)?;
|
||||
data.write_u32::<LE>(self.direction as u32)?;
|
||||
data.write_u16::<LE>(self.max_life)?;
|
||||
data.write_u16::<LE>(self.stars)?;
|
||||
data.write_u16::<LE>(self.life)?;
|
||||
data.write_u16::<LE>(0)?;
|
||||
data.write_u32::<LE>(self.current_weapon)?;
|
||||
data.write_u32::<LE>(self.current_item)?;
|
||||
data.write_u32::<LE>(self.equipment)?;
|
||||
data.write_u32::<LE>(self.control_mode)?;
|
||||
data.write_u32::<LE>(self.counter)?;
|
||||
|
||||
for weapon in self.weapon_data.iter() {
|
||||
data.write_u32::<LE>(weapon.weapon_id)?;
|
||||
data.write_u32::<LE>(weapon.level)?;
|
||||
data.write_u32::<LE>(weapon.exp)?;
|
||||
data.write_u32::<LE>(weapon.max_ammo)?;
|
||||
data.write_u32::<LE>(weapon.ammo)?;
|
||||
}
|
||||
|
||||
for item in self.items.iter().copied() {
|
||||
data.write_u32::<LE>(item)?;
|
||||
}
|
||||
|
||||
for slot in self.teleporter_slots.iter() {
|
||||
data.write_u32::<LE>(slot.index)?;
|
||||
data.write_u32::<LE>(slot.event_num)?;
|
||||
}
|
||||
|
||||
let mut something = [0u8; 0x80];
|
||||
data.write(&something);
|
||||
|
||||
data.write_u32::<BE>(0x464c4147)?;
|
||||
data.write(&self.flags)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_from_save<R: io::Read>(mut data: R) -> GameResult<GameProfile> {
|
||||
// Do041220
|
||||
if data.read_u64::<BE>()? != 0x446f303431323230 {
|
||||
|
@ -125,7 +261,7 @@ impl GameProfile {
|
|||
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 control_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 },
|
||||
|
@ -188,7 +324,7 @@ impl GameProfile {
|
|||
current_weapon,
|
||||
current_item,
|
||||
equipment,
|
||||
control_mode: move_mode,
|
||||
control_mode,
|
||||
counter,
|
||||
weapon_data,
|
||||
items,
|
||||
|
|
|
@ -20,6 +20,8 @@ use crate::str;
|
|||
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
|
||||
use crate::texture_set::TextureSet;
|
||||
use crate::touch_controls::TouchControls;
|
||||
use crate::ggez::filesystem::OpenOptions;
|
||||
use std::io::Seek;
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum TimingMode {
|
||||
|
@ -162,8 +164,19 @@ impl SharedGameState {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save_game(&mut self, game_scene: &mut GameScene, ctx: &mut Context) -> GameResult {
|
||||
if let Ok(data) = filesystem::open_options(ctx, "/Profile.dat", OpenOptions::new().write(true).create(true)) {
|
||||
let profile = GameProfile::dump(self, game_scene);
|
||||
profile.write_save(data)?;
|
||||
} else {
|
||||
log::warn!("Cannot open save file.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_or_start_game(&mut self, ctx: &mut Context) -> GameResult {
|
||||
if let Ok(data) = filesystem::open(ctx, "/Profile.dat") {
|
||||
if let Ok(data) = filesystem::user_open(ctx, "/Profile.dat") {
|
||||
match GameProfile::load_from_save(data) {
|
||||
Ok(profile) => {
|
||||
self.reset();
|
||||
|
|
|
@ -159,6 +159,10 @@ impl SoundManager {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn current_song(&self) -> usize {
|
||||
self.current_song_id
|
||||
}
|
||||
}
|
||||
|
||||
enum PlaybackMessage {
|
||||
|
|
|
@ -27,6 +27,7 @@ use crate::scene::title_scene::TitleScene;
|
|||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::str;
|
||||
use crate::weapon::WeaponType;
|
||||
use crate::profile::GameProfile;
|
||||
|
||||
/// Engine's text script VM operation codes.
|
||||
#[derive(EnumString, Debug, FromPrimitive, PartialEq)]
|
||||
|
@ -351,6 +352,7 @@ pub enum TextScriptExecutionState {
|
|||
WaitStanding(u16, u32),
|
||||
WaitConfirmation(u16, u32, u16, u8, ConfirmSelection),
|
||||
WaitFade(u16, u32),
|
||||
SaveProfile(u16, u32),
|
||||
LoadProfile,
|
||||
}
|
||||
|
||||
|
@ -627,6 +629,12 @@ impl TextScriptVM {
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
TextScriptExecutionState::SaveProfile(event, ip) => {
|
||||
state.save_game(game_scene, ctx)?;
|
||||
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
|
||||
break;
|
||||
}
|
||||
TextScriptExecutionState::LoadProfile => {
|
||||
state.load_or_start_game(ctx)?;
|
||||
break;
|
||||
|
@ -1304,6 +1312,9 @@ impl TextScriptVM {
|
|||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
OpCode::SVP => {
|
||||
exec_state = TextScriptExecutionState::SaveProfile(event, cursor.position() as u32);
|
||||
}
|
||||
OpCode::LDP => {
|
||||
state.control_flags.set_tick_world(false);
|
||||
state.control_flags.set_control_enabled(false);
|
||||
|
@ -1316,7 +1327,7 @@ impl TextScriptVM {
|
|||
OpCode::CIL | OpCode::CPS | OpCode::KE2 |
|
||||
OpCode::CRE | OpCode::CSS | OpCode::FLA | OpCode::MLP |
|
||||
OpCode::SPS | OpCode::FR2 |
|
||||
OpCode::STC | OpCode::SVP | OpCode::HM2 => {
|
||||
OpCode::STC | OpCode::HM2 => {
|
||||
log::warn!("unimplemented opcode: {:?}", op);
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
|
|
Loading…
Reference in New Issue