mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-11-29 15:56:53 +00:00
Add <SIL/<CIL
This commit is contained in:
parent
8b5d56fe27
commit
bdc4e7d209
|
|
@ -1,8 +1,10 @@
|
|||
use crate::common::Rect;
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::scripting::tsc::text_script::IllustrationState;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
pub struct Credits {}
|
||||
|
|
@ -11,14 +13,48 @@ impl Credits {
|
|||
pub fn new() -> 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 {
|
||||
fn tick(&mut self, state: &mut SharedGameState, custom: ()) -> GameResult {
|
||||
fn tick(&mut self, _state: &mut SharedGameState, _custom: ()) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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() {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,6 +266,7 @@ pub struct EngineConstants {
|
|||
pub soundtracks: HashMap<String, String>,
|
||||
pub music_table: Vec<String>,
|
||||
pub organya_paths: Vec<String>,
|
||||
pub credit_illustration_paths: Vec<String>,
|
||||
}
|
||||
|
||||
impl Clone for EngineConstants {
|
||||
|
|
@ -291,6 +292,7 @@ impl Clone for EngineConstants {
|
|||
soundtracks: self.soundtracks.clone(),
|
||||
music_table: self.music_table.clone(),
|
||||
organya_paths: self.organya_paths.clone(),
|
||||
credit_illustration_paths: self.credit_illustration_paths.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1287,6 +1289,27 @@ impl EngineConstants {
|
|||
"Bullet" => (320, 176),
|
||||
"Caret" => (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_0" => (288, 240), // nxengine
|
||||
"Face_1" => (288, 240), // nxengine
|
||||
|
|
@ -1487,6 +1510,11 @@ impl EngineConstants {
|
|||
"/base/Org/".to_owned(), // CS+
|
||||
"/Resource/ORG/".to_owned(), // CSE2E
|
||||
],
|
||||
credit_illustration_paths: vec![
|
||||
"".to_owned(),
|
||||
"Resource/BITMAP/".to_owned(), // CSE2E
|
||||
"endpic/".to_owned(), // NXEngine
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
25
src/frame.rs
25
src/frame.rs
|
|
@ -35,18 +35,23 @@ impl Frame {
|
|||
}
|
||||
|
||||
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();
|
||||
|
||||
if (stage.map.width as usize).saturating_sub(1) * (tile_size as usize) < state.canvas_size.0 as usize {
|
||||
self.x = -(((state.canvas_size.0 as i32 - (stage.map.width as i32 - 1) * tile_size) * 0x200) / 2);
|
||||
if (stage.map.width as usize).saturating_sub(1) * (tile_size as usize) < screen_width as usize {
|
||||
self.x = -(((screen_width as i32 - (stage.map.width as i32 - 1) * tile_size) * 0x200) / 2);
|
||||
} 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 {
|
||||
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 {
|
||||
self.x = max_x;
|
||||
}
|
||||
|
|
@ -72,18 +77,22 @@ impl Frame {
|
|||
}
|
||||
|
||||
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();
|
||||
|
||||
if (stage.map.width as usize).saturating_sub(1) * (tile_size as usize) < state.canvas_size.0 as usize {
|
||||
self.x = -(((state.canvas_size.0 as i32 - (stage.map.width as i32 - 1) * tile_size) * 0x200) / 2);
|
||||
if (stage.map.width as usize).saturating_sub(1) * (tile_size as usize) < screen_width as usize {
|
||||
self.x = -(((screen_width as i32 - (stage.map.width as i32 - 1) * tile_size) * 0x200) / 2);
|
||||
} 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 {
|
||||
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 {
|
||||
self.x = max_x;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ pub trait BackendTexture {
|
|||
}
|
||||
|
||||
#[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 {
|
||||
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")]
|
||||
{
|
||||
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.");
|
||||
|
|
|
|||
|
|
@ -31,13 +31,14 @@ use crate::GAME_SUSPENDED;
|
|||
|
||||
pub struct SDL2Backend {
|
||||
context: Sdl,
|
||||
size_hint: (u16, u16),
|
||||
}
|
||||
|
||||
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 backend = SDL2Backend { context };
|
||||
let backend = SDL2Backend { context, size_hint };
|
||||
|
||||
Ok(Box::new(backend))
|
||||
}
|
||||
|
|
@ -45,7 +46,7 @@ impl SDL2Backend {
|
|||
|
||||
impl Backend for SDL2Backend {
|
||||
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 {
|
||||
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 video = sdl.video().map_err(|e| GameError::WindowError(e))?;
|
||||
let gl_attr = video.gl_attr();
|
||||
|
|
@ -72,7 +73,7 @@ impl SDL2EventLoop {
|
|||
gl_attr.set_context_profile(GLProfile::Core);
|
||||
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.resizable();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::Game;
|
|||
|
||||
pub struct Context {
|
||||
pub headless: bool,
|
||||
pub size_hint: (u16, u16),
|
||||
pub(crate) filesystem: Filesystem,
|
||||
pub(crate) renderer: Option<Box<dyn BackendRenderer>>,
|
||||
pub(crate) keyboard_context: KeyboardContext,
|
||||
|
|
@ -18,6 +19,7 @@ impl Context {
|
|||
pub fn new() -> Context {
|
||||
Context {
|
||||
headless: false,
|
||||
size_hint: (640, 480),
|
||||
filesystem: Filesystem::new(),
|
||||
renderer: None,
|
||||
keyboard_context: KeyboardContext::new(),
|
||||
|
|
@ -28,7 +30,7 @@ impl Context {
|
|||
}
|
||||
|
||||
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()?;
|
||||
self.renderer = Some(event_loop.new_renderer()?);
|
||||
|
||||
|
|
|
|||
|
|
@ -2018,6 +2018,7 @@ impl Scene for GameScene {
|
|||
};
|
||||
|
||||
self.inventory_dim = self.inventory_dim.clamp(0.0, 1.0);
|
||||
self.credits.draw_tick(state);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,14 @@ pub enum TextScriptExecutionState {
|
|||
Reset,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
pub enum IllustrationState {
|
||||
Hidden,
|
||||
Shown,
|
||||
FadeIn(f32),
|
||||
FadeOut(f32),
|
||||
}
|
||||
|
||||
pub struct TextScriptVM {
|
||||
pub scripts: Rc<RefCell<Scripts>>,
|
||||
pub state: TextScriptExecutionState,
|
||||
|
|
@ -117,6 +125,8 @@ pub struct TextScriptVM {
|
|||
pub line_1: Vec<char>,
|
||||
pub line_2: Vec<char>,
|
||||
pub line_3: Vec<char>,
|
||||
pub current_illustration: Option<String>,
|
||||
pub illustration_state: IllustrationState,
|
||||
prev_char: char,
|
||||
}
|
||||
|
||||
|
|
@ -180,6 +190,8 @@ impl TextScriptVM {
|
|||
line_1: Vec::with_capacity(24),
|
||||
line_2: Vec::with_capacity(24),
|
||||
line_3: Vec::with_capacity(24),
|
||||
current_illustration: None,
|
||||
illustration_state: IllustrationState::Hidden,
|
||||
prev_char: '\x00',
|
||||
}
|
||||
}
|
||||
|
|
@ -219,6 +231,8 @@ impl TextScriptVM {
|
|||
pub fn reset(&mut self) {
|
||||
self.state = TextScriptExecutionState::Ended;
|
||||
self.flags.0 = 0;
|
||||
self.current_illustration = None;
|
||||
self.illustration_state = IllustrationState::Hidden;
|
||||
self.clear_text_box();
|
||||
}
|
||||
|
||||
|
|
@ -1520,10 +1534,33 @@ impl TextScriptVM {
|
|||
|
||||
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
|
||||
// Zero operands
|
||||
TSCOpCode::CIL
|
||||
| TSCOpCode::CPS
|
||||
TSCOpCode::CPS
|
||||
| TSCOpCode::KE2
|
||||
| TSCOpCode::CSS
|
||||
| TSCOpCode::MLP
|
||||
|
|
@ -1536,7 +1573,7 @@ impl TextScriptVM {
|
|||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
// 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)?;
|
||||
|
||||
log::warn!("unimplemented opcode: {:?} {}", op, par_a);
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ impl SharedGameState {
|
|||
base_path = "/base/";
|
||||
} else if filesystem::exists(ctx, "/base/lighting.tbl") {
|
||||
info!("Cave Story+ (Switch) data files detected.");
|
||||
ctx.size_hint = (854, 480);
|
||||
constants.apply_csplus_patches(&sound_manager);
|
||||
constants.apply_csplus_nx_patches();
|
||||
base_path = "/base/";
|
||||
|
|
|
|||
|
|
@ -354,18 +354,26 @@ 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 load_texture(
|
||||
&self,
|
||||
ctx: &mut Context,
|
||||
constants: &EngineConstants,
|
||||
name: &str,
|
||||
) -> GameResult<Box<dyn SpriteBatch>> {
|
||||
let path = self
|
||||
.paths
|
||||
.iter()
|
||||
.find_map(|s| {
|
||||
FILE_TYPES.iter().map(|ext| [s, name, ext].join("")).find(|path| filesystem::exists(ctx, path))
|
||||
})
|
||||
let path = self.find_texture(ctx, name)
|
||||
.ok_or_else(|| GameError::ResourceLoadError(format!("Texture {} does not exist.", name)))?;
|
||||
|
||||
let has_glow_layer = self
|
||||
|
|
@ -376,7 +384,7 @@ impl TextureSet {
|
|||
})
|
||||
.is_some();
|
||||
|
||||
info!("Loading texture: {}", path);
|
||||
info!("Loading texture: {} -> {}", name, path);
|
||||
|
||||
let batch = self.load_image(ctx, &path)?;
|
||||
let size = batch.dimensions();
|
||||
|
|
|
|||
Loading…
Reference in a new issue