use path list for resource loading
This commit is contained in:
parent
3374f13c2b
commit
c127ee4bd4
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
58
src/stage.rs
58
src/stage.rs
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue