1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-11-30 16:18:00 +00:00

Add <SIL/<CIL

This commit is contained in:
Alula 2021-10-16 14:59:27 +02:00
parent 8b5d56fe27
commit bdc4e7d209
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
10 changed files with 151 additions and 28 deletions

View file

@ -1,8 +1,10 @@
use crate::common::Rect; use crate::common::{Color, Rect};
use crate::entity::GameEntity; use crate::entity::GameEntity;
use crate::frame::Frame; use crate::frame::Frame;
use crate::framework::context::Context; use crate::framework::context::Context;
use crate::framework::error::GameResult; use crate::framework::error::GameResult;
use crate::framework::graphics;
use crate::scripting::tsc::text_script::IllustrationState;
use crate::shared_game_state::SharedGameState; use crate::shared_game_state::SharedGameState;
pub struct Credits {} pub struct Credits {}
@ -11,14 +13,48 @@ impl Credits {
pub fn new() -> Credits { pub fn new() -> Credits {
Credits {} Credits {}
} }
pub fn draw_tick(&mut self, state: &mut SharedGameState) {
match state.textscript_vm.illustration_state {
IllustrationState::FadeIn(mut x) => {
x += 40.0 * state.frame_time as f32;
state.textscript_vm.illustration_state =
if x >= 0.0 { IllustrationState::Shown } else { IllustrationState::FadeIn(x) };
}
IllustrationState::FadeOut(mut x) => {
x -= 40.0 * state.frame_time as f32;
state.textscript_vm.illustration_state =
if x <= -160.0 { IllustrationState::Hidden } else { IllustrationState::FadeOut(x) };
}
_ => (),
}
}
} }
impl GameEntity<()> for Credits { impl GameEntity<()> for Credits {
fn tick(&mut self, state: &mut SharedGameState, custom: ()) -> GameResult { fn tick(&mut self, _state: &mut SharedGameState, _custom: ()) -> GameResult {
Ok(()) Ok(())
} }
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult { fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
if state.textscript_vm.illustration_state != IllustrationState::Hidden {
let x = match state.textscript_vm.illustration_state {
IllustrationState::FadeIn(x) | IllustrationState::FadeOut(x) => x,
_ => 0.0,
};
if let Some(tex) = &state.textscript_vm.current_illustration {
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, tex)?;
batch.add(x, 0.0);
batch.draw(ctx)?;
} else {
let rect = Rect::new_size((x * state.scale) as isize, 0, (160.0 * state.scale) as _, state.screen_size.1 as _);
graphics::draw_rect(ctx, rect, Color::from_rgb(0, 0, 32))?;
}
}
if state.creditscript_vm.lines.is_empty() { if state.creditscript_vm.lines.is_empty() {
return Ok(()); return Ok(());
} }

View file

@ -266,6 +266,7 @@ pub struct EngineConstants {
pub soundtracks: HashMap<String, String>, pub soundtracks: HashMap<String, String>,
pub music_table: Vec<String>, pub music_table: Vec<String>,
pub organya_paths: Vec<String>, pub organya_paths: Vec<String>,
pub credit_illustration_paths: Vec<String>,
} }
impl Clone for EngineConstants { impl Clone for EngineConstants {
@ -291,6 +292,7 @@ impl Clone for EngineConstants {
soundtracks: self.soundtracks.clone(), soundtracks: self.soundtracks.clone(),
music_table: self.music_table.clone(), music_table: self.music_table.clone(),
organya_paths: self.organya_paths.clone(), organya_paths: self.organya_paths.clone(),
credit_illustration_paths: self.credit_illustration_paths.clone(),
} }
} }
} }
@ -1287,6 +1289,27 @@ impl EngineConstants {
"Bullet" => (320, 176), "Bullet" => (320, 176),
"Caret" => (320, 240), "Caret" => (320, 240),
"casts" => (320, 240), "casts" => (320, 240),
"Credit01" => (160, 240),
"Credit01a" => (160, 240),
"Credit02" => (160, 240),
"Credit02a" => (160, 240),
"Credit03" => (160, 240),
"Credit03a" => (160, 240),
"Credit04" => (160, 240),
"Credit05" => (160, 240),
"Credit06" => (160, 240),
"Credit07" => (160, 240),
"Credit08" => (160, 240),
"Credit09" => (160, 240),
"Credit10" => (160, 240),
"Credit11" => (160, 240),
"Credit12" => (160, 240),
"Credit13" => (160, 240),
"Credit14" => (160, 240),
"Credit15" => (160, 240),
"Credit16" => (160, 240),
"Credit17" => (160, 240),
"Credit18" => (160, 240),
"Face" => (288, 240), "Face" => (288, 240),
"Face_0" => (288, 240), // nxengine "Face_0" => (288, 240), // nxengine
"Face_1" => (288, 240), // nxengine "Face_1" => (288, 240), // nxengine
@ -1487,6 +1510,11 @@ impl EngineConstants {
"/base/Org/".to_owned(), // CS+ "/base/Org/".to_owned(), // CS+
"/Resource/ORG/".to_owned(), // CSE2E "/Resource/ORG/".to_owned(), // CSE2E
], ],
credit_illustration_paths: vec![
"".to_owned(),
"Resource/BITMAP/".to_owned(), // CSE2E
"endpic/".to_owned(), // NXEngine
],
} }
} }

View file

@ -35,18 +35,23 @@ impl Frame {
} }
pub fn immediate_update(&mut self, state: &mut SharedGameState, stage: &Stage) { pub fn immediate_update(&mut self, state: &mut SharedGameState, stage: &Stage) {
let mut screen_width = state.canvas_size.0;
if state.constants.is_switch {
screen_width += 10.0; // hack for scrolling
}
let tile_size = state.tile_size.as_int(); let tile_size = state.tile_size.as_int();
if (stage.map.width as usize).saturating_sub(1) * (tile_size as usize) < state.canvas_size.0 as usize { if (stage.map.width as usize).saturating_sub(1) * (tile_size as usize) < screen_width as usize {
self.x = -(((state.canvas_size.0 as i32 - (stage.map.width as i32 - 1) * tile_size) * 0x200) / 2); self.x = -(((screen_width as i32 - (stage.map.width as i32 - 1) * tile_size) * 0x200) / 2);
} else { } else {
self.x = self.target_x - (state.canvas_size.0 as i32 * 0x200 / 2); self.x = self.target_x - (screen_width as i32 * 0x200 / 2);
if self.x < 0 { if self.x < 0 {
self.x = 0; self.x = 0;
} }
let max_x = (((stage.map.width as i32 - 1) * tile_size) - state.canvas_size.0 as i32) * 0x200; let max_x = (((stage.map.width as i32 - 1) * tile_size) - screen_width as i32) * 0x200;
if self.x > max_x { if self.x > max_x {
self.x = max_x; self.x = max_x;
} }
@ -72,18 +77,22 @@ impl Frame {
} }
pub fn update(&mut self, state: &mut SharedGameState, stage: &Stage) { pub fn update(&mut self, state: &mut SharedGameState, stage: &Stage) {
let mut screen_width = state.canvas_size.0;
if state.constants.is_switch {
screen_width += 10.0;
}
let tile_size = state.tile_size.as_int(); let tile_size = state.tile_size.as_int();
if (stage.map.width as usize).saturating_sub(1) * (tile_size as usize) < state.canvas_size.0 as usize { if (stage.map.width as usize).saturating_sub(1) * (tile_size as usize) < screen_width as usize {
self.x = -(((state.canvas_size.0 as i32 - (stage.map.width as i32 - 1) * tile_size) * 0x200) / 2); self.x = -(((screen_width as i32 - (stage.map.width as i32 - 1) * tile_size) * 0x200) / 2);
} else { } else {
self.x += (self.target_x - (state.canvas_size.0 as i32 * 0x200 / 2) - self.x) / self.wait; self.x += (self.target_x - (screen_width as i32 * 0x200 / 2) - self.x) / self.wait;
if self.x < 0 { if self.x < 0 {
self.x = 0; self.x = 0;
} }
let max_x = (((stage.map.width as i32 - 1) * tile_size) - state.canvas_size.0 as i32) * 0x200; let max_x = (((stage.map.width as i32 - 1) * tile_size) - screen_width as i32) * 0x200;
if self.x > max_x { if self.x > max_x {
self.x = max_x; self.x = max_x;
} }

View file

@ -75,7 +75,7 @@ pub trait BackendTexture {
} }
#[allow(unreachable_code)] #[allow(unreachable_code)]
pub fn init_backend(headless: bool) -> GameResult<Box<dyn Backend>> { pub fn init_backend(headless: bool, size_hint: (u16, u16)) -> GameResult<Box<dyn Backend>> {
if headless { if headless {
return crate::framework::backend_null::NullBackend::new(); return crate::framework::backend_null::NullBackend::new();
} }
@ -87,7 +87,7 @@ pub fn init_backend(headless: bool) -> GameResult<Box<dyn Backend>> {
#[cfg(feature = "backend-sdl")] #[cfg(feature = "backend-sdl")]
{ {
return crate::framework::backend_sdl2::SDL2Backend::new(); return crate::framework::backend_sdl2::SDL2Backend::new(size_hint);
} }
log::warn!("No backend compiled in, using null backend instead."); log::warn!("No backend compiled in, using null backend instead.");

View file

@ -31,13 +31,14 @@ use crate::GAME_SUSPENDED;
pub struct SDL2Backend { pub struct SDL2Backend {
context: Sdl, context: Sdl,
size_hint: (u16, u16),
} }
impl SDL2Backend { impl SDL2Backend {
pub fn new() -> GameResult<Box<dyn Backend>> { pub fn new(size_hint: (u16, u16)) -> GameResult<Box<dyn Backend>> {
let context = sdl2::init().map_err(|e| GameError::WindowError(e))?; let context = sdl2::init().map_err(|e| GameError::WindowError(e))?;
let backend = SDL2Backend { context }; let backend = SDL2Backend { context, size_hint };
Ok(Box::new(backend)) Ok(Box::new(backend))
} }
@ -45,7 +46,7 @@ impl SDL2Backend {
impl Backend for SDL2Backend { impl Backend for SDL2Backend {
fn create_event_loop(&self) -> GameResult<Box<dyn BackendEventLoop>> { fn create_event_loop(&self) -> GameResult<Box<dyn BackendEventLoop>> {
SDL2EventLoop::new(&self.context) SDL2EventLoop::new(&self.context, self.size_hint)
} }
} }
@ -64,7 +65,7 @@ struct SDL2Context {
} }
impl SDL2EventLoop { impl SDL2EventLoop {
pub fn new(sdl: &Sdl) -> GameResult<Box<dyn BackendEventLoop>> { pub fn new(sdl: &Sdl, size_hint: (u16, u16)) -> GameResult<Box<dyn BackendEventLoop>> {
let event_pump = sdl.event_pump().map_err(|e| GameError::WindowError(e))?; let event_pump = sdl.event_pump().map_err(|e| GameError::WindowError(e))?;
let video = sdl.video().map_err(|e| GameError::WindowError(e))?; let video = sdl.video().map_err(|e| GameError::WindowError(e))?;
let gl_attr = video.gl_attr(); let gl_attr = video.gl_attr();
@ -72,7 +73,7 @@ impl SDL2EventLoop {
gl_attr.set_context_profile(GLProfile::Core); gl_attr.set_context_profile(GLProfile::Core);
gl_attr.set_context_version(3, 0); gl_attr.set_context_version(3, 0);
let mut window = video.window("Cave Story (doukutsu-rs)", 640, 480); let mut window = video.window("Cave Story (doukutsu-rs)", size_hint.0 as _, size_hint.1 as _);
window.position_centered(); window.position_centered();
window.resizable(); window.resizable();

View file

@ -6,6 +6,7 @@ use crate::Game;
pub struct Context { pub struct Context {
pub headless: bool, pub headless: bool,
pub size_hint: (u16, u16),
pub(crate) filesystem: Filesystem, pub(crate) filesystem: Filesystem,
pub(crate) renderer: Option<Box<dyn BackendRenderer>>, pub(crate) renderer: Option<Box<dyn BackendRenderer>>,
pub(crate) keyboard_context: KeyboardContext, pub(crate) keyboard_context: KeyboardContext,
@ -18,6 +19,7 @@ impl Context {
pub fn new() -> Context { pub fn new() -> Context {
Context { Context {
headless: false, headless: false,
size_hint: (640, 480),
filesystem: Filesystem::new(), filesystem: Filesystem::new(),
renderer: None, renderer: None,
keyboard_context: KeyboardContext::new(), keyboard_context: KeyboardContext::new(),
@ -28,7 +30,7 @@ impl Context {
} }
pub fn run(&mut self, game: &mut Game) -> GameResult { pub fn run(&mut self, game: &mut Game) -> GameResult {
let backend = init_backend(self.headless)?; let backend = init_backend(self.headless, self.size_hint)?;
let mut event_loop = backend.create_event_loop()?; let mut event_loop = backend.create_event_loop()?;
self.renderer = Some(event_loop.new_renderer()?); self.renderer = Some(event_loop.new_renderer()?);

View file

@ -2018,6 +2018,7 @@ impl Scene for GameScene {
}; };
self.inventory_dim = self.inventory_dim.clamp(0.0, 1.0); self.inventory_dim = self.inventory_dim.clamp(0.0, 1.0);
self.credits.draw_tick(state);
Ok(()) Ok(())
} }

View file

@ -97,6 +97,14 @@ pub enum TextScriptExecutionState {
Reset, Reset,
} }
#[derive(PartialEq, Copy, Clone)]
pub enum IllustrationState {
Hidden,
Shown,
FadeIn(f32),
FadeOut(f32),
}
pub struct TextScriptVM { pub struct TextScriptVM {
pub scripts: Rc<RefCell<Scripts>>, pub scripts: Rc<RefCell<Scripts>>,
pub state: TextScriptExecutionState, pub state: TextScriptExecutionState,
@ -117,6 +125,8 @@ pub struct TextScriptVM {
pub line_1: Vec<char>, pub line_1: Vec<char>,
pub line_2: Vec<char>, pub line_2: Vec<char>,
pub line_3: Vec<char>, pub line_3: Vec<char>,
pub current_illustration: Option<String>,
pub illustration_state: IllustrationState,
prev_char: char, prev_char: char,
} }
@ -180,6 +190,8 @@ impl TextScriptVM {
line_1: Vec::with_capacity(24), line_1: Vec::with_capacity(24),
line_2: Vec::with_capacity(24), line_2: Vec::with_capacity(24),
line_3: Vec::with_capacity(24), line_3: Vec::with_capacity(24),
current_illustration: None,
illustration_state: IllustrationState::Hidden,
prev_char: '\x00', prev_char: '\x00',
} }
} }
@ -219,6 +231,8 @@ impl TextScriptVM {
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.state = TextScriptExecutionState::Ended; self.state = TextScriptExecutionState::Ended;
self.flags.0 = 0; self.flags.0 = 0;
self.current_illustration = None;
self.illustration_state = IllustrationState::Hidden;
self.clear_text_box(); self.clear_text_box();
} }
@ -1520,10 +1534,33 @@ impl TextScriptVM {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
} }
TSCOpCode::SIL => {
let number = read_cur_varint(&mut cursor)? as u16;
for path in state.constants.credit_illustration_paths.iter() {
let path = format!("{}Credit{:02}", path, number);
if let Some(_) = state.texture_set.find_texture(ctx, &path) {
state.textscript_vm.current_illustration = Some(path);
break;
}
}
state.textscript_vm.illustration_state = IllustrationState::FadeIn(-160.0);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::CIL => {
state.textscript_vm.illustration_state = if let Some(_) = state.textscript_vm.current_illustration {
IllustrationState::FadeOut(0.0)
} else {
IllustrationState::Hidden
};
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
// unimplemented opcodes // unimplemented opcodes
// Zero operands // Zero operands
TSCOpCode::CIL TSCOpCode::CPS
| TSCOpCode::CPS
| TSCOpCode::KE2 | TSCOpCode::KE2
| TSCOpCode::CSS | TSCOpCode::CSS
| TSCOpCode::MLP | TSCOpCode::MLP
@ -1536,7 +1573,7 @@ impl TextScriptVM {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
} }
// One operand codes // One operand codes
TSCOpCode::UNJ | TSCOpCode::XX1 | TSCOpCode::SIL | TSCOpCode::SSS | TSCOpCode::ACH => { TSCOpCode::UNJ | TSCOpCode::XX1 | TSCOpCode::SSS | TSCOpCode::ACH => {
let par_a = read_cur_varint(&mut cursor)?; let par_a = read_cur_varint(&mut cursor)?;
log::warn!("unimplemented opcode: {:?} {}", op, par_a); log::warn!("unimplemented opcode: {:?} {}", op, par_a);

View file

@ -164,6 +164,7 @@ impl SharedGameState {
base_path = "/base/"; base_path = "/base/";
} else if filesystem::exists(ctx, "/base/lighting.tbl") { } else if filesystem::exists(ctx, "/base/lighting.tbl") {
info!("Cave Story+ (Switch) data files detected."); info!("Cave Story+ (Switch) data files detected.");
ctx.size_hint = (854, 480);
constants.apply_csplus_patches(&sound_manager); constants.apply_csplus_patches(&sound_manager);
constants.apply_csplus_nx_patches(); constants.apply_csplus_nx_patches();
base_path = "/base/"; base_path = "/base/";

View file

@ -354,18 +354,26 @@ impl TextureSet {
create_texture(ctx, width as u16, height as u16, &img) 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 load_texture( pub fn load_texture(
&self, &self,
ctx: &mut Context, ctx: &mut Context,
constants: &EngineConstants, constants: &EngineConstants,
name: &str, name: &str,
) -> GameResult<Box<dyn SpriteBatch>> { ) -> GameResult<Box<dyn SpriteBatch>> {
let path = self let path = self.find_texture(ctx, name)
.paths
.iter()
.find_map(|s| {
FILE_TYPES.iter().map(|ext| [s, name, ext].join("")).find(|path| filesystem::exists(ctx, path))
})
.ok_or_else(|| GameError::ResourceLoadError(format!("Texture {} does not exist.", name)))?; .ok_or_else(|| GameError::ResourceLoadError(format!("Texture {} does not exist.", name)))?;
let has_glow_layer = self let has_glow_layer = self
@ -376,7 +384,7 @@ impl TextureSet {
}) })
.is_some(); .is_some();
info!("Loading texture: {}", path); info!("Loading texture: {} -> {}", name, path);
let batch = self.load_image(ctx, &path)?; let batch = self.load_image(ctx, &path)?;
let size = batch.dimensions(); let size = batch.dimensions();