1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2024-10-31 19:44:20 +00:00
doukutsu-rs/src/game/scripting/tsc/text_script.rs

1919 lines
82 KiB
Rust
Raw Normal View History

use std::cell::RefCell;
2020-09-25 22:28:37 +00:00
use std::cmp::Ordering;
use std::collections::HashMap;
2020-08-23 02:17:45 +00:00
use std::io;
2020-08-27 22:29:10 +00:00
use std::io::Cursor;
use std::io::Seek;
use std::io::SeekFrom;
2020-09-05 02:56:29 +00:00
use std::ops::Not;
use std::rc::Rc;
2020-08-18 16:46:07 +00:00
use num_traits::{clamp, FromPrimitive};
use crate::bitfield;
2022-11-19 17:20:03 +00:00
use crate::common::Direction::{Left, Right};
use crate::common::{Direction, FadeDirection, FadeState, Rect};
2020-09-25 17:14:52 +00:00
use crate::engine_constants::EngineConstants;
2020-09-06 00:37:42 +00:00
use crate::entity::GameEntity;
2021-01-28 22:33:43 +00:00
use crate::framework::context::Context;
use crate::framework::error::GameResult;
2022-11-19 17:20:03 +00:00
use crate::game::frame::UpdateTarget;
use crate::game::npc::NPC;
use crate::game::player::{ControlMode, TargetPlayer};
use crate::game::scripting::tsc::bytecode_utils::read_cur_varint;
use crate::game::scripting::tsc::encryption::decrypt_tsc;
use crate::game::scripting::tsc::opcodes::TSCOpCode;
use crate::game::shared_game_state::ReplayState;
use crate::game::shared_game_state::SharedGameState;
use crate::game::weapon::WeaponType;
2022-11-20 19:38:36 +00:00
use crate::graphics::font::{Font, Symbols};
2021-04-23 01:55:13 +00:00
use crate::input::touch_controls::TouchControlType;
2020-08-27 02:43:21 +00:00
use crate::scene::game_scene::GameScene;
2020-08-18 16:46:07 +00:00
2022-09-17 10:57:37 +00:00
const TSC_SUBSTITUTION_MAP_SIZE: usize = 1;
bitfield! {
2021-10-15 14:36:05 +00:00
pub struct TextScriptFlags(u16);
impl Debug;
pub render, set_render: 0;
pub background_visible, set_background_visible: 1;
pub fast, set_fast: 4;
pub position_top, set_position_top: 5;
pub perma_fast, set_perma_fast: 6;
pub cutscene_skip, set_cutscene_skip: 7;
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[repr(u8)]
pub enum TextScriptEncoding {
UTF8 = 0,
ShiftJIS,
}
impl From<&str> for TextScriptEncoding {
fn from(s: &str) -> Self {
match s {
"utf-8" => Self::UTF8,
_ => Self::ShiftJIS,
}
}
}
impl TextScriptEncoding {
pub fn invalid_encoding(encoding: TextScriptEncoding, state: &SharedGameState) -> bool {
let required_encoding = if (state.loc.code == "jp" || state.loc.code == "en") && state.constants.is_base() {
TextScriptEncoding::ShiftJIS
} else {
TextScriptEncoding::UTF8
};
encoding != required_encoding
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[repr(u8)]
pub enum TextScriptLine {
Line1 = 0,
Line2,
2020-08-27 23:34:28 +00:00
Line3,
}
2020-09-05 02:56:29 +00:00
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[repr(u8)]
pub enum ConfirmSelection {
Yes,
No,
}
2020-09-29 00:43:55 +00:00
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[repr(u8)]
pub enum ScriptMode {
Map,
Inventory,
StageSelect,
Debug,
2020-09-29 00:43:55 +00:00
}
2020-09-05 02:56:29 +00:00
impl Not for ConfirmSelection {
type Output = ConfirmSelection;
fn not(self) -> ConfirmSelection {
if self == ConfirmSelection::Yes {
ConfirmSelection::No
} else {
ConfirmSelection::Yes
}
}
}
2020-08-27 02:43:21 +00:00
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum TextScriptExecutionState {
Ended,
2020-08-27 02:43:21 +00:00
Running(u16, u32),
Msg(u16, u32, u32, u8),
MsgNewLine(u16, u32, u32, u8, u8),
2020-09-06 00:37:42 +00:00
WaitTicks(u16, u32, u16),
2021-02-12 23:12:33 +00:00
WaitInput(u16, u32, u16),
WaitStanding(u16, u32),
2020-09-05 02:56:29 +00:00
WaitConfirmation(u16, u32, u16, u8, ConfirmSelection),
2020-08-28 01:41:14 +00:00
WaitFade(u16, u32),
2021-12-02 05:57:44 +00:00
FallingIsland(u16, u32, i32, i32, u16, bool),
2022-01-23 04:55:50 +00:00
MapSystem,
2020-10-30 01:29:53 +00:00
SaveProfile(u16, u32),
2020-09-22 00:01:55 +00:00
LoadProfile,
2020-11-01 19:39:57 +00:00
Reset,
}
2021-10-16 12:59:27 +00:00
#[derive(PartialEq, Copy, Clone)]
pub enum IllustrationState {
Hidden,
Shown,
FadeIn(f32),
FadeOut(f32),
}
2020-08-27 02:43:21 +00:00
pub struct TextScriptVM {
pub scripts: Rc<RefCell<Scripts>>,
2020-08-27 02:43:21 +00:00
pub state: TextScriptExecutionState,
2020-09-25 22:28:37 +00:00
pub stack: Vec<TextScriptExecutionState>,
pub flags: TextScriptFlags,
2020-09-29 00:43:55 +00:00
pub mode: ScriptMode,
2020-11-29 12:06:10 +00:00
/// The player who triggered the event.
pub executor_player: TargetPlayer,
2020-08-27 23:34:28 +00:00
/// Toggle for non-strict TSC parsing because English versions of CS+ (both AG and Nicalis release)
/// modified the events carelessly and since original Pixel's engine hasn't enforced constraints
/// while parsing no one noticed them.
pub strict_mode: bool,
2020-08-27 22:29:10 +00:00
pub suspend: bool,
2022-02-18 00:54:22 +00:00
/// Requires `constants.textscript.reset_invicibility_on_any_script`
2022-01-11 14:26:10 +00:00
pub reset_invicibility: bool,
2021-01-16 13:51:52 +00:00
pub numbers: [u16; 4],
pub face: u16,
2020-09-05 02:56:29 +00:00
pub item: u16,
pub current_line: TextScriptLine,
pub line_1: Vec<char>,
pub line_2: Vec<char>,
2020-08-27 23:34:28 +00:00
pub line_3: Vec<char>,
2021-10-16 12:59:27 +00:00
pub current_illustration: Option<String>,
pub illustration_state: IllustrationState,
2021-02-12 23:12:33 +00:00
prev_char: char,
2022-09-17 10:57:37 +00:00
pub substitution_rect_map: [(char, Rect<u16>); TSC_SUBSTITUTION_MAP_SIZE],
2020-08-27 02:43:21 +00:00
}
2020-09-29 00:43:55 +00:00
pub struct Scripts {
/// Head.tsc - shared part of map scripts
pub global_script: TextScript,
2020-09-29 00:43:55 +00:00
/// <Map>.tsc - map script
pub scene_script: TextScript,
2020-09-29 00:43:55 +00:00
/// ArmsItem.tsc - used by inventory
pub inventory_script: TextScript,
/// StageSelect.tsc - used by teleport target selector
pub stage_select_script: TextScript,
/// Debug TSC sink - used by the debug command line
pub debug_script: TextScript,
}
2020-09-29 00:43:55 +00:00
impl Scripts {
2020-09-29 20:19:47 +00:00
pub fn find_script(&self, mode: ScriptMode, event_num: u16) -> Option<&Vec<u8>> {
match mode {
ScriptMode::Map | ScriptMode::Debug => {
if mode == ScriptMode::Debug {
if let Some(tsc) = self.debug_script.event_map.get(&event_num) {
return Some(tsc);
}
}
2020-09-29 20:19:47 +00:00
if let Some(tsc) = self.scene_script.event_map.get(&event_num) {
return Some(tsc);
} else if let Some(tsc) = self.global_script.event_map.get(&event_num) {
return Some(tsc);
}
}
ScriptMode::Inventory => {
if let Some(tsc) = self.inventory_script.event_map.get(&event_num) {
return Some(tsc);
}
}
ScriptMode::StageSelect => {
if let Some(tsc) = self.stage_select_script.event_map.get(&event_num) {
return Some(tsc);
}
}
}
None
}
}
2020-08-27 02:43:21 +00:00
impl TextScriptVM {
pub fn new() -> Self {
Self {
scripts: Rc::new(RefCell::new(Scripts {
global_script: TextScript::new(),
scene_script: TextScript::new(),
2020-09-29 00:43:55 +00:00
inventory_script: TextScript::new(),
stage_select_script: TextScript::new(),
debug_script: TextScript::new(),
})),
2020-08-27 02:43:21 +00:00
state: TextScriptExecutionState::Ended,
2020-09-25 22:28:37 +00:00
stack: Vec::with_capacity(6),
2020-11-29 12:06:10 +00:00
flags: TextScriptFlags(0),
mode: ScriptMode::Map,
executor_player: TargetPlayer::Player1,
2020-08-27 23:34:28 +00:00
strict_mode: false,
2020-08-27 22:29:10 +00:00
suspend: true,
2022-01-11 14:26:10 +00:00
reset_invicibility: false,
2021-01-16 13:51:52 +00:00
numbers: [0; 4],
2020-08-27 02:43:21 +00:00
face: 0,
2020-11-29 12:06:10 +00:00
item: 0,
current_line: TextScriptLine::Line1,
2020-08-27 02:43:21 +00:00
line_1: Vec::with_capacity(24),
line_2: Vec::with_capacity(24),
2020-08-27 23:34:28 +00:00
line_3: Vec::with_capacity(24),
2021-10-16 12:59:27 +00:00
current_illustration: None,
illustration_state: IllustrationState::Hidden,
2021-02-12 23:12:33 +00:00
prev_char: '\x00',
2022-09-17 10:57:37 +00:00
substitution_rect_map: [('=', Rect::new(0, 0, 0, 0))],
2020-08-27 02:43:21 +00:00
}
}
pub fn set_global_script(&mut self, script: TextScript) {
{
let mut scripts = self.scripts.borrow_mut();
scripts.global_script = script;
}
2021-02-12 10:05:28 +00:00
if !self.suspend {
self.reset();
}
2020-08-27 02:43:21 +00:00
}
pub fn set_scene_script(&mut self, script: TextScript) {
{
let mut scripts = self.scripts.borrow_mut();
scripts.scene_script = script;
}
2021-02-12 10:05:28 +00:00
if !self.suspend {
self.reset();
}
2020-08-27 02:43:21 +00:00
}
2020-09-29 00:43:55 +00:00
pub fn set_inventory_script(&mut self, script: TextScript) {
let mut scripts = self.scripts.borrow_mut();
scripts.inventory_script = script;
2020-09-10 10:25:40 +00:00
}
2020-09-29 00:43:55 +00:00
pub fn set_stage_select_script(&mut self, script: TextScript) {
let mut scripts = self.scripts.borrow_mut();
scripts.stage_select_script = script;
2020-09-10 10:25:40 +00:00
}
pub fn set_debug_script(&mut self, script: TextScript) {
let mut scripts = self.scripts.borrow_mut();
scripts.debug_script = script;
}
2022-09-17 10:57:37 +00:00
pub fn set_substitution_rect_map(&mut self, rect_map: [(char, Rect<u16>); TSC_SUBSTITUTION_MAP_SIZE]) {
2022-07-23 11:57:40 +00:00
self.substitution_rect_map = rect_map;
}
2020-08-27 02:43:21 +00:00
pub fn reset(&mut self) {
self.state = TextScriptExecutionState::Ended;
2021-04-28 20:31:26 +00:00
self.flags.0 = 0;
2021-10-16 12:59:27 +00:00
self.current_illustration = None;
self.illustration_state = IllustrationState::Hidden;
self.face = 0;
self.clear_text_box();
}
pub fn clear_text_box(&mut self) {
2020-09-09 22:07:36 +00:00
self.item = 0;
self.current_line = TextScriptLine::Line1;
2020-08-27 02:43:21 +00:00
self.line_1.clear();
self.line_2.clear();
2020-08-27 23:34:28 +00:00
self.line_3.clear();
2020-08-27 02:43:21 +00:00
}
2020-09-29 20:19:47 +00:00
pub fn set_mode(&mut self, mode: ScriptMode) {
self.reset();
self.mode = mode;
}
2020-09-29 00:43:55 +00:00
2020-08-27 02:43:21 +00:00
pub fn start_script(&mut self, event_num: u16) {
self.reset();
2022-01-11 14:26:10 +00:00
self.reset_invicibility = true;
2020-08-27 02:43:21 +00:00
self.state = TextScriptExecutionState::Running(event_num, 0);
2020-09-10 23:41:00 +00:00
2020-08-27 02:43:21 +00:00
log::info!("Started script: #{:04}", event_num);
}
2020-08-27 22:29:10 +00:00
pub fn run(state: &mut SharedGameState, game_scene: &mut GameScene, ctx: &mut Context) -> GameResult {
let scripts_ref = state.textscript_vm.scripts.clone();
let scripts = scripts_ref.borrow();
let mut cached_event: Option<(u16, &Vec<u8>)> = None;
2020-08-27 02:43:21 +00:00
loop {
2021-02-12 10:05:28 +00:00
if state.textscript_vm.suspend {
break;
}
2020-08-27 22:29:10 +00:00
2020-08-27 02:43:21 +00:00
match state.textscript_vm.state {
TextScriptExecutionState::Ended => {
state.control_flags.set_interactions_disabled(false);
2020-08-27 02:43:21 +00:00
break;
}
TextScriptExecutionState::Running(event, ip) => {
2020-09-10 23:41:00 +00:00
state.control_flags.set_interactions_disabled(true);
// The `!event` case gets optimized out on None match
match (cached_event, !event) {
(None, bevent) | (Some((bevent, _)), _) if bevent != event => {
if let Some(bytecode) = scripts.find_script(state.textscript_vm.mode, event) {
cached_event = Some((event, bytecode));
} else {
cached_event = None;
}
}
_ => (),
}
state.textscript_vm.state = if let Some((_, bytecode)) = cached_event {
TextScriptVM::execute(bytecode, event, ip, state, game_scene, ctx)?
} else {
TextScriptExecutionState::Ended
};
if state.textscript_vm.state == TextScriptExecutionState::Ended {
state.textscript_vm.reset();
}
2020-08-27 02:43:21 +00:00
}
TextScriptExecutionState::Msg(event, ip, remaining, counter) => {
if counter > 0 {
state.textscript_vm.state = TextScriptExecutionState::Msg(event, ip, remaining, counter - 1);
break;
}
2021-04-23 01:55:13 +00:00
if !state.control_flags.control_enabled() {
state.touch_controls.control_type = TouchControlType::Dialog;
}
match (cached_event, !event) {
(None, bevent) | (Some((bevent, _)), _) if bevent != event => {
if let Some(bytecode) = scripts.find_script(state.textscript_vm.mode, event) {
cached_event = Some((event, bytecode));
} else {
cached_event = None;
}
}
_ => (),
}
if let Some((_, bytecode)) = cached_event {
2022-01-17 22:29:30 +00:00
let mut cursor: Cursor<&[u8]> = Cursor::new(bytecode);
let mut new_line = false;
cursor.seek(SeekFrom::Start(ip as u64))?;
let chr = std::char::from_u32(read_cur_varint(&mut cursor)? as u32).unwrap_or('\u{fffd}');
2022-11-20 19:38:36 +00:00
let builder = state.font.builder().with_symbols(Some(Symbols {
symbols: &state.textscript_vm.substitution_rect_map,
texture: "",
}));
match chr {
2020-08-27 22:29:10 +00:00
'\n' if state.textscript_vm.current_line == TextScriptLine::Line1 => {
state.textscript_vm.current_line = TextScriptLine::Line2;
}
2020-08-27 23:34:28 +00:00
'\n' if state.textscript_vm.current_line == TextScriptLine::Line2 => {
state.textscript_vm.current_line = TextScriptLine::Line3;
}
'\n' => {
new_line = true;
}
'\r' => {}
_ if state.textscript_vm.current_line == TextScriptLine::Line1 => {
2021-02-12 23:12:33 +00:00
state.textscript_vm.prev_char = chr;
state.textscript_vm.line_1.push(chr);
2021-02-12 23:12:33 +00:00
2022-11-20 19:38:36 +00:00
let text_len = builder.compute_width_iter(state.textscript_vm.line_1.iter().copied());
2021-02-12 23:12:33 +00:00
if text_len >= 284.0 {
state.textscript_vm.current_line = TextScriptLine::Line2;
}
}
_ if state.textscript_vm.current_line == TextScriptLine::Line2 => {
2021-02-12 23:12:33 +00:00
state.textscript_vm.prev_char = chr;
state.textscript_vm.line_2.push(chr);
2021-02-12 23:12:33 +00:00
2022-11-20 19:38:36 +00:00
let text_len = builder.compute_width_iter(state.textscript_vm.line_2.iter().copied());
2021-02-12 23:12:33 +00:00
if text_len >= 284.0 {
state.textscript_vm.current_line = TextScriptLine::Line3;
}
}
2020-08-27 23:34:28 +00:00
_ if state.textscript_vm.current_line == TextScriptLine::Line3 => {
2021-02-12 23:12:33 +00:00
state.textscript_vm.prev_char = chr;
2020-08-27 23:34:28 +00:00
state.textscript_vm.line_3.push(chr);
2021-02-12 23:12:33 +00:00
2022-11-20 19:38:36 +00:00
let text_len = builder.compute_width_iter(state.textscript_vm.line_3.iter().copied());
2021-02-12 23:12:33 +00:00
if text_len >= 284.0 {
new_line = true;
2021-02-12 23:12:33 +00:00
}
2020-08-27 23:34:28 +00:00
}
_ => {}
}
2022-02-18 00:54:22 +00:00
if remaining > 1 {
2021-10-15 14:36:05 +00:00
let ticks = if state.textscript_vm.flags.fast() || state.textscript_vm.flags.cutscene_skip()
{
2020-10-03 00:06:02 +00:00
0
2021-10-15 14:36:05 +00:00
} else if remaining != 2
&& (game_scene.player1.controller.jump()
2022-11-20 19:38:36 +00:00
|| game_scene.player1.controller.shoot()
|| game_scene.player2.controller.jump()
|| game_scene.player2.controller.shoot())
2021-02-12 10:05:28 +00:00
{
2021-06-20 19:41:09 +00:00
state.constants.textscript.text_speed_fast
2020-10-03 00:06:02 +00:00
} else {
2021-06-20 19:41:09 +00:00
state.constants.textscript.text_speed_normal
2020-10-03 00:06:02 +00:00
};
if ticks > 0 {
state.sound_manager.play_sfx(2);
}
state.textscript_vm.state = if !new_line {
TextScriptExecutionState::Msg(event, cursor.position() as u32, remaining - 1, ticks)
} else {
2022-01-21 21:40:37 +00:00
TextScriptExecutionState::MsgNewLine(
event,
cursor.position() as u32,
remaining - 1,
ticks,
4,
)
};
} else {
2023-05-16 14:00:17 +00:00
let ticks = if state.textscript_vm.flags.fast() || state.textscript_vm.flags.cutscene_skip()
{
0
} else {
state.constants.textscript.text_speed_fast
};
if ticks > 0 {
state.sound_manager.play_sfx(2);
}
2023-05-16 14:00:17 +00:00
state.textscript_vm.state = if new_line {
TextScriptExecutionState::MsgNewLine(
event,
cursor.position() as u32,
remaining,
ticks,
4,
)
} else {
TextScriptExecutionState::Running(event, cursor.position() as u32)
};
}
} else {
2020-08-27 02:43:21 +00:00
state.textscript_vm.reset();
}
}
TextScriptExecutionState::MsgNewLine(event, ip, remaining, ticks, mut counter) => {
counter = if state.textscript_vm.flags.fast() || state.textscript_vm.flags.cutscene_skip() {
0
} else {
counter.saturating_sub(1)
};
if counter == 0 {
state.textscript_vm.line_1.clear();
state.textscript_vm.line_1.append(&mut state.textscript_vm.line_2);
state.textscript_vm.line_2.append(&mut state.textscript_vm.line_3);
2023-05-16 14:00:17 +00:00
state.textscript_vm.state = if remaining < 2 {
TextScriptExecutionState::Running(event, ip)
} else {
TextScriptExecutionState::Msg(event, ip, remaining, ticks)
};
} else {
2022-01-21 21:40:37 +00:00
state.textscript_vm.state =
TextScriptExecutionState::MsgNewLine(event, ip, remaining, ticks, counter);
}
break;
}
2020-08-27 02:43:21 +00:00
TextScriptExecutionState::WaitTicks(event, ip, ticks) => {
if ticks == 0 {
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
} else if ticks != 9999 {
2020-08-27 02:43:21 +00:00
state.textscript_vm.state = TextScriptExecutionState::WaitTicks(event, ip, ticks - 1);
break;
} else {
break;
2020-08-27 02:43:21 +00:00
}
}
2020-09-05 02:56:29 +00:00
TextScriptExecutionState::WaitConfirmation(event, ip, no_event, wait, selection) => {
if wait > 0 {
2021-02-12 10:05:28 +00:00
state.textscript_vm.state =
TextScriptExecutionState::WaitConfirmation(event, ip, no_event, wait - 1, selection);
2020-09-05 02:56:29 +00:00
break;
}
2021-04-23 01:55:13 +00:00
let mut confirm =
game_scene.player1.controller.trigger_jump() || game_scene.player2.controller.trigger_jump();
if state.settings.touch_controls && !state.control_flags.control_enabled() {
state.touch_controls.control_type = TouchControlType::None;
let (off_left, _, off_right, off_bottom) =
crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
let box_x = ((state.canvas_size.0 - off_left - off_right) / 2.0) as isize + 51;
2021-04-28 19:34:09 +00:00
let box_y = (state.canvas_size.1 - off_bottom - 96.0 - 10.0) as isize;
2021-04-23 01:55:13 +00:00
2021-04-28 19:34:09 +00:00
if state.touch_controls.consume_click_in(Rect::new_size(box_x, box_y, 40, 40)) {
2021-04-23 01:55:13 +00:00
match selection {
ConfirmSelection::Yes => confirm = true,
ConfirmSelection::No => {
state.sound_manager.play_sfx(1);
state.textscript_vm.state = TextScriptExecutionState::WaitConfirmation(
event,
ip,
no_event,
0,
ConfirmSelection::Yes,
);
}
}
2021-04-28 19:34:09 +00:00
} else if state.touch_controls.consume_click_in(Rect::new_size(box_x + 41, box_y, 40, 40)) {
2021-04-23 01:55:13 +00:00
match selection {
ConfirmSelection::Yes => {
state.sound_manager.play_sfx(1);
state.textscript_vm.state = TextScriptExecutionState::WaitConfirmation(
event,
ip,
no_event,
0,
ConfirmSelection::No,
);
}
ConfirmSelection::No => confirm = true,
}
}
}
2020-11-28 19:25:51 +00:00
if game_scene.player1.controller.trigger_left()
|| game_scene.player1.controller.trigger_right()
|| game_scene.player2.controller.trigger_left()
2021-02-12 10:05:28 +00:00
|| game_scene.player2.controller.trigger_right()
{
state.sound_manager.play_sfx(1);
2021-02-12 10:05:28 +00:00
state.textscript_vm.state =
TextScriptExecutionState::WaitConfirmation(event, ip, no_event, 0, !selection);
2020-09-05 02:56:29 +00:00
break;
}
2021-04-23 01:55:13 +00:00
if confirm {
state.sound_manager.play_sfx(18);
2020-09-05 02:56:29 +00:00
match selection {
ConfirmSelection::Yes => {
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
2020-09-06 00:37:42 +00:00
}
2020-09-05 02:56:29 +00:00
ConfirmSelection::No => {
2021-04-28 20:31:26 +00:00
state.textscript_vm.clear_text_box();
2020-09-05 02:56:29 +00:00
state.textscript_vm.state = TextScriptExecutionState::Running(no_event, 0);
2020-09-06 00:37:42 +00:00
}
2020-09-05 02:56:29 +00:00
}
}
break;
}
TextScriptExecutionState::WaitStanding(event, ip) => {
if game_scene.player1.flags.hit_bottom_wall() || game_scene.player2.flags.hit_bottom_wall() {
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
}
break;
}
2021-02-12 23:12:33 +00:00
TextScriptExecutionState::WaitInput(event, ip, blink) => {
state.textscript_vm.state = TextScriptExecutionState::WaitInput(event, ip, (blink + 1) % 20);
2021-04-23 01:55:13 +00:00
if !state.control_flags.control_enabled() {
state.touch_controls.control_type = TouchControlType::Dialog;
}
2021-05-02 04:06:51 +00:00
if state.textscript_vm.flags.cutscene_skip()
|| game_scene.player1.controller.trigger_jump()
2020-11-28 19:25:51 +00:00
|| game_scene.player1.controller.trigger_shoot()
|| game_scene.player2.controller.trigger_jump()
2021-01-02 02:39:50 +00:00
|| game_scene.player2.controller.trigger_shoot()
2021-02-12 10:05:28 +00:00
{
2020-08-27 02:43:21 +00:00
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
}
break;
}
2020-08-28 01:41:14 +00:00
TextScriptExecutionState::WaitFade(event, ip) => {
if state.fade_state == FadeState::Hidden || state.fade_state == FadeState::Visible {
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
}
break;
}
2021-12-02 05:57:44 +00:00
TextScriptExecutionState::FallingIsland(event, ip, pos_x, mut pos_y, mut tick, mode) => {
if tick == 900 {
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
break;
}
tick += 1;
if mode {
if tick < 350 {
pos_y += 0x33;
} else if tick < 500 {
pos_y += 0x19;
} else if tick < 600 {
pos_y += 0xC;
} else if tick == 750 {
tick = 900;
}
} else {
pos_y += 0x33;
}
2020-10-30 01:29:53 +00:00
state.textscript_vm.state =
TextScriptExecutionState::FallingIsland(event, ip, pos_x, pos_y, tick, mode);
2021-12-02 05:57:44 +00:00
break;
}
2020-10-30 01:29:53 +00:00
TextScriptExecutionState::SaveProfile(event, ip) => {
state.save_game(game_scene, ctx)?;
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
break;
}
2020-09-22 00:01:55 +00:00
TextScriptExecutionState::LoadProfile => {
state.load_or_start_game(ctx)?;
break;
}
2020-11-01 19:39:57 +00:00
TextScriptExecutionState::Reset => {
state.reset();
state.start_new_game(ctx)?;
2020-11-01 19:39:57 +00:00
break;
}
2022-01-23 04:55:50 +00:00
TextScriptExecutionState::MapSystem => {
break;
}
2020-08-27 02:43:21 +00:00
}
}
Ok(())
}
2021-02-12 10:05:28 +00:00
pub fn execute(
2022-01-17 22:29:30 +00:00
bytecode: &[u8],
2021-02-12 10:05:28 +00:00
event: u16,
ip: u32,
state: &mut SharedGameState,
game_scene: &mut GameScene,
ctx: &mut Context,
) -> GameResult<TextScriptExecutionState> {
2020-08-27 02:43:21 +00:00
let mut exec_state = state.textscript_vm.state;
let mut cursor = Cursor::new(bytecode);
cursor.seek(SeekFrom::Start(ip as u64))?;
2020-08-27 02:43:21 +00:00
let op: TSCOpCode = if let Some(op) =
2022-11-20 19:38:36 +00:00
FromPrimitive::from_i32(read_cur_varint(&mut cursor).unwrap_or_else(|_| TSCOpCode::END as i32))
{
op
} else {
return Ok(TextScriptExecutionState::Ended);
};
match op {
TSCOpCode::_NOP => {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::_UNI => {}
TSCOpCode::_STR => {
let mut len = read_cur_varint(&mut cursor)? as u32;
if state.textscript_vm.flags.render() {
state.textscript_vm.prev_char = '\x00';
exec_state = TextScriptExecutionState::Msg(event, cursor.position() as u32, len, 4);
} else {
while len > 0 {
len -= 1;
let _ = read_cur_varint(&mut cursor)?;
}
// simply skip the text if we aren't in message mode.
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
}
TSCOpCode::_END => {
// Vanilla keeps execution going into the next event if no proper end condition is met
let scripts_ref = state.textscript_vm.scripts.clone();
let scripts = scripts_ref.borrow();
let script_list = scripts.scene_script.get_event_ids();
if let Some(next_event) = script_list.iter().find(|&&next_event| next_event > event) {
exec_state = TextScriptExecutionState::Running(*next_event, 0 as u32);
} else {
state.textscript_vm.flags.set_cutscene_skip(false);
exec_state = TextScriptExecutionState::Ended;
}
}
TSCOpCode::END => {
state.textscript_vm.flags.set_cutscene_skip(false);
state.control_flags.set_tick_world(true);
state.control_flags.set_control_enabled(true);
2020-09-06 00:37:42 +00:00
state.textscript_vm.flags.set_render(false);
state.textscript_vm.flags.set_background_visible(false);
state.textscript_vm.stack.clear();
if state.textscript_vm.mode == ScriptMode::Debug {
state.textscript_vm.set_mode(ScriptMode::Map);
}
game_scene.player1.cond.set_interacted(false);
game_scene.player2.cond.set_interacted(false);
2020-09-29 20:19:47 +00:00
exec_state = TextScriptExecutionState::Ended;
}
TSCOpCode::SLP => {
state.textscript_vm.set_mode(ScriptMode::StageSelect);
let event_num = if let Some(slot) =
2022-11-20 19:38:36 +00:00
state.teleporter_slots.get(game_scene.stage_select.current_teleport_slot as usize)
{
1000 + slot.0
} else {
1000
};
exec_state = TextScriptExecutionState::Running(event_num, 0);
}
TSCOpCode::PSp => {
let index = read_cur_varint(&mut cursor)? as u16;
let event_num = read_cur_varint(&mut cursor)? as u16;
if let Some(slot) = state.teleporter_slots.iter_mut().find(|s| s.0 == index) {
slot.1 = event_num;
} else {
state.teleporter_slots.push((index, event_num));
}
2020-09-29 20:19:47 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::PRI => {
state.control_flags.set_tick_world(false);
state.control_flags.set_control_enabled(false);
2020-09-29 20:19:47 +00:00
game_scene.player1.shock_counter = 0;
game_scene.player2.shock_counter = 0;
2020-09-29 20:19:47 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::KEY => {
state.control_flags.set_tick_world(true);
state.control_flags.set_control_enabled(false);
2020-08-27 02:43:21 +00:00
game_scene.player1.up = false;
game_scene.player1.shock_counter = 0;
game_scene.player2.up = false;
game_scene.player2.shock_counter = 0;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::FRE => {
state.control_flags.set_tick_world(true);
state.control_flags.set_control_enabled(true);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::MYD => {
let new_direction = read_cur_varint(&mut cursor)? as usize;
if let Some(direction) = Direction::from_int(new_direction) {
Fix LOTS of bugs and inaccuracies (#213) - Player vs. soft solid NPCs used the wrong collision size - Curly w/ Nemesis attached too high above the player when standing facing up - Butes from Bute spawners started following the player at the wrong conditions - Small pignons in Cemetery never turned around - Refill stations spawned with alt direction didn't spawn with upward velocity - Misery blocks spawned with glitchy framerects - Various issues with possessed Misery - Various issues with possessed Sue - Ballos (phase 1) didn't face the player at certain points - Ballos orbiting eyes (phase 4) didn't spawn smoke when killed - A part of Ballos phase 2 had the wrong collision type (apparently this was intentional due to a bug with soft solid collision; hopefully that's also fixed by these changes?) - A few missing sound effects during the Ballos fight - Green devil generators weren't spawning enough enemies - Balrog missile trails were in the wrong direction - Core blade projectiles had the wrong check for water collision - Monster X fish missiles moved at the wrong initial angle - Sisters fireball attacks shot in the wrong direction - Sisters missing smoke when shot - Undead Core missing smoke in some cases - <DNA'd NPCs spawned the wrong amount of smoke (fixes Heavy Press lightning despawn for the speedrun ;) ) - Player jumping and falling animations were flipped - Implemented <MYDxxxx facing towards entity xxxx if xxxx >= 10 (also <MYD0003 should not set the player's direction to down) - <TRA resets invincibility in freeware (due to starting a new event) - Bubbler level 1 bullets spawned at the wrong offset - Bubbler and Machine Gun autofire timers shouldn't reset when fire button is pressed - Fixed level 3 missile and Super Missile spawn offsets and trajectories - Nemesis carets spawned at wrong offsets - Various small typos/logic errors
2023-06-16 09:51:22 +00:00
if direction != Direction::Bottom {
game_scene.player1.direction = direction;
game_scene.player2.direction = direction;
}
} else if new_direction >= 10 {
for npc in game_scene.npc_list.iter_alive() {
// The vanilla game treats this as a 1-byte value lol
//if npc.event_num == (new_direction & 0xFF) as u16 {
if npc.event_num == new_direction as u16 {
game_scene.player1.direction = if game_scene.player1.x > npc.x { Direction::Left } else { Direction::Right };
game_scene.player2.direction = if game_scene.player2.x > npc.x { Direction::Left } else { Direction::Right };
}
}
}
game_scene.player1.cond.set_interacted(new_direction == 3);
game_scene.player2.cond.set_interacted(new_direction == 3);
game_scene.player1.vel_x = 0;
game_scene.player2.vel_x = 0;
2020-08-28 02:12:13 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::MYB => {
let new_direction = read_cur_varint(&mut cursor)? as usize;
game_scene.player1.vel_y = -0x200;
game_scene.player2.vel_y = -0x200;
// Reset interaction condition, needed for places like talking to Toroko in shack
game_scene.player1.cond.set_interacted(false);
game_scene.player2.cond.set_interacted(false);
if let Some(direction) = Direction::from_int_facing(new_direction) {
match direction {
Direction::Left => {
game_scene.player1.direction = Left;
game_scene.player2.direction = Left;
game_scene.player1.vel_x = 0x200;
game_scene.player2.vel_x = 0x200;
2020-08-28 02:12:13 +00:00
}
Direction::Up => {
game_scene.player1.vel_y = -0x200;
game_scene.player2.vel_y = -0x200;
}
Direction::Right => {
game_scene.player1.direction = Right;
game_scene.player2.direction = Right;
game_scene.player1.vel_x = -0x200;
game_scene.player2.vel_x = -0x200;
}
Direction::Bottom => {
game_scene.player1.vel_y = 0x200;
game_scene.player2.vel_y = 0x200;
}
_ => (),
}
} else {
for npc in game_scene.npc_list.iter_alive() {
if npc.event_num == new_direction as u16 {
if game_scene.player1.x >= npc.x {
game_scene.player1.direction = Left;
game_scene.player1.vel_x = 0x200;
} else {
game_scene.player1.direction = Right;
game_scene.player1.vel_x = -0x200;
}
if game_scene.player2.x >= npc.x {
game_scene.player2.direction = Left;
game_scene.player2.vel_x = 0x200;
} else {
game_scene.player2.direction = Right;
game_scene.player2.vel_x = -0x200;
}
break;
}
}
}
2020-08-28 02:12:13 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::SMC => {
game_scene.player1.cond.set_hidden(false);
game_scene.player2.cond.set_hidden(false);
2020-08-28 02:12:13 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::HMC => {
game_scene.player1.cond.set_hidden(true);
game_scene.player2.cond.set_hidden(true);
2020-08-28 02:12:13 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
2022-02-10 03:09:11 +00:00
TSCOpCode::HM2 => {
let player = match state.textscript_vm.executor_player {
TargetPlayer::Player1 => &mut game_scene.player1,
TargetPlayer::Player2 => &mut game_scene.player2,
};
player.cond.set_hidden(true);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::WAI => {
let ticks = read_cur_varint(&mut cursor)? as u16;
2020-09-25 22:28:37 +00:00
exec_state = TextScriptExecutionState::WaitTicks(event, cursor.position() as u32, ticks);
}
TSCOpCode::WAS => {
exec_state = TextScriptExecutionState::WaitStanding(event, cursor.position() as u32);
}
TSCOpCode::NOD => {
exec_state = TextScriptExecutionState::WaitInput(event, cursor.position() as u32, 0);
}
TSCOpCode::FLp | TSCOpCode::FLm => {
let flag_num = read_cur_varint(&mut cursor)? as u16;
state.set_flag(flag_num as usize, op == TSCOpCode::FLp);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::SKp | TSCOpCode::SKm => {
let flag_num = read_cur_varint(&mut cursor)? as u16;
state.set_skip_flag(flag_num as usize, op == TSCOpCode::SKp);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::FFm => {
let flag_from = read_cur_varint(&mut cursor)? as usize;
let flag_to = read_cur_varint(&mut cursor)? as usize;
if flag_to >= flag_from {
for flag in flag_from..=flag_to {
if state.get_flag(flag) {
state.set_flag(flag, false);
break;
2020-08-27 02:43:21 +00:00
}
}
}
2021-05-02 02:04:52 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::FLJ => {
let flag_num = read_cur_varint(&mut cursor)? as usize;
let event_num = read_cur_varint(&mut cursor)? as u16;
if state.get_flag(flag_num) {
state.textscript_vm.clear_text_box();
exec_state = TextScriptExecutionState::Running(event_num, 0);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
}
TSCOpCode::MPJ => {
let event_num = read_cur_varint(&mut cursor)? as u16;
if state.get_map_flag(game_scene.stage_id) {
state.textscript_vm.clear_text_box();
exec_state = TextScriptExecutionState::Running(event_num, 0);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
}
TSCOpCode::ITJ => {
let item_id = read_cur_varint(&mut cursor)? as u16;
let event_num = read_cur_varint(&mut cursor)? as u16;
if game_scene.inventory_player1.has_item(item_id) {
state.textscript_vm.clear_text_box();
exec_state = TextScriptExecutionState::Running(event_num, 0);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
}
TSCOpCode::INJ => {
let item_id = read_cur_varint(&mut cursor)? as u16;
let amount = read_cur_varint(&mut cursor)? as u16;
let event_num = read_cur_varint(&mut cursor)? as u16;
if game_scene.inventory_player1.has_item_amount(item_id, Ordering::Equal, amount) {
state.textscript_vm.clear_text_box();
exec_state = TextScriptExecutionState::Running(event_num, 0);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
}
TSCOpCode::AMJ => {
let weapon = read_cur_varint(&mut cursor)? as u8;
let event_num = read_cur_varint(&mut cursor)? as u16;
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon);
if weapon_type.is_some() && game_scene.inventory_player1.has_weapon(weapon_type.unwrap()) {
state.textscript_vm.clear_text_box();
exec_state = TextScriptExecutionState::Running(event_num, 0);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
}
TSCOpCode::NCJ => {
let npc_type = read_cur_varint(&mut cursor)? as u16;
let event_num = read_cur_varint(&mut cursor)? as u16;
if game_scene.npc_list.is_alive_by_type(npc_type) {
state.textscript_vm.clear_text_box();
exec_state = TextScriptExecutionState::Running(event_num, 0);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
}
TSCOpCode::ECJ => {
let npc_event_num = read_cur_varint(&mut cursor)? as u16;
let event_num = read_cur_varint(&mut cursor)? as u16;
if game_scene.npc_list.is_alive_by_event(npc_event_num) {
state.textscript_vm.clear_text_box();
exec_state = TextScriptExecutionState::Running(event_num, 0);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
}
TSCOpCode::SKJ => {
let flag_id = read_cur_varint(&mut cursor)? as u16;
let event_num = read_cur_varint(&mut cursor)? as u16;
if state.get_skip_flag(flag_id as usize) {
state.textscript_vm.clear_text_box();
exec_state = TextScriptExecutionState::Running(event_num, 0);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
}
TSCOpCode::S2PJ => {
let event_num = read_cur_varint(&mut cursor)? as u16;
exec_state = if game_scene.player2.cond.alive() {
TextScriptExecutionState::Running(event_num, 0)
} else {
TextScriptExecutionState::Running(event, cursor.position() as u32)
}
}
TSCOpCode::EVE => {
let event_num = read_cur_varint(&mut cursor)? as u16;
state.textscript_vm.clear_text_box();
exec_state = TextScriptExecutionState::Running(event_num, 0);
}
TSCOpCode::PSH => {
let event_num = read_cur_varint(&mut cursor)? as u16;
let saved_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
state.textscript_vm.stack.push(saved_state);
2021-10-10 22:17:49 +00:00
state.textscript_vm.clear_text_box();
exec_state = TextScriptExecutionState::Running(event_num, 0);
}
TSCOpCode::POP => {
if let Some(saved_state) = state.textscript_vm.stack.pop() {
exec_state = saved_state;
} else {
log::warn!("Tried to <POP from TSC stack without saved state!");
exec_state = TextScriptExecutionState::Ended;
}
}
TSCOpCode::MM0 => {
game_scene.player1.vel_x = 0;
game_scene.player2.vel_x = 0;
2021-10-10 22:17:49 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::SMP => {
let pos_x = read_cur_varint(&mut cursor)? as usize;
let pos_y = read_cur_varint(&mut cursor)? as usize;
2020-08-28 02:12:13 +00:00
let tile_type = game_scene.stage.tile_at(pos_x, pos_y);
game_scene.stage.change_tile(pos_x, pos_y, tile_type.wrapping_sub(1));
2020-09-25 22:28:37 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::CMP => {
let pos_x = read_cur_varint(&mut cursor)? as usize;
let pos_y = read_cur_varint(&mut cursor)? as usize;
let tile_type = read_cur_varint(&mut cursor)? as u8;
if game_scene.stage.change_tile(pos_x, pos_y, tile_type) {
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
npc.x = pos_x as i32 * 0x2000;
npc.y = pos_y as i32 * 0x2000;
let _ = game_scene.npc_list.spawn(0x100, npc.clone());
let _ = game_scene.npc_list.spawn(0x100, npc);
}
2020-09-25 22:28:37 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::MLp => {
let life = read_cur_varint(&mut cursor)? as u16;
game_scene.player1.life += life;
game_scene.player1.max_life += life;
game_scene.player2.life += life;
game_scene.player2.max_life += life;
#[cfg(feature = "discord-rpc")]
state.discord_rpc.update_hp(&game_scene.player1)?;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::FAC => {
let face = read_cur_varint(&mut cursor)? as u16;
2022-02-06 17:22:26 +00:00
// Switch uses xx00 for face animation states
if face % 100 != state.textscript_vm.face % 100 {
2022-02-07 02:52:19 +00:00
game_scene.text_boxes.slide_in = 7;
2022-02-06 17:22:26 +00:00
}
state.textscript_vm.face = face;
2020-08-28 02:12:13 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::CLR => {
state.textscript_vm.current_line = TextScriptLine::Line1;
state.textscript_vm.line_1.clear();
state.textscript_vm.line_2.clear();
state.textscript_vm.line_3.clear();
2020-11-01 19:39:57 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::MSG | TSCOpCode::MS2 | TSCOpCode::MS3 => {
state.textscript_vm.current_line = TextScriptLine::Line1;
state.textscript_vm.line_1.clear();
state.textscript_vm.line_2.clear();
state.textscript_vm.line_3.clear();
state.textscript_vm.flags.set_render(true);
state.textscript_vm.flags.set_background_visible(op != TSCOpCode::MS2);
state.textscript_vm.flags.set_fast(state.textscript_vm.flags.perma_fast());
state.textscript_vm.flags.set_position_top(op != TSCOpCode::MSG);
if op == TSCOpCode::MS2 {
state.textscript_vm.face = 0;
}
2020-11-01 19:08:52 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::SAT | TSCOpCode::CAT => {
state.textscript_vm.flags.set_perma_fast(true);
2020-08-27 02:43:21 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::TUR => {
state.textscript_vm.flags.set_fast(true);
2020-08-27 22:29:10 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::CLO => {
state.textscript_vm.flags.set_render(false);
state.textscript_vm.flags.set_background_visible(false);
state.textscript_vm.flags.set_fast(false);
state.textscript_vm.flags.set_position_top(false);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::YNJ => {
let event_no = read_cur_varint(&mut cursor)? as u16;
state.sound_manager.play_sfx(5);
exec_state = TextScriptExecutionState::WaitConfirmation(
event,
cursor.position() as u32,
event_no,
16,
ConfirmSelection::Yes,
);
}
2022-02-10 03:09:11 +00:00
TSCOpCode::UNJ => {
let mode = read_cur_varint(&mut cursor)?;
let event_num = read_cur_varint(&mut cursor)? as u16;
exec_state = if game_scene.player1.control_mode as i32 == mode {
TextScriptExecutionState::Running(event_num, cursor.position() as u32)
} else {
TextScriptExecutionState::Running(event, cursor.position() as u32)
};
}
TSCOpCode::NUM => {
let index = read_cur_varint(&mut cursor)? as usize;
if let Some(num) = state.textscript_vm.numbers.get(index) {
let mut str = num.to_string().chars().collect();
2020-10-03 00:06:02 +00:00
match state.textscript_vm.current_line {
TextScriptLine::Line1 => state.textscript_vm.line_1.append(&mut str),
TextScriptLine::Line2 => state.textscript_vm.line_2.append(&mut str),
TextScriptLine::Line3 => state.textscript_vm.line_3.append(&mut str),
2020-10-03 00:06:02 +00:00
}
}
2020-10-03 00:06:02 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::GIT => {
let item = read_cur_varint(&mut cursor)? as u16;
state.textscript_vm.item = item;
2020-10-03 00:06:02 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::TRA => {
let map_id = read_cur_varint(&mut cursor)? as usize;
let event_num = read_cur_varint(&mut cursor)? as u16;
let mut new_scene = GameScene::new(state, ctx, map_id)?;
let block_size = new_scene.stage.map.tile_size.as_int() * 0x200;
let pos_x = read_cur_varint(&mut cursor)? as i32 * block_size;
let pos_y = read_cur_varint(&mut cursor)? as i32 * block_size;
new_scene.intro_mode = game_scene.intro_mode;
new_scene.inventory_player1 = game_scene.inventory_player1.clone();
new_scene.inventory_player2 = game_scene.inventory_player2.clone();
new_scene.player1 = game_scene.player1.clone();
new_scene.player1.vel_x = 0;
new_scene.player1.vel_y = 0;
new_scene.player1.x = pos_x;
new_scene.player1.y = pos_y;
new_scene.player2 = game_scene.player2.clone();
new_scene.player2.vel_x = 0;
new_scene.player2.vel_y = 0;
new_scene.player2.x = pos_x;
new_scene.player2.y = pos_y;
// Reset player interaction flag upon TRA
new_scene.player1.cond.set_interacted(false);
new_scene.player2.cond.set_interacted(false);
2022-01-13 16:34:36 +00:00
// Reset ground collision for WAS / WaitStanding
new_scene.player1.flags.set_hit_bottom_wall(false);
new_scene.player2.flags.set_hit_bottom_wall(false);
new_scene.frame.wait = game_scene.frame.wait;
new_scene.nikumaru = game_scene.nikumaru;
2022-03-04 23:37:25 +00:00
new_scene.replay = game_scene.replay.clone();
Fix LOTS of bugs and inaccuracies (#213) - Player vs. soft solid NPCs used the wrong collision size - Curly w/ Nemesis attached too high above the player when standing facing up - Butes from Bute spawners started following the player at the wrong conditions - Small pignons in Cemetery never turned around - Refill stations spawned with alt direction didn't spawn with upward velocity - Misery blocks spawned with glitchy framerects - Various issues with possessed Misery - Various issues with possessed Sue - Ballos (phase 1) didn't face the player at certain points - Ballos orbiting eyes (phase 4) didn't spawn smoke when killed - A part of Ballos phase 2 had the wrong collision type (apparently this was intentional due to a bug with soft solid collision; hopefully that's also fixed by these changes?) - A few missing sound effects during the Ballos fight - Green devil generators weren't spawning enough enemies - Balrog missile trails were in the wrong direction - Core blade projectiles had the wrong check for water collision - Monster X fish missiles moved at the wrong initial angle - Sisters fireball attacks shot in the wrong direction - Sisters missing smoke when shot - Undead Core missing smoke in some cases - <DNA'd NPCs spawned the wrong amount of smoke (fixes Heavy Press lightning despawn for the speedrun ;) ) - Player jumping and falling animations were flipped - Implemented <MYDxxxx facing towards entity xxxx if xxxx >= 10 (also <MYD0003 should not set the player's direction to down) - <TRA resets invincibility in freeware (due to starting a new event) - Bubbler level 1 bullets spawned at the wrong offset - Bubbler and Machine Gun autofire timers shouldn't reset when fire button is pressed - Fixed level 3 missile and Super Missile spawn offsets and trajectories - Nemesis carets spawned at wrong offsets - Various small typos/logic errors
2023-06-16 09:51:22 +00:00
// Reset player invincibility (kind of hacky, but oh well)
if state.constants.textscript.reset_invicibility_on_any_script {
new_scene.player1.shock_counter = 0;
new_scene.player2.shock_counter = 0;
}
let skip = state.textscript_vm.flags.cutscene_skip();
state.control_flags.set_tick_world(true);
state.control_flags.set_interactions_disabled(true);
state.textscript_vm.flags.0 = 0;
state.textscript_vm.flags.set_cutscene_skip(skip);
state.textscript_vm.face = 0;
state.textscript_vm.item = 0;
state.textscript_vm.current_line = TextScriptLine::Line1;
state.textscript_vm.line_1.clear();
state.textscript_vm.line_2.clear();
state.textscript_vm.line_3.clear();
state.textscript_vm.suspend = true;
state.next_scene = Some(Box::new(new_scene));
log::info!("Transitioning to stage {}, with script #{:04}", map_id, event_num);
exec_state = TextScriptExecutionState::Running(event_num, 0);
}
TSCOpCode::MOV => {
let block_size = state.tile_size.as_int() * 0x200;
2020-08-28 01:41:14 +00:00
let pos_x = read_cur_varint(&mut cursor)? as i32 * block_size;
let pos_y = read_cur_varint(&mut cursor)? as i32 * block_size;
2021-01-16 13:51:52 +00:00
game_scene.player1.cond.set_interacted(false);
game_scene.player2.cond.set_interacted(false);
2021-01-16 13:51:52 +00:00
for player in [&mut game_scene.player1, &mut game_scene.player2].iter_mut() {
player.vel_x = 0;
player.vel_y = 0;
player.x = pos_x;
player.y = pos_y;
}
2021-01-16 13:51:52 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::S2MV => {
let param = read_cur_varint(&mut cursor)? as usize;
let (executor, partner) = match state.textscript_vm.executor_player {
TargetPlayer::Player1 => (&game_scene.player1, &mut game_scene.player2),
TargetPlayer::Player2 => (&game_scene.player2, &mut game_scene.player1),
};
2020-09-06 00:37:42 +00:00
match param {
0 | 1 => {
partner.vel_x = 0;
partner.vel_y = 0;
partner.x = executor.x + if param == 0 { -0x2000 } else { 0x2000 };
partner.y = executor.y;
2020-09-06 00:37:42 +00:00
}
2..=10 => {
log::warn!("<2MV unknown param");
2020-08-27 22:29:10 +00:00
}
// what the fuck
i => {
let distance = i as i32 / 10;
2021-06-27 01:06:56 +00:00
partner.vel_x = 0;
partner.vel_y = 0;
partner.x = executor.x + if (param % 10) == 1 { distance * 0x200 } else { -distance * 0x200 };
partner.y = executor.y;
}
}
if partner.cond.alive() && !partner.cond.hidden() {
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
npc.x = partner.x;
npc.y = partner.y;
let _ = game_scene.npc_list.spawn(0x100, npc.clone());
let _ = game_scene.npc_list.spawn(0x100, npc.clone());
let _ = game_scene.npc_list.spawn(0x100, npc.clone());
let _ = game_scene.npc_list.spawn(0x100, npc);
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::UNI => {
let control_mode = read_cur_varint(&mut cursor)? as u8;
2020-11-29 12:06:10 +00:00
let mode: Option<ControlMode> = FromPrimitive::from_u8(control_mode);
if let Some(mode) = mode {
game_scene.player1.control_mode = mode;
game_scene.player2.control_mode = mode;
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::FAI => {
let fade_type = read_cur_varint(&mut cursor)? as usize;
if let Some(direction) = FadeDirection::from_int(fade_type) {
state.fade_state = FadeState::FadeIn(15, direction);
}
exec_state = TextScriptExecutionState::WaitFade(event, cursor.position() as u32);
}
TSCOpCode::FAO => {
let fade_type = read_cur_varint(&mut cursor)? as usize;
if let Some(direction) = FadeDirection::from_int(fade_type) {
2022-03-01 02:24:24 +00:00
state.fade_state = FadeState::FadeOut(-15, direction.opposite());
}
exec_state = TextScriptExecutionState::WaitFade(event, cursor.position() as u32);
}
TSCOpCode::QUA => {
let count = read_cur_varint(&mut cursor)? as u16;
2020-08-28 01:41:14 +00:00
state.quake_counter = count;
2022-08-13 14:54:05 +00:00
state.quake_rumble_counter = count as u32;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::MNA => {
game_scene.display_map_name(160);
2020-08-28 01:41:14 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::CMU => {
let song_id = read_cur_varint(&mut cursor)? as usize;
2023-08-10 10:39:40 +00:00
state.sound_manager.play_song(song_id, &state.constants, &state.settings, ctx, false)?;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::FMU => {
2023-08-10 10:39:40 +00:00
state.sound_manager.play_song(0, &state.constants, &state.settings, ctx, true)?;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::RMU => {
state.sound_manager.restore_state()?;
2020-09-04 23:47:09 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::SOU => {
let sound = read_cur_varint(&mut cursor)? as u8;
2020-09-03 11:48:56 +00:00
state.sound_manager.play_sfx(sound);
2020-09-03 11:48:56 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::DNP => {
let event_num = read_cur_varint(&mut cursor)? as u16;
2020-09-03 11:48:56 +00:00
game_scene.npc_list.kill_npcs_by_event(event_num, state);
2020-09-16 19:55:32 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::DNA => {
let npc_remove_type = read_cur_varint(&mut cursor)? as u16;
2020-09-16 19:55:32 +00:00
game_scene.npc_list.kill_npcs_by_type(npc_remove_type, true, state);
2020-09-05 02:09:52 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::FOB => {
let part_id = read_cur_varint(&mut cursor)? as u16;
let ticks = read_cur_varint(&mut cursor)? as i32;
2020-09-05 02:09:52 +00:00
game_scene.frame.wait = ticks;
game_scene.frame.update_target = UpdateTarget::Boss(part_id);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::FOM => {
let ticks = read_cur_varint(&mut cursor)? as i32;
game_scene.frame.wait = ticks;
game_scene.frame.update_target = UpdateTarget::Player;
2020-10-31 14:32:13 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::FON => {
let event_num = read_cur_varint(&mut cursor)? as u16;
let ticks = read_cur_varint(&mut cursor)? as i32;
game_scene.frame.wait = ticks;
for npc in game_scene.npc_list.iter() {
if event_num == npc.event_num {
game_scene.frame.update_target = UpdateTarget::NPC(npc.id);
break;
2020-10-31 14:32:13 +00:00
}
}
2020-11-01 19:39:57 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::BSL => {
let event_num = read_cur_varint(&mut cursor)? as u16;
if event_num == 0 {
game_scene.boss_life_bar.set_boss_target(&game_scene.boss);
} else {
for npc in game_scene.npc_list.iter_alive() {
if event_num == npc.event_num {
game_scene.boss_life_bar.set_npc_target(npc.id, &game_scene.npc_list);
break;
}
2020-11-01 19:39:57 +00:00
}
}
2020-09-06 00:37:42 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::BOA => {
let action_num = read_cur_varint(&mut cursor)? as u16;
2020-09-06 00:37:42 +00:00
game_scene.boss.parts[0].action_num = action_num;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::ANP => {
let event_num = read_cur_varint(&mut cursor)? as u16;
let action_num = read_cur_varint(&mut cursor)? as u16;
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
for npc in game_scene.npc_list.iter_alive() {
if npc.event_num == event_num {
npc.action_num = action_num;
npc.tsc_direction = tsc_direction as u16;
if direction == Direction::FacingPlayer {
let player = match state.textscript_vm.executor_player {
TargetPlayer::Player1 => &game_scene.player1,
TargetPlayer::Player2 => &game_scene.player2,
};
npc.direction = if player.x < npc.x { Direction::Left } else { Direction::Right };
2022-02-10 04:00:02 +00:00
} else if tsc_direction != 5 {
npc.direction = direction;
}
}
}
2020-11-02 02:20:16 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::CNP | TSCOpCode::INP => {
let event_num = read_cur_varint(&mut cursor)? as u16;
let new_type = read_cur_varint(&mut cursor)? as u16;
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
for npc in game_scene.npc_list.iter_alive() {
if npc.event_num == event_num {
npc.npc_flags.set_solid_soft(false);
npc.npc_flags.set_ignore_tile_44(false);
npc.npc_flags.set_invulnerable(false);
npc.npc_flags.set_ignore_solidity(false);
npc.npc_flags.set_bouncy(false);
npc.npc_flags.set_shootable(false);
npc.npc_flags.set_solid_hard(false);
npc.npc_flags.set_rear_and_top_not_hurt(false);
npc.npc_flags.set_show_damage(false);
if op == TSCOpCode::INP {
npc.npc_flags.set_event_when_touched(true);
2020-09-06 00:37:42 +00:00
}
npc.npc_type = new_type;
npc.display_bounds = state.npc_table.get_display_bounds(new_type);
npc.hit_bounds = state.npc_table.get_hit_bounds(new_type);
let entry = state.npc_table.get_entry(new_type).unwrap().to_owned();
npc.npc_flags.0 |= entry.npc_flags.0;
npc.life = entry.life;
npc.size = entry.size;
npc.exp = entry.experience as u16;
npc.damage = entry.damage as u16;
npc.spritesheet_id = entry.spritesheet_id as u16;
2020-09-06 00:37:42 +00:00
npc.cond.set_alive(true);
npc.action_num = 0;
npc.action_counter = 0;
npc.anim_num = 0;
npc.anim_counter = 0;
npc.vel_x = 0;
npc.vel_y = 0;
npc.tsc_direction = tsc_direction as u16;
if direction == Direction::FacingPlayer {
let player = match state.textscript_vm.executor_player {
TargetPlayer::Player1 => &game_scene.player1,
TargetPlayer::Player2 => &game_scene.player2,
};
npc.direction = if player.x < npc.x { Direction::Left } else { Direction::Right };
2022-02-10 04:00:02 +00:00
} else if tsc_direction != 5 {
npc.direction = direction;
2020-09-06 00:37:42 +00:00
}
npc.tick(
state,
(
[&mut game_scene.player1, &mut game_scene.player2],
&game_scene.npc_list,
&mut game_scene.stage,
&mut game_scene.bullet_manager,
&mut game_scene.flash,
2021-12-02 05:57:44 +00:00
&mut game_scene.boss,
),
)?;
2020-09-06 00:37:42 +00:00
}
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::MNP => {
let event_num = read_cur_varint(&mut cursor)? as u16;
let x = read_cur_varint(&mut cursor)? as i32;
let y = read_cur_varint(&mut cursor)? as i32;
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
let block_size = state.tile_size.as_int() * 0x200;
for npc in game_scene.npc_list.iter_alive() {
if npc.event_num == event_num {
2021-06-27 01:06:56 +00:00
npc.x = x * block_size;
npc.y = y * block_size;
2020-11-01 19:05:29 +00:00
npc.tsc_direction = tsc_direction as u16;
2020-10-31 14:32:13 +00:00
if direction == Direction::FacingPlayer {
let player = match state.textscript_vm.executor_player {
TargetPlayer::Player1 => &game_scene.player1,
TargetPlayer::Player2 => &game_scene.player2,
};
npc.direction = if player.x < npc.x { Direction::Left } else { Direction::Right };
2022-02-10 04:00:02 +00:00
} else if tsc_direction != 5 {
2020-10-31 14:32:13 +00:00
npc.direction = direction;
}
break;
2020-10-31 14:32:13 +00:00
}
}
2020-09-09 22:21:27 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::SNP => {
let npc_type = read_cur_varint(&mut cursor)? as u16;
let x = read_cur_varint(&mut cursor)? as i32;
let y = read_cur_varint(&mut cursor)? as i32;
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
let block_size = state.tile_size.as_int() * 0x200;
let mut npc = NPC::create(npc_type, &state.npc_table);
npc.cond.set_alive(true);
npc.x = x * block_size;
npc.y = y * block_size;
npc.tsc_direction = tsc_direction as u16;
if direction == Direction::FacingPlayer {
let player = match state.textscript_vm.executor_player {
TargetPlayer::Player1 => &game_scene.player1,
TargetPlayer::Player2 => &game_scene.player2,
};
npc.direction = if player.x < npc.x { Direction::Left } else { Direction::Right };
} else {
npc.direction = direction;
}
2020-09-06 00:37:42 +00:00
let _ = game_scene.npc_list.spawn(0x100, npc);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::LIp => {
let life = read_cur_varint(&mut cursor)? as u16;
game_scene.player1.life = clamp(game_scene.player1.life + life, 0, game_scene.player1.max_life);
game_scene.player2.life = clamp(game_scene.player2.life + life, 0, game_scene.player2.max_life);
2020-09-25 22:28:37 +00:00
#[cfg(feature = "discord-rpc")]
state.discord_rpc.update_hp(&game_scene.player1)?;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::ITp => {
let item_id = read_cur_varint(&mut cursor)? as u16;
state.sound_manager.play_sfx(38);
2020-09-25 22:28:37 +00:00
if !game_scene.inventory_player1.has_item(item_id) {
game_scene.inventory_player1.add_item(item_id);
2022-03-15 22:18:25 +00:00
state.mod_requirements.append_item(ctx, item_id)?;
}
if !game_scene.inventory_player2.has_item(item_id) {
game_scene.inventory_player2.add_item(item_id);
2022-03-15 22:18:25 +00:00
state.mod_requirements.append_item(ctx, item_id)?;
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::IpN => {
let item_id = read_cur_varint(&mut cursor)? as u16;
let amount = read_cur_varint(&mut cursor)? as u16;
2022-10-06 16:31:10 +00:00
state.sound_manager.play_sfx(38);
if game_scene.inventory_player1.has_item_amount(item_id, Ordering::Less, amount) {
game_scene.inventory_player1.add_item(item_id);
2022-03-15 22:18:25 +00:00
state.mod_requirements.append_item(ctx, item_id)?;
}
if game_scene.inventory_player2.has_item_amount(item_id, Ordering::Less, amount) {
game_scene.inventory_player2.add_item(item_id);
2022-03-15 22:18:25 +00:00
state.mod_requirements.append_item(ctx, item_id)?;
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::ITm => {
let item_id = read_cur_varint(&mut cursor)? as u16;
2021-01-16 13:51:52 +00:00
game_scene.inventory_player1.consume_item(item_id);
game_scene.inventory_player2.consume_item(item_id);
2022-04-21 17:24:44 +00:00
game_scene.inventory_player1.current_item = 0;
game_scene.inventory_player2.current_item = 0;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::AMp => {
let weapon_id = read_cur_varint(&mut cursor)? as u8;
let max_ammo = read_cur_varint(&mut cursor)? as u16;
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon_id);
state.textscript_vm.numbers[0] = max_ammo;
if let Some(wtype) = weapon_type {
2022-10-10 07:44:51 +00:00
state.sound_manager.play_sfx(38);
game_scene.inventory_player1.add_weapon(wtype, max_ammo);
game_scene.inventory_player2.add_weapon(wtype, max_ammo);
2022-03-15 22:18:25 +00:00
state.mod_requirements.append_weapon(ctx, weapon_id as u16)?;
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::AMm => {
let weapon_id = read_cur_varint(&mut cursor)? as u8;
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon_id);
2021-01-16 13:51:52 +00:00
if let Some(wtype) = weapon_type {
game_scene.inventory_player1.remove_weapon(wtype);
game_scene.inventory_player2.remove_weapon(wtype);
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::AEp => {
game_scene.inventory_player1.refill_all_ammo();
game_scene.inventory_player2.refill_all_ammo();
2020-09-11 12:58:21 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::TAM => {
let old_weapon_id = read_cur_varint(&mut cursor)? as u8;
let new_weapon_id = read_cur_varint(&mut cursor)? as u8;
let max_ammo = read_cur_varint(&mut cursor)? as u16;
let old_weapon_type: Option<WeaponType> = FromPrimitive::from_u8(old_weapon_id);
let new_weapon_type: Option<WeaponType> = FromPrimitive::from_u8(new_weapon_id);
if let Some(wtype) = new_weapon_type {
game_scene.inventory_player1.trade_weapon(old_weapon_type, wtype, max_ammo);
game_scene.inventory_player2.trade_weapon(old_weapon_type, wtype, max_ammo);
}
2020-09-11 12:58:21 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::ZAM => {
game_scene.inventory_player1.reset_all_weapon_xp();
game_scene.inventory_player2.reset_all_weapon_xp();
2020-09-11 12:58:21 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::EQp => {
let mask = read_cur_varint(&mut cursor)? as u16;
2020-09-11 12:58:21 +00:00
game_scene.player1.equip.0 |= mask;
game_scene.player2.equip.0 |= mask;
2021-03-15 21:10:16 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::EQm => {
let mask = read_cur_varint(&mut cursor)? as u16;
game_scene.player1.equip.0 &= !mask;
game_scene.player2.equip.0 &= !mask;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::FLA => {
game_scene.flash.set_blink();
2021-05-02 02:04:52 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::INI => {
2022-02-15 01:03:24 +00:00
game_scene.player1.flags.0 = 0;
game_scene.player2.flags.0 = 0;
exec_state = TextScriptExecutionState::Reset;
}
TSCOpCode::ESC => {
state.control_flags.set_tick_world(false);
state.control_flags.set_control_enabled(false);
state.control_flags.set_interactions_disabled(true);
state.textscript_vm.flags.set_cutscene_skip(false);
2021-05-02 02:04:52 +00:00
2022-03-25 12:58:01 +00:00
exec_state = TextScriptExecutionState::Running(state.constants.game.intro_event, 0);
state.textscript_vm.suspend = true;
2023-08-10 10:39:40 +00:00
state.sound_manager.play_song(0, &state.constants, &state.settings, ctx, false)?;
2022-03-25 12:58:01 +00:00
state.reset();
state.start_intro(ctx)?;
}
TSCOpCode::SVP => {
exec_state = TextScriptExecutionState::SaveProfile(event, cursor.position() as u32);
}
TSCOpCode::LDP => {
2022-02-15 01:03:24 +00:00
game_scene.player1.flags.0 = 0;
game_scene.player2.flags.0 = 0;
state.control_flags.set_tick_world(false);
state.control_flags.set_control_enabled(false);
state.control_flags.set_interactions_disabled(true);
2020-08-28 02:12:13 +00:00
exec_state = TextScriptExecutionState::LoadProfile;
}
TSCOpCode::MPp => {
let stage_id = read_cur_varint(&mut cursor)? as u16;
2020-08-28 02:12:13 +00:00
state.set_map_flag(stage_id as usize, true);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::CRE => {
state.textscript_vm.flags.set_cutscene_skip(false);
state.control_flags.set_credits_running(true);
state.creditscript_vm.start();
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
2021-10-16 12:59:27 +00:00
TSCOpCode::SIL => {
let number = read_cur_varint(&mut cursor)? as u16;
log::warn!("<SIL{:04}", number);
state.textscript_vm.current_illustration = None;
state.textscript_vm.illustration_state = IllustrationState::FadeIn(-160.0);
2021-10-16 12:59:27 +00:00
2022-01-17 22:29:30 +00:00
for path in &state.constants.credit_illustration_paths {
2021-10-16 12:59:27 +00:00
let path = format!("{}Credit{:02}", path, number);
2022-02-10 07:54:20 +00:00
if state.texture_set.find_texture(ctx, &state.constants.base_paths, &path).is_some() {
2021-10-16 12:59:27 +00:00
state.textscript_vm.current_illustration = Some(path);
break;
}
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::CIL => {
log::warn!("<CIL");
2022-01-17 22:29:30 +00:00
state.textscript_vm.illustration_state = if state.textscript_vm.current_illustration.is_some() {
2021-10-16 12:59:27 +00:00
IllustrationState::FadeOut(0.0)
} else {
IllustrationState::Hidden
};
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
2021-12-02 05:57:44 +00:00
TSCOpCode::CPS => {
state.sound_manager.stop_sfx(58);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::SPS => {
state.sound_manager.loop_sfx(58);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::CSS => {
state.sound_manager.stop_sfx(40);
state.sound_manager.stop_sfx(41);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::SSS => {
2022-01-22 21:45:54 +00:00
let freq = read_cur_varint(&mut cursor)? as f32 / 2205.0;
2021-12-02 05:57:44 +00:00
2022-01-22 21:45:54 +00:00
state.sound_manager.loop_sfx_freq(40, freq);
state.sound_manager.loop_sfx_freq(41, freq);
2021-12-02 05:57:44 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::XX1 => {
let mode = read_cur_varint(&mut cursor)?;
2022-03-15 22:18:25 +00:00
if mode != 0 && !state.mod_requirements.beat_hell {
state.mod_requirements.beat_hell = true;
state.mod_requirements.save(ctx)?;
}
exec_state = TextScriptExecutionState::FallingIsland(
event,
cursor.position() as u32,
0x15000,
0x8000,
0,
mode != 0,
);
2021-12-02 05:57:44 +00:00
}
2022-01-21 21:40:37 +00:00
TSCOpCode::STC => {
2022-03-04 23:37:25 +00:00
let new_record = game_scene.nikumaru.save_counter(state, ctx)?;
2022-08-14 11:19:46 +00:00
if state.replay_state == ReplayState::Recording {
game_scene.replay.stop_recording(state, ctx, new_record)?;
2022-03-04 23:37:25 +00:00
}
2022-01-21 21:40:37 +00:00
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
2022-01-23 04:55:50 +00:00
TSCOpCode::MLP => {
exec_state = TextScriptExecutionState::MapSystem;
}
2022-02-10 03:09:11 +00:00
TSCOpCode::KE2 => {
state.control_flags.set_tick_world(true);
state.control_flags.set_ok_button_disabled(true);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::FR2 => {
state.control_flags.set_tick_world(true);
state.control_flags.set_ok_button_disabled(false);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
2022-02-10 03:09:11 +00:00
TSCOpCode::ACH => {
// todo: any idea for any practical purpose of that opcode?
let idx = read_cur_varint(&mut cursor)?;
2022-02-10 03:09:11 +00:00
log::info!("achievement get: {}", idx);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
2020-08-27 02:43:21 +00:00
}
}
Ok(exec_state)
}
}
pub struct TextScript {
2022-11-19 17:20:03 +00:00
pub(crate) event_map: HashMap<u16, Vec<u8>>,
}
2020-08-23 02:17:45 +00:00
2020-08-27 02:43:21 +00:00
impl Clone for TextScript {
fn clone(&self) -> Self {
2021-02-12 10:05:28 +00:00
Self { event_map: self.event_map.clone() }
2020-08-27 02:43:21 +00:00
}
}
impl Default for TextScript {
fn default() -> Self {
TextScript::new()
}
}
2020-08-18 16:46:07 +00:00
impl TextScript {
2020-08-27 02:43:21 +00:00
pub fn new() -> TextScript {
2021-02-12 10:05:28 +00:00
Self { event_map: HashMap::new() }
2020-08-27 02:43:21 +00:00
}
2020-08-23 02:17:45 +00:00
/// Loads, decrypts and compiles a text script from specified stream.
2020-09-25 17:14:52 +00:00
pub fn load_from<R: io::Read>(mut data: R, constants: &EngineConstants) -> GameResult<TextScript> {
2020-08-23 02:17:45 +00:00
let mut buf = Vec::new();
data.read_to_end(&mut buf)?;
2020-09-25 17:14:52 +00:00
if constants.textscript.encrypted {
decrypt_tsc(&mut buf);
2020-08-23 02:17:45 +00:00
}
2020-09-25 17:14:52 +00:00
TextScript::compile(&buf, false, constants.textscript.encoding)
2020-08-23 02:17:45 +00:00
}
2020-08-27 02:43:21 +00:00
pub fn get_event_ids(&self) -> Vec<u16> {
2021-10-15 14:36:05 +00:00
let mut vec: Vec<u16> = self.event_map.keys().copied().collect();
vec.sort();
vec
}
pub fn has_event(&self, id: u16) -> bool {
self.event_map.contains_key(&id)
}
}