use path list for resource loading

This commit is contained in:
Alula 2022-02-10 08:54:20 +01:00
parent 3374f13c2b
commit c127ee4bd4
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
13 changed files with 209 additions and 177 deletions

View File

@ -16,25 +16,24 @@ pub struct BMFontRenderer {
}
impl BMFontRenderer {
pub fn load(root: &str, desc_path: &str, ctx: &mut Context) -> GameResult<BMFontRenderer> {
let root = PathBuf::from(root);
let full_path = &root.join(PathBuf::from(desc_path));
pub fn load(roots: &Vec<String>, desc_path: &str, ctx: &mut Context) -> GameResult<BMFontRenderer> {
let full_path = PathBuf::from(desc_path);
let desc_stem =
full_path.file_stem().ok_or_else(|| ResourceLoadError("Cannot extract the file stem.".to_owned()))?;
let stem = full_path.parent().unwrap_or(full_path).join(desc_stem);
let stem = full_path.parent().unwrap_or(&full_path).join(desc_stem);
let font = BMFont::load_from(filesystem::open(ctx, &full_path)?)?;
let font = BMFont::load_from(filesystem::open_find(ctx, roots, &full_path)?)?;
let mut pages = Vec::new();
let (zeros, _, _) = FILE_TYPES
.iter()
.map(|ext| (1, ext, format!("{}_0{}", stem.to_string_lossy(), ext)))
.find(|(_, _, path)| filesystem::exists(ctx, &path))
.find(|(_, _, path)| filesystem::exists_find(ctx, roots, &path))
.or_else(|| {
FILE_TYPES
.iter()
.map(|ext| (2, ext, format!("{}_00{}", stem.to_string_lossy(), ext)))
.find(|(_, _, path)| filesystem::exists(ctx, &path))
.find(|(_, _, path)| filesystem::exists_find(ctx, roots, &path))
})
.ok_or_else(|| ResourceLoadError(format!("Cannot find glyph atlas 0 for font: {:?}", desc_path)))?;
@ -164,7 +163,7 @@ impl BMFontRenderer {
let batch = texture_set.get_or_load_batch(ctx, constants, page_tex)?;
let mut offset_x = x;
for (chr, glyph) in chars.iter() {
for (_chr, glyph) in chars.iter() {
if glyph.page == page {
batch.add_rect_scaled_tinted(
offset_x + (glyph.xoffset as f32 * constants.font_scale),

View File

@ -12,6 +12,8 @@ use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::player::ControlMode;
use crate::scripting::tsc::text_script::TextScriptEncoding;
use crate::settings::Settings;
use crate::shared_game_state::Season;
use crate::sound::pixtone::{Channel, Envelope, PixToneParameters, Waveform};
use crate::sound::SoundManager;
@ -276,6 +278,7 @@ impl Clone for TitleConsts {
#[derive(Debug)]
pub struct EngineConstants {
pub base_paths: Vec<String>,
pub is_cs_plus: bool,
pub is_switch: bool,
pub supports_og_textures: bool,
@ -303,6 +306,7 @@ pub struct EngineConstants {
impl Clone for EngineConstants {
fn clone(&self) -> EngineConstants {
EngineConstants {
base_paths: self.base_paths.clone(),
is_cs_plus: self.is_cs_plus,
is_switch: self.is_switch,
supports_og_textures: self.supports_og_textures,
@ -332,6 +336,7 @@ impl Clone for EngineConstants {
impl EngineConstants {
pub fn defaults() -> Self {
EngineConstants {
base_paths: Vec::new(),
is_cs_plus: false,
is_switch: false,
supports_og_textures: false,
@ -1664,15 +1669,38 @@ impl EngineConstants {
self.game.tile_offset_x = 3;
}
pub fn rebuild_path_list(&mut self, mod_path: Option<String>, season: Season, settings: &Settings) {
self.base_paths.clear();
self.base_paths.push("/".to_owned());
if self.is_cs_plus {
self.base_paths.insert(0, "/base/".to_owned());
if settings.original_textures {
self.base_paths.insert(0, "/base/ogph/".to_string())
} else if settings.seasonal_textures {
match season {
Season::Halloween => self.base_paths.insert(0, "/Halloween/season/".to_string()),
Season::Christmas => self.base_paths.insert(0, "/Christmas/season/".to_string()),
_ => {}
}
}
}
if let Some(mod_path) = mod_path {
self.base_paths.insert(0, mod_path);
}
}
pub fn apply_constant_json_files(&mut self) {}
/// Loads bullet.tbl and arms_level.tbl from CS+ files,
/// even though they match vanilla 1:1, we should load them for completeness
/// or if any crazy person uses it for a CS+ mod...
pub fn load_csplus_tables(&mut self, ctx: &mut Context) -> GameResult {
if filesystem::exists(ctx, "/base/bullet.tbl") {
if let Ok(mut file) = filesystem::open_find(ctx, &self.base_paths, "/bullet.tbl") {
let mut data = Vec::new();
filesystem::open(ctx, "/base/bullet.tbl")?.read_to_end(&mut data)?;
file.read_to_end(&mut data)?;
let bullets = data.len() / 0x2A;
let mut f = Cursor::new(data);
@ -1699,13 +1727,11 @@ impl EngineConstants {
self.weapon.bullet_table = new_bullet_table;
log::info!("Loaded bullet.tbl.");
} else {
log::warn!("CS+ bullet.tbl not found.");
}
if filesystem::exists(ctx, "/base/arms_level.tbl") {
if let Ok(mut file) = filesystem::open_find(ctx, &self.base_paths, "/arms_level.tbl") {
let mut data = Vec::new();
filesystem::open(ctx, "/base/arms_level.tbl")?.read_to_end(&mut data)?;
file.read_to_end(&mut data)?;
let mut f = Cursor::new(data);
let mut new_level_table = EngineConstants::defaults().weapon.level_table;
@ -1718,8 +1744,6 @@ impl EngineConstants {
self.weapon.level_table = new_level_table;
log::info!("Loaded arms_level.tbl.");
} else {
log::warn!("CS+ arms_level.tbl not found.")
}
Ok(())
@ -1728,8 +1752,9 @@ impl EngineConstants {
/// Load in the `faceanm.dat` file that details the Switch extensions to the <FAC command
/// It's actually a text file, go figure
pub fn load_animated_faces(&mut self, ctx: &mut Context) -> GameResult {
if filesystem::exists(ctx, "/base/faceanm.dat") {
let file = filesystem::open(ctx, "/base/faceanm.dat")?;
self.animated_face_table.clear();
if let Ok(mut file) = filesystem::open_find(ctx, &self.base_paths, "/faceanm.dat") {
let buf = BufReader::new(file);
let mut face_id = 1;
let mut anim_id = 0;

View File

@ -2,6 +2,7 @@ use std::fmt;
use std::io;
use std::io::SeekFrom;
use std::path;
use std::path::PathBuf;
use crate::framework::context::Context;
use crate::framework::error::{GameError, GameResult};
@ -73,10 +74,7 @@ impl Filesystem {
// User data VFS.
let user_overlay = vfs::OverlayFS::new();
Filesystem {
vfs: overlay,
user_vfs: user_overlay,
}
Filesystem { vfs: overlay, user_vfs: user_overlay }
}
/// Opens the given `path` and returns the resulting `File`
@ -96,12 +94,9 @@ impl Filesystem {
/// Note that even if you open a file read-write, it can only
/// write to files in the "user" directory.
pub(crate) fn open_options<P: AsRef<path::Path>>(&self, path: P, options: OpenOptions) -> GameResult<File> {
self.user_vfs
.open_options(path.as_ref(), options)
.map(|f| File::VfsFile(f))
.map_err(|e| {
GameError::ResourceLoadError(format!("Tried to open {:?} but got error: {:?}", path.as_ref(), e))
})
self.user_vfs.open_options(path.as_ref(), options).map(|f| File::VfsFile(f)).map_err(|e| {
GameError::ResourceLoadError(format!("Tried to open {:?} but got error: {:?}", path.as_ref(), e))
})
}
/// Creates a new file in the user directory and opens it
@ -140,10 +135,7 @@ impl Filesystem {
/// 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)
self.user_vfs.metadata(path.as_ref()).map(|m| m.is_file()).unwrap_or(false)
}
/// Check whether a path points at a file.
@ -153,10 +145,7 @@ impl Filesystem {
/// 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)
self.user_vfs.metadata(path.as_ref()).map(|m| m.is_dir()).unwrap_or(false)
}
/// Check whether a path points at a directory.
@ -240,6 +229,23 @@ pub fn open<P: AsRef<path::Path>>(ctx: &Context, path: P) -> GameResult<File> {
ctx.filesystem.open(path)
}
pub fn open_find<P: AsRef<path::Path>>(ctx: &Context, roots: &Vec<String>, path: P) -> GameResult<File> {
let mut errors = Vec::new();
for root in roots {
let mut full_path = root.to_string();
full_path.push_str(path.as_ref().to_string_lossy().as_ref());
let result = ctx.filesystem.open(&full_path);
if result.is_ok() {
return result;
}
errors.push((PathBuf::from(full_path), result.err().unwrap()));
}
Err(GameError::ResourceNotFound("File not found".to_owned(), errors))
}
/// 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: &Context, path: P) -> GameResult<File> {
@ -306,6 +312,20 @@ pub fn exists<P: AsRef<path::Path>>(ctx: &Context, path: P) -> bool {
ctx.filesystem.exists(path.as_ref())
}
pub fn exists_find<P: AsRef<path::Path>>(ctx: &Context, roots: &Vec<String>, path: P) -> bool {
for root in roots {
let mut full_path = root.to_string();
full_path.push_str(path.as_ref().to_string_lossy().as_ref());
if ctx.filesystem.exists(full_path) {
return true;
}
}
false
}
/// Check whether a path points at a file.
pub fn is_file<P: AsRef<path::Path>>(ctx: &Context, path: P) -> bool {
ctx.filesystem.is_file(path)

View File

@ -69,7 +69,7 @@ impl Map {
pub fn load_pxpack<R: io::Read>(
mut map_data: R,
root: &str,
roots: &Vec<String>,
data: &mut StageData,
ctx: &mut Context,
) -> GameResult<Map> {
@ -206,11 +206,13 @@ impl Map {
)?;
}
if let Ok(mut attrib_data) = filesystem::open(ctx, [root, "Stage/", &tileset_fg, ".pxa"].join("")) {
if let Ok(mut attrib_data) = filesystem::open_find(ctx, roots, ["/Stage/", &tileset_fg, ".pxa"].join("")) {
if attrib_data.read_exact(&mut attrib).is_err() {
log::warn!("Map attribute data is shorter than 256 bytes!");
}
} else if let Ok(mut attrib_data) = filesystem::open(ctx, [root, "Stage/", &tileset_fg, ".pxattr"].join("")) {
} else if let Ok(mut attrib_data) =
filesystem::open_find(ctx, roots, ["/Stage/", &tileset_fg, ".pxattr"].join(""))
{
attrib_data.read_exact(&mut magic)?;
if &magic != b"pxMAP01\0" {

View File

@ -47,9 +47,14 @@ impl SettingsMenu {
.push_entry(MenuEntry::Toggle("Motion interpolation:".to_string(), state.settings.motion_interpolation));
self.graphics.push_entry(MenuEntry::Toggle("Subpixel scrolling:".to_string(), state.settings.subpixel_coords));
// NS version uses two different maps, therefore we can't dynamically switch between graphics presets.
if state.constants.supports_og_textures {
self.graphics
.push_entry(MenuEntry::Toggle("Original textures".to_string(), state.settings.original_textures));
if !state.constants.is_switch || self.on_title {
self.graphics
.push_entry(MenuEntry::Toggle("Original textures".to_string(), state.settings.original_textures));
} else {
self.graphics.push_entry(MenuEntry::Disabled("Original textures".to_string()));
}
} else {
self.graphics.push_entry(MenuEntry::Hidden);
}
@ -227,7 +232,7 @@ impl SettingsMenu {
MenuSelectionResult::Selected(4, toggle) => {
if let MenuEntry::Toggle(_, value) = toggle {
state.settings.original_textures = !state.settings.original_textures;
state.reload_textures();
state.reload_resources(ctx)?;
let _ = state.settings.save(ctx);
*value = state.settings.original_textures;
@ -236,7 +241,7 @@ impl SettingsMenu {
MenuSelectionResult::Selected(5, toggle) => {
if let MenuEntry::Toggle(_, value) = toggle {
state.settings.seasonal_textures = !state.settings.seasonal_textures;
state.reload_textures();
state.reload_graphics();
let _ = state.settings.save(ctx);
*value = state.settings.seasonal_textures;

View File

@ -85,16 +85,17 @@ pub struct BasicPlayerSkin {
impl BasicPlayerSkin {
pub fn new(texture_name: String, state: &SharedGameState, ctx: &mut Context) -> BasicPlayerSkin {
let metapath = format!("{}/{}.dskinmeta", state.base_path, texture_name);
let mut metadata = DEFAULT_SKINMETA.clone();
if let Ok(file) = filesystem::open(ctx, metapath) {
let meta_path = format!("/{}.dskinmeta", texture_name);
if let Ok(file) = filesystem::open_find(ctx, &state.constants.base_paths, &meta_path) {
match serde_json::from_reader::<File, SkinMeta>(file) {
Ok(meta) if SUPPORTED_SKINMETA_VERSIONS.contains(&meta.version) => {
metadata = meta;
}
Ok(meta) => {
log::warn!("Unsupported skin metadata file version: {}", meta.version);
log::warn!("{}: Unsupported skin metadata file version: {}", meta_path, meta.version);
}
Err(err) => {
log::warn!("Failed to load skin metadata file: {:?}", err);

View File

@ -71,7 +71,7 @@ impl EditorScene {
}
if let Some(stage) = state.stages.get(stage_id) {
let stage = Stage::load(&state.base_path, stage, ctx)?;
let stage = Stage::load(&state.constants.base_paths, stage, ctx)?;
let new_instance = EditorInstance::new(stage_id, stage);
self.instances.push(new_instance);

View File

@ -102,7 +102,7 @@ const CUTSCENE_SKIP_WAIT: u16 = 50;
impl GameScene {
pub fn new(state: &mut SharedGameState, ctx: &mut Context, id: usize) -> GameResult<Self> {
info!("Loading stage {} ({})", id, &state.stages[id].map);
let stage = Stage::load(&state.base_path, &state.stages[id], ctx)?;
let stage = Stage::load(&state.constants.base_paths, &state.stages[id], ctx)?;
info!("Loaded stage: {}", stage.data.name);
GameScene::from_stage(state, ctx, stage, id)
@ -112,13 +112,17 @@ impl GameScene {
let mut water_params = WaterParams::new();
let mut water_renderer = WaterRenderer::new();
if let Ok(water_param_file) =
filesystem::open(ctx, [&state.base_path, "Stage/", &state.stages[id].tileset.name, ".pxw"].join(""))
{
water_params.load_from(water_param_file)?;
info!("Loaded water parameters file.");
if !state.settings.original_textures {
if let Ok(water_param_file) = filesystem::open_find(
ctx,
&state.constants.base_paths,
["Stage/", &state.stages[id].tileset.name, ".pxw"].join(""),
) {
water_params.load_from(water_param_file)?;
info!("Loaded water parameters file.");
water_renderer.initialize(stage.map.find_water_regions());
water_renderer.initialize(stage.map.find_water_regions());
}
}
let stage_textures = {
@ -1524,7 +1528,7 @@ impl Scene for GameScene {
.wrapping_add(self.stage_id as i32)
.rotate_right(7);
state.game_rng = XorShift::new(seed);
state.textscript_vm.set_scene_script(self.stage.load_text_script(&state.base_path, &state.constants, ctx)?);
state.textscript_vm.set_scene_script(self.stage.load_text_script(&state.constants.base_paths, &state.constants, ctx)?);
state.textscript_vm.suspend = false;
state.tile_size = self.stage.map.tile_size;
#[cfg(feature = "scripting-lua")]
@ -1533,7 +1537,7 @@ impl Scene for GameScene {
self.player1.controller = state.settings.create_player1_controller();
self.player2.controller = state.settings.create_player2_controller();
let npcs = self.stage.load_npcs(&state.base_path, ctx)?;
let npcs = self.stage.load_npcs(&state.constants.base_paths, ctx)?;
for npc_data in npcs.iter() {
log::info!("creating npc: {:?}", npc_data);

View File

@ -1,11 +1,12 @@
use imgui::Ui;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::framework::ui::Components;
use crate::npc::NPCTable;
use crate::scene::no_data_scene::NoDataScene;
use crate::scene::Scene;
use crate::scripting::tsc::credit_script::CreditScript;
use crate::scripting::tsc::text_script::TextScript;
use crate::shared_game_state::SharedGameState;
use crate::stage::StageData;
@ -19,26 +20,7 @@ impl LoadingScene {
}
fn load_stuff(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let stages = StageData::load_stage_table(ctx, &state.base_path)?;
state.stages = stages;
let npc_tbl = filesystem::open(ctx, [&state.base_path, "/npc.tbl"].join(""))?;
let npc_table = NPCTable::load_from(npc_tbl)?;
state.npc_table = npc_table;
let head_tsc = filesystem::open(ctx, [&state.base_path, "/Head.tsc"].join(""))?;
let head_script = TextScript::load_from(head_tsc, &state.constants)?;
state.textscript_vm.set_global_script(head_script);
let arms_item_tsc = filesystem::open(ctx, [&state.base_path, "/ArmsItem.tsc"].join(""))?;
let arms_item_script = TextScript::load_from(arms_item_tsc, &state.constants)?;
state.textscript_vm.set_inventory_script(arms_item_script);
let stage_select_tsc = filesystem::open(ctx, [&state.base_path, "/StageSelect.tsc"].join(""))?;
let stage_select_script = TextScript::load_from(stage_select_tsc, &state.constants)?;
state.textscript_vm.set_stage_select_script(stage_select_script);
let credit_tsc = filesystem::open(ctx, [&state.base_path, "/Credit.tsc"].join(""))?;
let credit_script = CreditScript::load_from(credit_tsc, &state.constants)?;
state.creditscript_vm.set_script(credit_script);
state.reload_resources(ctx)?;
if ctx.headless {
log::info!("Headless mode detected, skipping intro and loading last saved game.");

View File

@ -1630,7 +1630,7 @@ impl TextScriptVM {
for path in &state.constants.credit_illustration_paths {
let path = format!("{}Credit{:02}", path, number);
if state.texture_set.find_texture(ctx, &path).is_some() {
if state.texture_set.find_texture(ctx, &state.constants.base_paths, &path).is_some() {
state.textscript_vm.current_illustration = Some(path);
break;
}

View File

@ -6,15 +6,15 @@ use chrono::{Datelike, Local};
use crate::bmfont_renderer::BMFontRenderer;
use crate::caret::{Caret, CaretType};
use crate::common::{ControlFlags, Direction, FadeState};
use crate::components::draw_common::{draw_number, Alignment};
use crate::components::draw_common::{Alignment, draw_number};
use crate::engine_constants::EngineConstants;
use crate::framework::{filesystem, graphics};
use crate::framework::backend::BackendTexture;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics::{create_texture_mutable, set_render_target};
use crate::framework::keyboard::ScanCode;
use crate::framework::vfs::OpenOptions;
use crate::framework::{filesystem, graphics};
#[cfg(feature = "hooks")]
use crate::hooks::init_hooks;
use crate::input::touch_controls::TouchControls;
@ -22,12 +22,12 @@ use crate::npc::NPCTable;
use crate::profile::GameProfile;
use crate::rng::XorShift;
use crate::scene::game_scene::GameScene;
use crate::scene::title_scene::TitleScene;
use crate::scene::Scene;
use crate::scene::title_scene::TitleScene;
#[cfg(feature = "scripting-lua")]
use crate::scripting::lua::LuaScriptingState;
use crate::scripting::tsc::credit_script::CreditScriptVM;
use crate::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
use crate::scripting::tsc::credit_script::{CreditScript, CreditScriptVM};
use crate::scripting::tsc::text_script::{ScriptMode, TextScript, TextScriptExecutionState, TextScriptVM};
use crate::settings::Settings;
use crate::sound::SoundManager;
use crate::stage::StageData;
@ -158,7 +158,7 @@ pub struct SharedGameState {
pub teleporter_slots: Vec<(u16, u16)>,
pub carets: Vec<Caret>,
pub touch_controls: TouchControls,
pub base_path: String,
pub mod_path: Option<String>,
pub npc_table: NPCTable,
pub npc_super_pos: (i32, i32),
pub npc_curly_target: (i32, i32),
@ -192,7 +192,6 @@ impl SharedGameState {
pub fn new(ctx: &mut Context) -> GameResult<SharedGameState> {
let mut constants = EngineConstants::defaults();
let sound_manager = SoundManager::new(ctx)?;
let mut base_path = "/";
let settings = Settings::load(ctx)?;
if filesystem::exists(ctx, "/base/lighting.tbl") {
@ -200,50 +199,33 @@ impl SharedGameState {
ctx.size_hint = (854, 480);
constants.apply_csplus_patches(&sound_manager);
constants.apply_csplus_nx_patches();
constants.load_csplus_tables(ctx)?;
constants.load_animated_faces(ctx)?;
base_path = "/base/";
} else if filesystem::exists(ctx, "/base/Nicalis.bmp") || filesystem::exists(ctx, "/base/Nicalis.png") {
info!("Cave Story+ (PC) data files detected.");
constants.apply_csplus_patches(&sound_manager);
constants.load_csplus_tables(ctx)?;
base_path = "/base/";
} else if filesystem::exists(ctx, "/mrmap.bin") {
info!("CSE2E data files detected.");
} else if filesystem::exists(ctx, "/stage.dat") {
info!("NXEngine-evo data files detected.");
}
let font = BMFontRenderer::load(base_path, &constants.font_path, ctx)
.or_else(|_| BMFontRenderer::load("/", "builtin/builtin_font.fnt", ctx))?;
let season = Season::current();
let mut texture_set = TextureSet::new(base_path);
constants.rebuild_path_list(None, season, &settings);
if constants.is_cs_plus {
texture_set.apply_seasonal_content(season, &settings);
}
let font = BMFontRenderer::load(&constants.base_paths, &constants.font_path, ctx)
.or_else(|e| {
log::warn!("Failed to load font, using built-in: {}", e);
BMFontRenderer::load(&vec!["/".to_owned()], "/builtin/builtin_font.fnt", ctx)
})?;
for i in 0..0xffu8 {
let path = format!("{}/pxt/fx{:02x}.pxt", base_path, i);
if let Ok(file) = filesystem::open(ctx, path) {
sound_manager.set_sample_params_from_file(i, file)?;
continue;
}
let path = format!("/pxt/fx{:02x}.pxt", i);
if let Ok(file) = filesystem::open(ctx, path) {
sound_manager.set_sample_params_from_file(i, file)?;
continue;
}
let path = format!("{}/PixTone/{:03}.pxt", base_path, i);
if let Ok(file) = filesystem::open(ctx, path) {
if let Ok(file) = filesystem::open_find(ctx, &constants.base_paths, path) {
sound_manager.set_sample_params_from_file(i, file)?;
continue;
}
let path = format!("/PixTone/{:03}.pxt", i);
if let Ok(file) = filesystem::open(ctx, path) {
if let Ok(file) = filesystem::open_find(ctx, &constants.base_paths, path) {
sound_manager.set_sample_params_from_file(i, file)?;
continue;
}
@ -269,7 +251,7 @@ impl SharedGameState {
teleporter_slots: Vec::with_capacity(8),
carets: Vec::with_capacity(32),
touch_controls: TouchControls::new(),
base_path: base_path.to_owned(),
mod_path: None,
npc_table: NPCTable::new(),
npc_super_pos: (0, 0),
npc_curly_target: (0, 0),
@ -290,7 +272,7 @@ impl SharedGameState {
menu_character: MenuCharacter::Quote,
constants,
font,
texture_set,
texture_set: TextureSet::new(),
#[cfg(feature = "scripting-lua")]
lua: LuaScriptingState::new(),
sound_manager,
@ -324,18 +306,45 @@ impl SharedGameState {
}
}
pub fn reload_textures(&mut self) {
let mut texture_set = TextureSet::new(&self.base_path);
pub fn reload_resources(&mut self, ctx: &mut Context) -> GameResult {
self.constants.rebuild_path_list(self.mod_path.clone(), self.season, &self.settings);
self.constants.load_csplus_tables(ctx)?;
self.constants.load_animated_faces(ctx)?;
let stages = StageData::load_stage_table(ctx, &self.constants.base_paths)?;
self.stages = stages;
if self.constants.is_cs_plus {
texture_set.apply_seasonal_content(self.season, &self.settings);
}
let npc_tbl = filesystem::open_find(ctx, &self.constants.base_paths, "/npc.tbl")?;
let npc_table = NPCTable::load_from(npc_tbl)?;
self.npc_table = npc_table;
self.texture_set = texture_set;
let head_tsc = filesystem::open_find(ctx, &self.constants.base_paths, "/Head.tsc")?;
let head_script = TextScript::load_from(head_tsc, &self.constants)?;
self.textscript_vm.set_global_script(head_script);
let arms_item_tsc = filesystem::open_find(ctx, &self.constants.base_paths, "/ArmsItem.tsc")?;
let arms_item_script = TextScript::load_from(arms_item_tsc, &self.constants)?;
self.textscript_vm.set_inventory_script(arms_item_script);
let stage_select_tsc = filesystem::open_find(ctx, &self.constants.base_paths, "/StageSelect.tsc")?;
let stage_select_script = TextScript::load_from(stage_select_tsc, &self.constants)?;
self.textscript_vm.set_stage_select_script(stage_select_script);
let credit_tsc = filesystem::open_find(ctx, &self.constants.base_paths, "/Credit.tsc")?;
let credit_script = CreditScript::load_from(credit_tsc, &self.constants)?;
self.creditscript_vm.set_script(credit_script);
self.texture_set.unload_all();
Ok(())
}
pub fn reload_graphics(&mut self) {
self.constants.rebuild_path_list(self.mod_path.clone(), self.season, &self.settings);
self.texture_set.unload_all();
}
pub fn graphics_reset(&mut self) {
self.reload_textures();
self.texture_set.unload_all();
}
pub fn start_new_game(&mut self, ctx: &mut Context) -> GameResult {

View File

@ -14,6 +14,7 @@ use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::map::{Map, NPCData};
use crate::scripting::tsc::text_script::TextScript;
use crate::GameError;
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct NpcType {
@ -274,20 +275,20 @@ fn from_shift_jis(s: &[u8]) -> String {
}
impl StageData {
pub fn load_stage_table(ctx: &mut Context, root: &str) -> GameResult<Vec<Self>> {
let stage_tbl_path = [root, "stage.tbl"].join("");
let stage_sect_path = [root, "stage.sect"].join("");
let mrmap_bin_path = [root, "mrmap.bin"].join("");
let stage_dat_path = [root, "stage.dat"].join("");
pub fn load_stage_table(ctx: &mut Context, roots: &Vec<String>) -> GameResult<Vec<Self>> {
let stage_tbl_path = "/stage.tbl";
let stage_sect_path = "/stage.sect";
let mrmap_bin_path = "/mrmap.bin";
let stage_dat_path = "/stage.dat";
if filesystem::exists(ctx, &stage_tbl_path) {
if let Ok(mut file) = filesystem::open_find(ctx, roots, stage_tbl_path) {
// Cave Story+ stage table.
let mut stages = Vec::new();
info!("Loading Cave Story+ stage table from {}", &stage_tbl_path);
let mut data = Vec::new();
filesystem::open(ctx, stage_tbl_path)?.read_to_end(&mut data)?;
file.read_to_end(&mut data)?;
let count = data.len() / 0xe5;
let mut f = Cursor::new(data);
@ -333,14 +334,14 @@ impl StageData {
}
return Ok(stages);
} else if filesystem::exists(ctx, &stage_sect_path) {
} else if let Ok(mut file) = filesystem::open_find(ctx, roots, stage_sect_path) {
// Cave Story freeware executable dump.
let mut stages = Vec::new();
info!("Loading Cave Story freeware exe dump stage table from {}", &stage_sect_path);
let mut data = Vec::new();
filesystem::open(ctx, stage_sect_path)?.read_to_end(&mut data)?;
file.read_to_end(&mut data)?;
let count = data.len() / 0xc8;
let mut f = Cursor::new(data);
@ -389,17 +390,16 @@ impl StageData {
}
return Ok(stages);
} else if filesystem::exists(ctx, &mrmap_bin_path) {
} else if let Ok(mut file) = filesystem::open_find(ctx, roots, mrmap_bin_path) {
// Moustache Rider stage table
let mut stages = Vec::new();
info!("Loading Moustache Rider stage table from {}", &mrmap_bin_path);
let mut data = Vec::new();
let mut fh = filesystem::open(ctx, &mrmap_bin_path)?;
let count = fh.read_u32::<LE>()?;
fh.read_to_end(&mut data)?;
let count = file.read_u32::<LE>()?;
file.read_to_end(&mut data)?;
if data.len() < count as usize * 0x74 {
return Err(ResourceLoadError(
@ -448,16 +448,15 @@ impl StageData {
}
return Ok(stages);
} else if filesystem::exists(ctx, &stage_dat_path) {
} else if let Ok(mut file) = filesystem::open_find(ctx, roots, stage_dat_path) {
let mut stages = Vec::new();
info!("Loading NXEngine stage table from {}", &stage_dat_path);
let mut data = Vec::new();
let mut fh = filesystem::open(ctx, &stage_dat_path)?;
let count = fh.read_u8()? as usize;
fh.read_to_end(&mut data)?;
let count = file.read_u8()? as usize;
file.read_to_end(&mut data)?;
if data.len() < count * 0x49 {
return Err(ResourceLoadError(
@ -518,40 +517,41 @@ pub struct Stage {
}
impl Stage {
pub fn load(root: &str, data: &StageData, ctx: &mut Context) -> GameResult<Self> {
pub fn load(roots: &Vec<String>, data: &StageData, ctx: &mut Context) -> GameResult<Self> {
let mut data = data.clone();
if let Ok(pxpack_file) = filesystem::open(ctx, [root, "Stage/", &data.map, ".pxpack"].join("")) {
let map = Map::load_pxpack(pxpack_file, root, &mut data, ctx)?;
if let Ok(pxpack_file) = filesystem::open_find(ctx, roots, ["/Stage/", &data.map, ".pxpack"].join("")) {
let map = Map::load_pxpack(pxpack_file, roots, &mut data, ctx)?;
let stage = Self { map, data };
Ok(stage)
} else {
let map_file = filesystem::open(ctx, [root, "Stage/", &data.map, ".pxm"].join(""))?;
let attrib_file = filesystem::open(ctx, [root, "Stage/", &data.tileset.name, ".pxa"].join(""))?;
return Ok(stage);
} else if let Ok(map_file) = filesystem::open_find(ctx, roots, ["/Stage/", &data.map, ".pxm"].join("")) {
let attrib_file = filesystem::open_find(ctx, roots, ["/Stage/", &data.tileset.name, ".pxa"].join(""))?;
let map = Map::load_pxm(map_file, attrib_file)?;
let stage = Self { map, data };
Ok(stage)
return Ok(stage);
}
Err(GameError::ResourceLoadError(format!("Stage {} not found", data.map)))
}
pub fn load_text_script(
&self,
root: &str,
roots: &Vec<String>,
constants: &EngineConstants,
ctx: &mut Context,
) -> GameResult<TextScript> {
let tsc_file = filesystem::open(ctx, [root, "Stage/", &self.data.map, ".tsc"].join(""))?;
let tsc_file = filesystem::open_find(ctx, roots, ["/Stage/", &self.data.map, ".tsc"].join(""))?;
let text_script = TextScript::load_from(tsc_file, constants)?;
Ok(text_script)
}
pub fn load_npcs(&self, root: &str, ctx: &mut Context) -> GameResult<Vec<NPCData>> {
let pxe_file = filesystem::open(ctx, [root, "Stage/", &self.data.map, ".pxe"].join(""))?;
pub fn load_npcs(&self, roots: &Vec<String>, ctx: &mut Context) -> GameResult<Vec<NPCData>> {
let pxe_file = filesystem::open_find(ctx, roots, ["/Stage/", &self.data.map, ".pxe"].join(""))?;
let npc_data = NPCData::load_from(pxe_file)?;
Ok(npc_data)

View File

@ -6,7 +6,7 @@ use itertools::Itertools;
use log::info;
use crate::common;
use crate::common::{Rect, FILE_TYPES};
use crate::common::{FILE_TYPES, Rect};
use crate::engine_constants::EngineConstants;
use crate::framework::backend::{BackendTexture, SpriteBatchCommand};
use crate::framework::context::Context;
@ -409,29 +409,16 @@ impl SpriteBatch for CombinedBatch {
pub struct TextureSet {
pub tex_map: HashMap<String, Box<dyn SpriteBatch>>,
pub paths: Vec<String>,
dummy_batch: Box<dyn SpriteBatch>,
}
impl TextureSet {
pub fn new(base_path: &str) -> TextureSet {
TextureSet {
tex_map: HashMap::new(),
paths: vec![base_path.to_string(), "".to_string()],
dummy_batch: Box::new(DummyBatch),
}
pub fn new() -> TextureSet {
TextureSet { tex_map: HashMap::new(), dummy_batch: Box::new(DummyBatch) }
}
pub fn apply_seasonal_content(&mut self, season: Season, settings: &Settings) {
if settings.original_textures {
self.paths.insert(0, "/base/ogph/".to_string())
} else if settings.seasonal_textures {
match season {
Season::Halloween => self.paths.insert(0, "/Halloween/season/".to_string()),
Season::Christmas => self.paths.insert(0, "/Christmas/season/".to_string()),
_ => {}
}
}
pub fn unload_all(&mut self) {
self.tex_map.clear();
}
fn make_transparent(rgba: &mut RgbaImage) {
@ -442,10 +429,10 @@ impl TextureSet {
}
}
fn load_image(&self, ctx: &mut Context, path: &str) -> GameResult<Box<dyn BackendTexture>> {
fn load_image(&self, ctx: &mut Context, roots: &Vec<String>, path: &str) -> GameResult<Box<dyn BackendTexture>> {
let img = {
let mut buf = [0u8; 8];
let mut reader = filesystem::open(ctx, path)?;
let mut reader = filesystem::open_find(ctx, roots,path)?;
reader.read_exact(&mut buf)?;
reader.seek(SeekFrom::Start(0))?;
@ -461,10 +448,8 @@ impl TextureSet {
create_texture(ctx, width as u16, height as u16, &img)
}
pub fn find_texture(&self, ctx: &mut Context, name: &str) -> Option<String> {
self.paths.iter().find_map(|s| {
FILE_TYPES.iter().map(|ext| [s, name, ext].join("")).find(|path| filesystem::exists(ctx, path))
})
pub fn find_texture(&self, ctx: &mut Context, roots: &Vec<String>, name: &str) -> Option<String> {
FILE_TYPES.iter().map(|ext| [name, ext].join("")).find(|path| filesystem::exists_find(ctx, roots, path))
}
pub fn load_texture(
@ -474,10 +459,10 @@ impl TextureSet {
name: &str,
) -> GameResult<Box<dyn SpriteBatch>> {
let path = self
.find_texture(ctx, name)
.find_texture(ctx, &constants.base_paths, name)
.ok_or_else(|| GameError::ResourceLoadError(format!("Texture {} does not exist.", name)))?;
let glow_path = self.find_texture(ctx, &[name, ".glow"].join(""));
let glow_path = self.find_texture(ctx, &constants.base_paths, &[name, ".glow"].join(""));
info!("Loading texture: {} -> {}", name, path);
@ -500,9 +485,9 @@ impl TextureSet {
}
}
let main_batch = make_batch(name, constants, self.load_image(ctx, &path)?);
let main_batch = make_batch(name, constants, self.load_image(ctx, &constants.base_paths, &path)?);
let glow_batch = if let Some(glow_path) = glow_path {
self.load_image(ctx, &glow_path).ok().map(|b| make_batch(name, constants, b))
self.load_image(ctx, &constants.base_paths, &glow_path).ok().map(|b| make_batch(name, constants, b))
} else {
None
};