1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-12-01 08:37:23 +00:00

rework cutscene skipping

This commit is contained in:
Alula 2021-05-02 06:06:51 +02:00
parent ad6a330ae0
commit ecabff27a8
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
3 changed files with 165 additions and 76 deletions

View file

@ -77,6 +77,19 @@ impl BMFontRenderer {
self.draw_colored_text(iter, x, y, (255, 255, 255, 255), constants, texture_set, ctx) self.draw_colored_text(iter, x, y, (255, 255, 255, 255), constants, texture_set, ctx)
} }
pub fn draw_text_with_shadow<I: Iterator<Item = char> + Clone>(
&self,
iter: I,
x: f32,
y: f32,
constants: &EngineConstants,
texture_set: &mut TextureSet,
ctx: &mut Context,
) -> GameResult {
self.draw_colored_text(iter.clone(), x + 1.0, y + 1.0, (0, 0, 0, 150), constants, texture_set, ctx)?;
self.draw_colored_text(iter, x, y, (255, 255, 255, 255), constants, texture_set, ctx)
}
pub fn draw_colored_text_scaled<I: Iterator<Item = char>>( pub fn draw_colored_text_scaled<I: Iterator<Item = char>>(
&self, &self,
iter: I, iter: I,

View file

@ -20,9 +20,9 @@ use crate::framework::graphics::{BlendMode, draw_rect, FilterMode};
use crate::framework::ui::Components; use crate::framework::ui::Components;
use crate::input::touch_controls::TouchControlType; use crate::input::touch_controls::TouchControlType;
use crate::inventory::{Inventory, TakeExperienceResult}; use crate::inventory::{Inventory, TakeExperienceResult};
use crate::npc::{NPC, NPCLayer};
use crate::npc::boss::BossNPC; use crate::npc::boss::BossNPC;
use crate::npc::list::NPCList; use crate::npc::list::NPCList;
use crate::npc::{NPC, NPCLayer};
use crate::physics::PhysicalEntity; use crate::physics::PhysicalEntity;
use crate::player::{Player, TargetPlayer}; use crate::player::{Player, TargetPlayer};
use crate::rng::XorShift; use crate::rng::XorShift;
@ -57,6 +57,7 @@ pub struct GameScene {
tex_background_name: String, tex_background_name: String,
tex_tileset_name: String, tex_tileset_name: String,
map_name_counter: u16, map_name_counter: u16,
skip_counter: u16,
} }
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)] #[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
@ -71,6 +72,7 @@ const FACE_TEX: &str = "Face";
const SWITCH_FACE_TEX: [&str; 4] = ["Face1", "Face2", "Face3", "Face4"]; const SWITCH_FACE_TEX: [&str; 4] = ["Face1", "Face2", "Face3", "Face4"];
const P2_LEFT_TEXT: &str = "< P2"; const P2_LEFT_TEXT: &str = "< P2";
const P2_RIGHT_TEXT: &str = "P2 >"; const P2_RIGHT_TEXT: &str = "P2 >";
const CUTSCENE_SKIP_WAIT: u16 = 50;
impl GameScene { impl GameScene {
pub fn new(state: &mut SharedGameState, ctx: &mut Context, id: usize) -> GameResult<Self> { pub fn new(state: &mut SharedGameState, ctx: &mut Context, id: usize) -> GameResult<Self> {
@ -112,6 +114,7 @@ impl GameScene {
tex_background_name, tex_background_name,
tex_tileset_name, tex_tileset_name,
map_name_counter: 0, map_name_counter: 0,
skip_counter: 0,
}) })
} }
@ -212,18 +215,19 @@ impl GameScene {
Ok(()) Ok(())
} }
fn draw_npc_layer(&self, state: &mut SharedGameState, ctx: &mut Context, layer: NPCLayer) -> GameResult { fn draw_npc_layer(&self, state: &mut SharedGameState, ctx: &mut Context, layer: NPCLayer) -> GameResult {
for npc in self.npc_list.iter_alive() { for npc in self.npc_list.iter_alive() {
if npc.layer != layer || npc.x < (self.frame.x - 128 * 0x200 - npc.display_bounds.width() as i32 * 0x200) if npc.layer != layer
|| npc.x < (self.frame.x - 128 * 0x200 - npc.display_bounds.width() as i32 * 0x200)
|| npc.x || npc.x
> (self.frame.x > (self.frame.x
+ 128 * 0x200 + 128 * 0x200
+ (state.canvas_size.0 as i32 + npc.display_bounds.width() as i32) * 0x200) + (state.canvas_size.0 as i32 + npc.display_bounds.width() as i32) * 0x200)
&& npc.y < (self.frame.y - 128 * 0x200 - npc.display_bounds.height() as i32 * 0x200) && npc.y < (self.frame.y - 128 * 0x200 - npc.display_bounds.height() as i32 * 0x200)
|| npc.y || npc.y
> (self.frame.y > (self.frame.y
+ 128 * 0x200 + 128 * 0x200
+ (state.canvas_size.1 as i32 + npc.display_bounds.height() as i32) * 0x200) + (state.canvas_size.1 as i32 + npc.display_bounds.height() as i32) * 0x200)
{ {
continue; continue;
} }
@ -412,10 +416,15 @@ impl GameScene {
return Ok(()); return Ok(());
} }
let (off_left, off_top, off_right, off_bottom) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale); let (off_left, off_top, off_right, off_bottom) =
crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
let center = ((state.canvas_size.0 - off_left - off_right) / 2.0).floor(); let center = ((state.canvas_size.0 - off_left - off_right) / 2.0).floor();
let top_pos = if state.textscript_vm.flags.position_top() { 32.0 + off_top } else { state.canvas_size.1 as f32 - off_bottom - 66.0 }; let top_pos = if state.textscript_vm.flags.position_top() {
32.0 + off_top
} else {
state.canvas_size.1 as f32 - off_bottom - 66.0
};
let left_pos = off_left + center - 122.0; let left_pos = off_left + center - 122.0;
{ {
@ -446,7 +455,7 @@ impl GameScene {
); );
batch.add_rect( batch.add_rect(
center + 32.0, center + 32.0,
state.canvas_size.1 - off_bottom- 104.0, state.canvas_size.1 - off_bottom - 104.0,
&state.constants.textscript.get_item_right, &state.constants.textscript.get_item_right,
); );
batch.add_rect( batch.add_rect(
@ -468,11 +477,7 @@ impl GameScene {
state.canvas_size.1 - off_bottom - 96.0 state.canvas_size.1 - off_bottom - 96.0
}; };
batch.add_rect( batch.add_rect(center + 56.0, pos_y, &state.constants.textscript.textbox_rect_yes_no);
center + 56.0,
pos_y,
&state.constants.textscript.textbox_rect_yes_no,
);
if wait == 0 { if wait == 0 {
let pos_x = if selection == ConfirmSelection::No { 41.0 } else { 0.0 }; let pos_x = if selection == ConfirmSelection::No { 41.0 } else { 0.0 };
@ -617,7 +622,15 @@ impl GameScene {
) )
} }
fn draw_light_raycast(&self, world_point_x: i32, world_point_y: i32, (br, bg, bb): (u8, u8, u8), att: u8, angle: Range<i32>, batch: &mut SizedBatch) { fn draw_light_raycast(
&self,
world_point_x: i32,
world_point_y: i32,
(br, bg, bb): (u8, u8, u8),
att: u8,
angle: Range<i32>,
batch: &mut SizedBatch,
) {
let px = world_point_x as f32 / 512.0; let px = world_point_x as f32 / 512.0;
let py = world_point_y as f32 / 512.0; let py = world_point_y as f32 / 512.0;
@ -649,43 +662,43 @@ impl GameScene {
&& x >= (bx * 16 - 8) as f32 && x >= (bx * 16 - 8) as f32
&& x <= (bx * 16 + 8) as f32 && x <= (bx * 16 + 8) as f32
&& y >= (by * 16 - 8) as f32 && y >= (by * 16 - 8) as f32
&& y <= (by * 16 + 8) as f32) || && y <= (by * 16 + 8) as f32)
((tile == 0x50 || tile == 0x70) || ((tile == 0x50 || tile == 0x70)
&& x >= (bx * 16 - 8) as f32 && x >= (bx * 16 - 8) as f32
&& x <= (bx * 16 + 8) as f32 && x <= (bx * 16 + 8) as f32
&& y <= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 + 4.0) && y <= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 + 4.0)
&& y >= (by * 16 - 8) as f32) || && y >= (by * 16 - 8) as f32)
((tile == 0x51 || tile == 0x71) || ((tile == 0x51 || tile == 0x71)
&& x >= (bx * 16 - 8) as f32 && x >= (bx * 16 - 8) as f32
&& x <= (bx * 16 + 8) as f32 && x <= (bx * 16 + 8) as f32
&& y <= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 - 4.0) && y <= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 - 4.0)
&& y >= (by * 16 - 8) as f32) || && y >= (by * 16 - 8) as f32)
((tile == 0x52 || tile == 0x72) || ((tile == 0x52 || tile == 0x72)
&& x >= (bx * 16 - 8) as f32 && x >= (bx * 16 - 8) as f32
&& x <= (bx * 16 + 8) as f32 && x <= (bx * 16 + 8) as f32
&& y <= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 - 4.0) && y <= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 - 4.0)
&& y >= (by * 16 - 8) as f32) || && y >= (by * 16 - 8) as f32)
((tile == 0x53 || tile == 0x73) || ((tile == 0x53 || tile == 0x73)
&& x >= (bx * 16 - 8) as f32 && x >= (bx * 16 - 8) as f32
&& x <= (bx * 16 + 8) as f32 && x <= (bx * 16 + 8) as f32
&& y <= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 + 4.0) && y <= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 + 4.0)
&& y >= (by * 16 - 8) as f32) || && y >= (by * 16 - 8) as f32)
((tile == 0x54 || tile == 0x74) || ((tile == 0x54 || tile == 0x74)
&& x >= (bx * 16 - 8) as f32 && x >= (bx * 16 - 8) as f32
&& x <= (bx * 16 + 8) as f32 && x <= (bx * 16 + 8) as f32
&& y >= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 - 4.0) && y >= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 - 4.0)
&& y <= (by * 16 + 8) as f32) || && y <= (by * 16 + 8) as f32)
((tile == 0x55 || tile == 0x75) || ((tile == 0x55 || tile == 0x75)
&& x >= (bx * 16 - 8) as f32 && x >= (bx * 16 - 8) as f32
&& x <= (bx * 16 + 8) as f32 && x <= (bx * 16 + 8) as f32
&& y >= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 + 4.0) && y >= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 + 4.0)
&& y <= (by * 16 + 8) as f32) || && y <= (by * 16 + 8) as f32)
((tile == 0x56 || tile == 0x76) || ((tile == 0x56 || tile == 0x76)
&& x >= (bx * 16 - 8) as f32 && x >= (bx * 16 - 8) as f32
&& x <= (bx * 16 + 8) as f32 && x <= (bx * 16 + 8) as f32
&& y >= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 + 4.0) && y >= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 + 4.0)
&& y <= (by * 16 + 8) as f32) || && y <= (by * 16 + 8) as f32)
((tile == 0x57 || tile == 0x77) || ((tile == 0x57 || tile == 0x77)
&& x >= (bx * 16 - 8) as f32 && x >= (bx * 16 - 8) as f32
&& x <= (bx * 16 + 8) as f32 && x <= (bx * 16 + 8) as f32
&& y >= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 - 4.0) && y >= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 - 4.0)
@ -724,7 +737,9 @@ impl GameScene {
let scale = state.scale; let scale = state.scale;
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "builtin/lightmap/spot")?; let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "builtin/lightmap/spot")?;
for (player, inv) in [(&self.player1, &self.inventory_player1), (&self.player2, &self.inventory_player2)].iter() { for (player, inv) in
[(&self.player1, &self.inventory_player1), (&self.player2, &self.inventory_player2)].iter()
{
if player.cond.alive() && !player.cond.hidden() && inv.get_current_weapon().is_some() { if player.cond.alive() && !player.cond.hidden() && inv.get_current_weapon().is_some() {
let range = match () { let range = match () {
_ if player.up => 60..120, _ if player.up => 60..120,
@ -1496,42 +1511,73 @@ impl Scene for GameScene {
} }
} }
match state.textscript_vm.mode { match state.textscript_vm.state {
ScriptMode::Map if state.control_flags.tick_world() => self.tick_world(state)?, TextScriptExecutionState::Running(_, _)
ScriptMode::StageSelect => self.stage_select.tick(state, (ctx, &self.player1, &self.player2))?, | TextScriptExecutionState::WaitTicks(_, _, _)
ScriptMode::Inventory => self.inventory_ui.tick(state, (ctx, &mut self.player1, &mut self.inventory_player1))?, | TextScriptExecutionState::WaitInput(_, _, _)
_ => {} | TextScriptExecutionState::Msg(_, _, _, _)
if !state.control_flags.control_enabled() && !state.textscript_vm.flags.cutscene_skip() =>
{
if self.player1.controller.inventory() {
self.skip_counter += 1;
if self.skip_counter >= CUTSCENE_SKIP_WAIT {
state.textscript_vm.flags.set_cutscene_skip(true);
}
} else if self.skip_counter > 0 {
self.skip_counter -= 1;
}
}
_ => {
self.skip_counter = 0;
}
} }
if self.map_name_counter > 0 { let mut ticks = 1;
self.map_name_counter -= 1; if state.textscript_vm.mode == ScriptMode::Map && state.textscript_vm.flags.cutscene_skip() {
ticks = 4;
} }
match state.fade_state { for _ in 0..ticks {
FadeState::FadeOut(tick, direction) if tick < 15 => { match state.textscript_vm.mode {
state.fade_state = FadeState::FadeOut(tick + 1, direction); ScriptMode::Map if state.control_flags.tick_world() => self.tick_world(state)?,
ScriptMode::StageSelect => self.stage_select.tick(state, (ctx, &self.player1, &self.player2))?,
ScriptMode::Inventory => {
self.inventory_ui.tick(state, (ctx, &mut self.player1, &mut self.inventory_player1))?
}
_ => {}
} }
FadeState::FadeOut(tick, _) if tick == 15 => {
state.fade_state = FadeState::Hidden; if self.map_name_counter > 0 {
self.map_name_counter -= 1;
} }
FadeState::FadeIn(tick, direction) if tick > -15 => {
state.fade_state = FadeState::FadeIn(tick - 1, direction); match state.fade_state {
FadeState::FadeOut(tick, direction) if tick < 15 => {
state.fade_state = FadeState::FadeOut(tick + 1, direction);
}
FadeState::FadeOut(tick, _) if tick == 15 => {
state.fade_state = FadeState::Hidden;
}
FadeState::FadeIn(tick, direction) if tick > -15 => {
state.fade_state = FadeState::FadeIn(tick - 1, direction);
}
FadeState::FadeIn(tick, _) if tick == -15 => {
state.fade_state = FadeState::Visible;
}
_ => {}
} }
FadeState::FadeIn(tick, _) if tick == -15 => {
state.fade_state = FadeState::Visible; self.flash.tick(state, ())?;
TextScriptVM::run(state, self, ctx)?;
#[cfg(feature = "scripting")]
state.lua.scene_tick(self);
if state.control_flags.control_enabled() {
self.tick = self.tick.wrapping_add(1);
} }
_ => {}
} }
self.flash.tick(state, ())?;
TextScriptVM::run(state, self, ctx)?;
#[cfg(feature = "scripting")]
state.lua.scene_tick(self);
if state.control_flags.control_enabled() {
self.tick = self.tick.wrapping_add(1);
}
Ok(()) Ok(())
} }
@ -1692,6 +1738,36 @@ impl Scene for GameScene {
} }
self.draw_text_boxes(state, ctx)?; self.draw_text_boxes(state, ctx)?;
if self.skip_counter > 0 {
let text = format!("Hold {:?} to skip the cutscene", state.settings.player1_key_map.inventory);
let width = state.font.text_width(text.chars(), &state.constants);
let pos_x = state.canvas_size.0 - width - 20.0;
let pos_y = 0.0;
let line_height = state.font.line_height(&state.constants);
let w = (self.skip_counter as f32 / CUTSCENE_SKIP_WAIT as f32) * (width + 20.0) / 2.0;
let mut rect = Rect::new_size((pos_x * state.scale) as isize,
(pos_y * state.scale) as isize,
((20.0 + width) * state.scale) as isize,
((20.0 + line_height) * state.scale) as isize);
draw_rect(ctx, rect, Color::from_rgb(0, 0, 32))?;
rect.right = rect.left + (w * state.scale) as isize;
draw_rect(ctx, rect, Color::from_rgb(160, 181, 222))?;
rect.left = ((state.canvas_size.0 - w) * state.scale) as isize;
rect.right = rect.left + (w * state.scale) as isize;
draw_rect(ctx, rect, Color::from_rgb(160, 181, 222))?;
state.font.draw_text_with_shadow(
text.chars(),
pos_x + 10.0,
pos_y + 10.0,
&state.constants,
&mut state.texture_set,
ctx,
)?;
}
if state.settings.debug_outlines { if state.settings.debug_outlines {
self.draw_debug_outlines(state, ctx)?; self.draw_debug_outlines(state, ctx)?;

View file

@ -303,6 +303,7 @@ bitfield! {
pub fast, set_fast: 4; pub fast, set_fast: 4;
pub position_top, set_position_top: 5; pub position_top, set_position_top: 5;
pub perma_fast, set_perma_fast: 6; pub perma_fast, set_perma_fast: 6;
pub cutscene_skip, set_cutscene_skip: 7;
} }
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
@ -604,10 +605,7 @@ impl TextScriptVM {
} }
if remaining > 1 { if remaining > 1 {
let ticks = if state.textscript_vm.flags.fast() let ticks = if state.textscript_vm.flags.fast() || state.textscript_vm.flags.cutscene_skip() {
|| game_scene.player1.controller.skip()
|| game_scene.player2.controller.skip()
{
0 0
} else if game_scene.player1.controller.jump() } else if game_scene.player1.controller.jump()
|| game_scene.player1.controller.shoot() || game_scene.player1.controller.shoot()
@ -644,6 +642,8 @@ impl TextScriptVM {
} }
} }
TextScriptExecutionState::WaitConfirmation(event, ip, no_event, wait, selection) => { TextScriptExecutionState::WaitConfirmation(event, ip, no_event, wait, selection) => {
state.textscript_vm.flags.set_cutscene_skip(false);
if wait > 0 { if wait > 0 {
state.textscript_vm.state = state.textscript_vm.state =
TextScriptExecutionState::WaitConfirmation(event, ip, no_event, wait - 1, selection); TextScriptExecutionState::WaitConfirmation(event, ip, no_event, wait - 1, selection);
@ -731,12 +731,11 @@ impl TextScriptVM {
state.touch_controls.control_type = TouchControlType::Dialog; state.touch_controls.control_type = TouchControlType::Dialog;
} }
if game_scene.player1.controller.trigger_jump() if state.textscript_vm.flags.cutscene_skip()
|| game_scene.player1.controller.trigger_jump()
|| game_scene.player1.controller.trigger_shoot() || game_scene.player1.controller.trigger_shoot()
|| game_scene.player1.controller.skip()
|| game_scene.player2.controller.trigger_jump() || game_scene.player2.controller.trigger_jump()
|| game_scene.player2.controller.trigger_shoot() || game_scene.player2.controller.trigger_shoot()
|| game_scene.player2.controller.skip()
{ {
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip); state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
} }
@ -809,9 +808,11 @@ impl TextScriptVM {
} }
} }
OpCode::_END => { OpCode::_END => {
state.textscript_vm.flags.set_cutscene_skip(false);
exec_state = TextScriptExecutionState::Ended; exec_state = TextScriptExecutionState::Ended;
} }
OpCode::END => { OpCode::END => {
state.textscript_vm.flags.set_cutscene_skip(false);
state.control_flags.set_tick_world(true); state.control_flags.set_tick_world(true);
state.control_flags.set_control_enabled(true); state.control_flags.set_control_enabled(true);
@ -1218,8 +1219,10 @@ impl TextScriptVM {
new_scene.player2.x = pos_x; new_scene.player2.x = pos_x;
new_scene.player2.y = pos_y; new_scene.player2.y = pos_y;
let skip = state.textscript_vm.flags.cutscene_skip();
state.control_flags.set_tick_world(true); state.control_flags.set_tick_world(true);
state.textscript_vm.flags.0 = 0; state.textscript_vm.flags.0 = 0;
state.textscript_vm.flags.set_cutscene_skip(skip);
state.textscript_vm.face = 0; state.textscript_vm.face = 0;
state.textscript_vm.item = 0; state.textscript_vm.item = 0;
state.textscript_vm.current_line = TextScriptLine::Line1; state.textscript_vm.current_line = TextScriptLine::Line1;
@ -1269,7 +1272,8 @@ impl TextScriptVM {
partner.vel_x = 0; partner.vel_x = 0;
partner.vel_y = 0; partner.vel_y = 0;
partner.x = executor.x + if (param % 10) == 1 { distance * 0x200 } else { -distance * 0x200 }; partner.x =
executor.x + if (param % 10) == 1 { distance * 0x200 } else { -distance * 0x200 };
partner.y = executor.y; partner.y = executor.y;
} }
} }
@ -1711,11 +1715,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
OpCode::UNJ OpCode::UNJ | OpCode::XX1 | OpCode::SIL | OpCode::SSS | OpCode::ACH => {
| OpCode::XX1
| OpCode::SIL
| OpCode::SSS
| OpCode::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);
@ -1844,7 +1844,7 @@ impl TextScript {
) -> GameResult<Vec<u8>> { ) -> GameResult<Vec<u8>> {
let mut bytecode = Vec::new(); let mut bytecode = Vec::new();
let mut char_buf = Vec::with_capacity(16); let mut char_buf = Vec::with_capacity(16);
let mut allow_next_event = false; let mut allow_next_event = true;
while let Some(&chr) = iter.peek() { while let Some(&chr) = iter.peek() {
match chr { match chr {