2020-09-25 22:28:37 +00:00
|
|
|
use std::cmp::Ordering;
|
2020-08-26 01:06:21 +00:00
|
|
|
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-08-26 01:06:21 +00:00
|
|
|
use std::iter::Peekable;
|
2020-09-05 02:56:29 +00:00
|
|
|
use std::ops::Not;
|
2020-08-26 01:06:21 +00:00
|
|
|
use std::str::FromStr;
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
use byteorder::ReadBytesExt;
|
2020-08-26 01:06:21 +00:00
|
|
|
use itertools::Itertools;
|
2020-08-27 02:43:21 +00:00
|
|
|
use num_derive::FromPrimitive;
|
2020-09-10 13:24:04 +00:00
|
|
|
use num_traits::{clamp, FromPrimitive};
|
2020-08-26 01:06:21 +00:00
|
|
|
|
2020-08-27 05:12:14 +00:00
|
|
|
use crate::bitfield;
|
2020-08-28 02:12:13 +00:00
|
|
|
use crate::common::{Direction, FadeDirection, FadeState};
|
2020-09-13 03:30:56 +00:00
|
|
|
use crate::encoding::{read_cur_shift_jis, read_cur_wtf8};
|
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;
|
2020-11-04 23:25:18 +00:00
|
|
|
use crate::frame::UpdateTarget;
|
2021-01-28 22:33:43 +00:00
|
|
|
use crate::framework::context::Context;
|
|
|
|
use crate::framework::error::GameError::{InvalidValue, ParseError};
|
|
|
|
use crate::framework::error::GameResult;
|
2020-12-25 22:39:41 +00:00
|
|
|
use crate::npc::NPC;
|
2020-11-29 12:06:10 +00:00
|
|
|
use crate::player::{ControlMode, TargetPlayer};
|
2020-08-27 02:43:21 +00:00
|
|
|
use crate::scene::game_scene::GameScene;
|
2020-09-20 15:27:31 +00:00
|
|
|
use crate::scene::title_scene::TitleScene;
|
|
|
|
use crate::shared_game_state::SharedGameState;
|
2020-09-23 13:10:42 +00:00
|
|
|
use crate::str;
|
2020-09-12 00:42:44 +00:00
|
|
|
use crate::weapon::WeaponType;
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2020-08-26 01:06:21 +00:00
|
|
|
/// Engine's text script VM operation codes.
|
2020-08-27 02:43:21 +00:00
|
|
|
#[derive(EnumString, Debug, FromPrimitive, PartialEq)]
|
|
|
|
#[repr(i32)]
|
2020-08-23 02:17:45 +00:00
|
|
|
pub enum OpCode {
|
|
|
|
// ---- Internal opcodes (used by bytecode, no TSC representation)
|
|
|
|
/// internal: no operation
|
2020-08-27 02:43:21 +00:00
|
|
|
_NOP = 0,
|
2020-08-26 01:06:21 +00:00
|
|
|
/// internal: unimplemented
|
|
|
|
_UNI,
|
2020-08-27 02:43:21 +00:00
|
|
|
/// internal: string marker
|
|
|
|
_STR,
|
|
|
|
/// internal: implicit END marker
|
|
|
|
_END,
|
2020-08-23 02:17:45 +00:00
|
|
|
|
2020-09-25 22:28:37 +00:00
|
|
|
// ---- Vanilla opcodes ----
|
|
|
|
/// <BOAxxxx, Starts boss animation
|
2020-08-23 02:17:45 +00:00
|
|
|
BOA,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <BSLxxxx, Starts boss fight
|
2020-08-23 02:17:45 +00:00
|
|
|
BSL,
|
|
|
|
|
2020-11-01 19:39:57 +00:00
|
|
|
/// <FOBxxxx:yyyy, Focuses on boss part xxxx and sets speed to yyyy ticks
|
2020-08-23 02:17:45 +00:00
|
|
|
FOB,
|
2020-11-01 19:39:57 +00:00
|
|
|
/// <FOMxxxx, Focuses on player and sets speed to xxxx
|
2020-08-23 02:17:45 +00:00
|
|
|
FOM,
|
2020-11-01 19:39:57 +00:00
|
|
|
/// <FONxxxx:yyyy, Focuses on NPC tagged with event xxxx and sets speed to yyyy
|
2020-08-23 02:17:45 +00:00
|
|
|
FON,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <FLA, Flashes screen
|
2020-08-23 02:17:45 +00:00
|
|
|
FLA,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <QUAxxxx, Starts quake for xxxx ticks
|
2020-08-23 02:17:45 +00:00
|
|
|
QUA,
|
|
|
|
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <UNIxxxx, Sets player movement mode (0 = normal, 1 = main artery)
|
2020-08-23 02:17:45 +00:00
|
|
|
UNI,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <HMC, Hides the player
|
2020-08-23 02:17:45 +00:00
|
|
|
HMC,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <SMC, Shows the player
|
2020-08-23 02:17:45 +00:00
|
|
|
SMC,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <MM0, Halts horizontal movement
|
2020-08-23 02:17:45 +00:00
|
|
|
MM0,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <MOVxxxx:yyyy, Moves the player to tile (xxxx,yyyy)
|
2020-08-23 02:17:45 +00:00
|
|
|
MOV,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <MYBxxxx, Bumps the player from direction xxxx
|
2020-08-23 02:17:45 +00:00
|
|
|
MYB,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <MYDxxxx, Makes the player face direction xxxx
|
2020-08-23 02:17:45 +00:00
|
|
|
MYD,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <TRAxxxx:yyyy:zzzz:wwww, Travels to map xxxx, starts event yyyy, places the player at tile (zzzz,wwww)
|
2020-08-23 02:17:45 +00:00
|
|
|
TRA,
|
|
|
|
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <END, Ends the current event
|
2020-08-23 02:17:45 +00:00
|
|
|
END,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <FRE, Starts world ticking and unlocks player controls.
|
2020-08-23 02:17:45 +00:00
|
|
|
FRE,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <FAIxxxx, Fades in with direction xxxx
|
2020-08-23 02:17:45 +00:00
|
|
|
FAI,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <FAOxxxx, Fades out with direction xxxx
|
2020-08-23 02:17:45 +00:00
|
|
|
FAO,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <WAIxxxx, Waits for xxxx frames
|
2020-08-23 02:17:45 +00:00
|
|
|
WAI,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <WASs, Waits until the player is standing
|
2020-08-23 02:17:45 +00:00
|
|
|
WAS,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <KEY, Locks out the player controls.
|
2020-08-23 02:17:45 +00:00
|
|
|
KEY,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <PRI, Stops world ticking and locks out player controls.
|
2020-08-23 02:17:45 +00:00
|
|
|
PRI,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <NOD, Waits for input
|
2020-08-23 02:17:45 +00:00
|
|
|
NOD,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <CAT, Instantly displays the text, works for entire event
|
2020-08-23 02:17:45 +00:00
|
|
|
CAT,
|
2020-10-03 00:06:02 +00:00
|
|
|
/// <SAT, Same as <CAT
|
2020-08-23 02:17:45 +00:00
|
|
|
SAT,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <TUR, Instantly displays the text, works until <MSG/2/3 or <END
|
2020-08-23 02:17:45 +00:00
|
|
|
TUR,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <CLO, Closes the text box
|
2020-08-23 02:17:45 +00:00
|
|
|
CLO,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <CLR, Clears the text box
|
2020-08-23 02:17:45 +00:00
|
|
|
CLR,
|
2020-09-26 00:03:17 +00:00
|
|
|
/// <FACxxxx, Shows the face xxxx in text box, 0 to hide,
|
|
|
|
/// CS+ Switch extensions:
|
|
|
|
/// - add 0100 to display talking animation (requires faceanm.dat)
|
|
|
|
/// - add 1000 to the number to display the face in opposite direction. (works on any CS, including freeware mods)
|
2020-08-23 02:17:45 +00:00
|
|
|
FAC,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <GITxxxx, Shows the item xxxx above text box, 0 to hide
|
2020-08-23 02:17:45 +00:00
|
|
|
GIT,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <MS2, Displays text on top of the screen without background.
|
2020-08-23 02:17:45 +00:00
|
|
|
MS2,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <MS3, Displays text on top of the screen with background.
|
2020-08-23 02:17:45 +00:00
|
|
|
MS3,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <MSG, Displays text on bottom of the screen with background.
|
2020-08-23 02:17:45 +00:00
|
|
|
MSG,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <NUMxxxx, Displays a value from AM+, buggy in vanilla.
|
2020-08-23 02:17:45 +00:00
|
|
|
NUM,
|
|
|
|
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <ANPxxxx:yyyy:zzzz, Changes the animation state of NPC tagged with
|
|
|
|
/// event xxxx to yyyy and set the direction to zzzz
|
2020-08-23 02:17:45 +00:00
|
|
|
ANP,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <CNPxxxx:yyyy:zzzz, Changes the NPC tagged with event xxxx to type yyyy
|
|
|
|
/// and makes it face direction zzzz
|
2020-08-23 02:17:45 +00:00
|
|
|
CNP,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <INPxxxx:yyyy:zzzz, Same as <CNP, but also sets NPC flag event_when_touched (0x100)
|
2020-08-23 02:17:45 +00:00
|
|
|
INP,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <MNPxxxx:yyyy:zzzz:wwww, Moves NPC tagged with event xxxx to tile position (xxxx,yyyy)
|
|
|
|
/// and makes it face direction zzzz
|
2020-08-23 02:17:45 +00:00
|
|
|
MNP,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <DNAxxxx, Deletes all NPCs of type xxxx
|
2020-08-23 02:17:45 +00:00
|
|
|
DNA,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <DNPxxxx, Deletes all NPCs of type xxxx
|
2020-08-23 02:17:45 +00:00
|
|
|
DNP,
|
|
|
|
SNP,
|
|
|
|
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <FL-xxxx, Sets the flag xxxx to false
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "FL-")]
|
2020-08-23 02:17:45 +00:00
|
|
|
FLm,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <FL+xxxx, Sets the flag xxxx to true
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "FL+")]
|
2020-08-23 02:17:45 +00:00
|
|
|
FLp,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <MP-xxxx, Sets the map xxxx to true
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "MP+")]
|
2020-08-23 02:17:45 +00:00
|
|
|
MPp,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <SK-xxxx, Sets the skip flag xxx to false
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "SK-")]
|
2020-08-23 02:17:45 +00:00
|
|
|
SKm,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <SK+xxxx, Sets the skip flag xxx to true
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "SK+")]
|
2020-08-23 02:17:45 +00:00
|
|
|
SKp,
|
|
|
|
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <EQ+xxxx, Sets specified bits in equip bitfield
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "EQ+")]
|
2020-08-23 02:17:45 +00:00
|
|
|
EQp,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <EQ-xxxx, Unsets specified bits in equip bitfield
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "EQ-")]
|
2020-08-23 02:17:45 +00:00
|
|
|
EQm,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <ML+xxxx, Adds xxxx to maximum health.
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "ML+")]
|
2020-08-23 02:17:45 +00:00
|
|
|
MLp,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <IT+xxxx, Adds item xxxx to players inventory.
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "IT+")]
|
2020-08-23 02:17:45 +00:00
|
|
|
ITp,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <IT-xxxx, Removes item xxxx to players inventory.
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "IT-")]
|
2020-08-23 02:17:45 +00:00
|
|
|
ITm,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <AM+xxxx:yyyy, Adds weapon xxxx with yyyy ammo (0 = infinite) to players inventory.
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "AM+")]
|
2020-08-23 02:17:45 +00:00
|
|
|
AMp,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <AM-xxxx, Removes weapon xxxx from players inventory.
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "AM-")]
|
2020-08-23 02:17:45 +00:00
|
|
|
AMm,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <TAMxxxx:yyyy:zzzz, Trades weapon xxxx for weapon yyyy with zzzz ammo
|
2020-08-23 02:17:45 +00:00
|
|
|
TAM,
|
|
|
|
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <UNJxxxx, Jumps to event xxxx if no damage has been taken
|
2020-08-23 02:17:45 +00:00
|
|
|
UNJ,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <NCJxxxx:yyyy, Jumps to event xxxx if NPC of type yyyy is alive
|
2020-08-23 02:17:45 +00:00
|
|
|
NCJ,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <ECJxxxx:yyyy, Jumps to event xxxx if NPC tagged with event yyyy is alive
|
2020-08-23 02:17:45 +00:00
|
|
|
ECJ,
|
2020-11-04 15:37:00 +00:00
|
|
|
/// <FLJxxxx:yyyy, Jumps to event yyyy if flag xxxx is set
|
2020-08-23 02:17:45 +00:00
|
|
|
FLJ,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <FLJxxxx:yyyy, Jumps to event xxxx if player has item yyyy
|
2020-08-23 02:17:45 +00:00
|
|
|
ITJ,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <MPJxxxx, Jumps to event xxxx if map flag for current stage is set
|
2020-08-23 02:17:45 +00:00
|
|
|
MPJ,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <YNJxxxx, Jumps to event xxxx if prompt response is No, otherwise continues event execution
|
2020-08-23 02:17:45 +00:00
|
|
|
YNJ,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <MPJxxxx, Jumps to event xxxx if skip flag for is set
|
2020-08-23 02:17:45 +00:00
|
|
|
SKJ,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <EVExxxx, Jumps to event xxxx
|
2020-08-23 02:17:45 +00:00
|
|
|
EVE,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <AMJyyyy, Jumps to event xxxx player has weapon yyyy
|
2020-08-23 02:17:45 +00:00
|
|
|
AMJ,
|
|
|
|
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <MLP, Displays the map of current stage
|
2020-08-23 02:17:45 +00:00
|
|
|
MLP,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <MLP, Displays the name of current stage
|
2020-08-23 02:17:45 +00:00
|
|
|
MNA,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <CMPxxxx:yyyy:zzzz, Sets the tile at (xxxx,yyyy) to type zzzz
|
2020-08-23 02:17:45 +00:00
|
|
|
CMP,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <SMPxxxx:yyyy:zzzz, Subtracts 1 from tile type at (xxxx,yyyy)
|
2020-08-23 02:17:45 +00:00
|
|
|
SMP,
|
|
|
|
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <CRE, Shows credits
|
2020-08-23 02:17:45 +00:00
|
|
|
CRE,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <XX1xxxx, Shows falling island
|
2020-08-23 02:17:45 +00:00
|
|
|
XX1,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <CIL, Hides credits illustration
|
2020-08-23 02:17:45 +00:00
|
|
|
CIL,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <SILxxxx, Shows credits illustration xxxx
|
2020-08-23 02:17:45 +00:00
|
|
|
SIL,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <ESC, Exits to title screen
|
2020-08-23 02:17:45 +00:00
|
|
|
ESC,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <ESC, Exits to credits
|
2020-08-23 02:17:45 +00:00
|
|
|
INI,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <LDP, Loads a saved game
|
2020-08-23 02:17:45 +00:00
|
|
|
LDP,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <PS+xxxx:yyyy, Sets teleporter slot xxxx to event number yyyy
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "PS+")]
|
2020-08-23 02:17:45 +00:00
|
|
|
PSp,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <SLP, Shows the teleporter menu
|
2020-08-23 02:17:45 +00:00
|
|
|
SLP,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <ZAM, Resets the experience and level of all weapons
|
2020-08-23 02:17:45 +00:00
|
|
|
ZAM,
|
|
|
|
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <AE+, Refills ammunition
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "AE+")]
|
2020-08-23 02:17:45 +00:00
|
|
|
AEp,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <LI+xxxx, Recovers xxxx health
|
2020-08-26 01:06:21 +00:00
|
|
|
#[strum(serialize = "LI+")]
|
2020-08-23 02:17:45 +00:00
|
|
|
LIp,
|
|
|
|
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <SVP, Saves the current game
|
2020-08-23 02:17:45 +00:00
|
|
|
SVP,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <STC, Saves the state of Nikumaru counter
|
2020-08-23 02:17:45 +00:00
|
|
|
STC,
|
|
|
|
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <SOUxxxx, Plays sound effect xxxx
|
2020-08-23 02:17:45 +00:00
|
|
|
SOU,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <CMUxxxx, Changes BGM to xxxx
|
2020-08-23 02:17:45 +00:00
|
|
|
CMU,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <FMU, Fades the BGM
|
2020-08-23 02:17:45 +00:00
|
|
|
FMU,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <RMU, Restores the music state of BGM played before current one
|
2020-08-23 02:17:45 +00:00
|
|
|
RMU,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <CPS, Stops the propeller sound
|
2020-08-23 02:17:45 +00:00
|
|
|
CPS,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <SPS, Starts the propeller sound
|
2020-08-23 02:17:45 +00:00
|
|
|
SPS,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <CSS, Stops the stream sound
|
2020-08-23 02:17:45 +00:00
|
|
|
CSS,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <SSSxxxx, Starts the stream sound at volume xxxx
|
2020-08-23 02:17:45 +00:00
|
|
|
SSS,
|
|
|
|
|
2020-08-26 01:06:21 +00:00
|
|
|
// ---- Cave Story+ specific opcodes ----
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <ACHxxxx, triggers a Steam achievement.
|
2020-08-26 01:06:21 +00:00
|
|
|
ACH,
|
|
|
|
|
2020-09-25 17:14:52 +00:00
|
|
|
// ---- Cave Story+ (Switch) specific opcodes ----
|
2020-12-08 12:56:43 +00:00
|
|
|
/// <HM2, HMC for non-executor player.
|
2020-09-25 17:14:52 +00:00
|
|
|
HM2,
|
2020-12-08 12:56:43 +00:00
|
|
|
/// <2MVxxxx, Put another player near the player who executed the event.
|
|
|
|
/// 0000 - puts player on left side of executor player
|
|
|
|
/// 0001 - puts player on right side of executor player
|
|
|
|
/// other values - Unknown purpose for now
|
2020-09-25 17:14:52 +00:00
|
|
|
#[strum(serialize = "2MV")]
|
|
|
|
S2MV,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <INJxxxx:yyyy:zzzz, Jumps to event zzzz if amount of item xxxx equals yyyy
|
2020-09-25 17:14:52 +00:00
|
|
|
INJ,
|
2020-09-25 22:28:37 +00:00
|
|
|
/// <I+Nxxxx:yyyy, Adds item xxxx with maximum amount of yyyy
|
|
|
|
#[strum(serialize = "I+N")]
|
|
|
|
IpN,
|
|
|
|
/// <FF-xxxx:yyyy, Set flags in range xxxx-yyyy to false
|
|
|
|
#[strum(serialize = "FF-")]
|
|
|
|
FFm,
|
|
|
|
/// <PSHxxxx, Pushes text script state to stack and starts event xxxx
|
|
|
|
PSH,
|
|
|
|
/// <POP, Restores text script state from stack and resumes previous event.
|
|
|
|
POP,
|
2020-11-29 12:06:10 +00:00
|
|
|
/// <KEY related to player 2?
|
2020-09-29 00:43:55 +00:00
|
|
|
KE2,
|
2020-11-29 12:06:10 +00:00
|
|
|
/// <FRE related to player 2?
|
2020-09-29 00:43:55 +00:00
|
|
|
FR2,
|
2020-08-23 02:17:45 +00:00
|
|
|
// ---- Custom opcodes, for use by modders ----
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
|
2020-08-27 05:12:14 +00:00
|
|
|
bitfield! {
|
|
|
|
pub struct TextScriptFlags(u16);
|
|
|
|
impl Debug;
|
|
|
|
pub render, set_render: 0;
|
|
|
|
pub background_visible, set_background_visible: 1;
|
2020-10-03 00:06:02 +00:00
|
|
|
pub fast, set_fast: 4;
|
2020-08-27 05:12:14 +00:00
|
|
|
pub position_top, set_position_top: 5;
|
2020-10-03 00:06:02 +00:00
|
|
|
pub perma_fast, set_perma_fast: 6;
|
2020-08-27 05:12:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
|
|
|
#[repr(u8)]
|
2020-08-26 01:06:21 +00:00
|
|
|
pub enum TextScriptEncoding {
|
2020-08-27 05:12:14 +00:00
|
|
|
UTF8 = 0,
|
2020-08-26 01:06:21 +00:00
|
|
|
ShiftJIS,
|
|
|
|
}
|
|
|
|
|
2020-08-27 05:12:14 +00:00
|
|
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
|
|
|
#[repr(u8)]
|
|
|
|
pub enum TextScriptLine {
|
|
|
|
Line1 = 0,
|
|
|
|
Line2,
|
2020-08-27 23:34:28 +00:00
|
|
|
Line3,
|
2020-08-27 05:12:14 +00:00
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
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)]
|
2020-08-28 01:41:14 +00:00
|
|
|
#[repr(u8)]
|
2020-08-27 02:43:21 +00:00
|
|
|
pub enum TextScriptExecutionState {
|
2020-08-26 01:06:21 +00:00
|
|
|
Ended,
|
2020-08-27 02:43:21 +00:00
|
|
|
Running(u16, u32),
|
2020-08-27 05:12:14 +00:00
|
|
|
Msg(u16, u32, u32, 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),
|
2020-09-10 13:24:04 +00:00
|
|
|
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),
|
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,
|
2020-08-26 01:06:21 +00:00
|
|
|
}
|
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
pub struct TextScriptVM {
|
2020-09-29 00:43:55 +00:00
|
|
|
pub scripts: Scripts,
|
2020-08-27 02:43:21 +00:00
|
|
|
pub state: TextScriptExecutionState,
|
2020-09-25 22:28:37 +00:00
|
|
|
pub stack: Vec<TextScriptExecutionState>,
|
2020-08-27 05:12:14 +00:00
|
|
|
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.
|
2020-11-30 13:31:40 +00:00
|
|
|
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,
|
2021-01-16 13:51:52 +00:00
|
|
|
pub numbers: [u16; 4],
|
2020-08-27 05:12:14 +00:00
|
|
|
pub face: u16,
|
2020-09-05 02:56:29 +00:00
|
|
|
pub item: u16,
|
2020-08-27 05:12:14 +00:00
|
|
|
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-02-12 23:12:33 +00:00
|
|
|
prev_char: char,
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for TextScriptVM {
|
|
|
|
fn default() -> Self {
|
|
|
|
TextScriptVM::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-29 00:43:55 +00:00
|
|
|
pub struct Scripts {
|
|
|
|
/// Head.tsc - shared part of map scripts
|
2020-08-27 05:12:14 +00:00
|
|
|
pub global_script: TextScript,
|
2020-09-29 00:43:55 +00:00
|
|
|
/// <Map>.tsc - map script
|
2020-08-27 05:12:14 +00:00
|
|
|
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,
|
2020-08-27 05:12:14 +00:00
|
|
|
}
|
|
|
|
|
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 => {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2020-08-27 05:12:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
fn read_cur_varint(cursor: &mut Cursor<&Vec<u8>>) -> GameResult<i32> {
|
|
|
|
let mut result = 0u32;
|
|
|
|
|
|
|
|
for o in 0..5 {
|
|
|
|
let n = cursor.read_u8()?;
|
|
|
|
result |= (n as u32 & 0x7f) << (o * 7);
|
|
|
|
|
|
|
|
if n & 0x80 == 0 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(((result << 31) ^ (result >> 1)) as i32)
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TextScriptVM {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
2020-09-29 00:43:55 +00:00
|
|
|
scripts: Scripts {
|
2020-08-27 05:12:14 +00:00
|
|
|
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(),
|
2020-08-27 05:12:14 +00:00
|
|
|
},
|
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,
|
2020-11-30 13:31:40 +00:00
|
|
|
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,
|
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,
|
2020-08-27 05:12:14 +00:00
|
|
|
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-02-12 23:12:33 +00:00
|
|
|
prev_char: '\x00',
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_global_script(&mut self, script: TextScript) {
|
2020-08-27 05:12:14 +00:00
|
|
|
self.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) {
|
2020-08-27 05:12:14 +00:00
|
|
|
self.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) {
|
|
|
|
self.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) {
|
|
|
|
self.scripts.stage_select_script = script;
|
2020-09-10 10:25:40 +00:00
|
|
|
}
|
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
pub fn reset(&mut self) {
|
|
|
|
self.state = TextScriptExecutionState::Ended;
|
2020-08-27 05:12:14 +00:00
|
|
|
self.clear_text_box();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clear_text_box(&mut self) {
|
|
|
|
self.flags.0 = 0;
|
2020-08-27 02:43:21 +00:00
|
|
|
self.face = 0;
|
2020-09-09 22:07:36 +00:00
|
|
|
self.item = 0;
|
2020-08-27 05:12:14 +00:00
|
|
|
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();
|
|
|
|
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 {
|
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 => {
|
2020-09-25 12:55:28 +00:00
|
|
|
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);
|
2020-08-27 22:29:10 +00:00
|
|
|
state.textscript_vm.state = TextScriptVM::execute(event, ip, state, game_scene, ctx)?;
|
2020-08-27 05:12:14 +00:00
|
|
|
|
|
|
|
if state.textscript_vm.state == TextScriptExecutionState::Ended {
|
|
|
|
state.textscript_vm.reset();
|
|
|
|
}
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
2020-09-06 15:55:07 +00:00
|
|
|
TextScriptExecutionState::Msg(event, ip, remaining, counter) => {
|
|
|
|
if counter > 0 {
|
|
|
|
state.textscript_vm.state = TextScriptExecutionState::Msg(event, ip, remaining, counter - 1);
|
2020-08-27 05:12:14 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-09-29 20:19:47 +00:00
|
|
|
if let Some(bytecode) = state.textscript_vm.scripts.find_script(state.textscript_vm.mode, event) {
|
2020-08-27 05:12:14 +00:00
|
|
|
let mut cursor = Cursor::new(bytecode);
|
|
|
|
cursor.seek(SeekFrom::Start(ip as u64))?;
|
|
|
|
|
2020-09-06 15:55:07 +00:00
|
|
|
let chr = std::char::from_u32(read_cur_varint(&mut cursor)? as u32).unwrap_or('\u{fffd}');
|
2020-08-27 05:12:14 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2020-08-27 05:12:14 +00:00
|
|
|
'\n' => {
|
|
|
|
state.textscript_vm.line_1.clear();
|
2020-08-27 22:29:10 +00:00
|
|
|
state.textscript_vm.line_1.append(&mut state.textscript_vm.line_2);
|
2020-08-27 23:34:28 +00:00
|
|
|
state.textscript_vm.line_2.append(&mut state.textscript_vm.line_3);
|
2020-08-27 05:12:14 +00:00
|
|
|
}
|
|
|
|
'\r' => {}
|
|
|
|
_ if state.textscript_vm.current_line == TextScriptLine::Line1 => {
|
2021-02-12 23:12:33 +00:00
|
|
|
state.textscript_vm.prev_char = chr;
|
2020-08-27 05:12:14 +00:00
|
|
|
state.textscript_vm.line_1.push(chr);
|
2021-02-12 23:12:33 +00:00
|
|
|
|
|
|
|
let text_len = state.font.text_width(state.textscript_vm.line_1.iter().copied(), &state.constants);
|
|
|
|
if text_len >= 284.0 {
|
|
|
|
state.textscript_vm.current_line = TextScriptLine::Line2;
|
|
|
|
}
|
2020-08-27 05:12:14 +00:00
|
|
|
}
|
|
|
|
_ if state.textscript_vm.current_line == TextScriptLine::Line2 => {
|
2021-02-12 23:12:33 +00:00
|
|
|
state.textscript_vm.prev_char = chr;
|
2020-08-27 05:12:14 +00:00
|
|
|
state.textscript_vm.line_2.push(chr);
|
2021-02-12 23:12:33 +00:00
|
|
|
|
|
|
|
let text_len = state.font.text_width(state.textscript_vm.line_2.iter().copied(), &state.constants);
|
|
|
|
if text_len >= 284.0 {
|
|
|
|
state.textscript_vm.current_line = TextScriptLine::Line3;
|
|
|
|
}
|
2020-08-27 05:12:14 +00:00
|
|
|
}
|
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
|
|
|
|
|
|
|
let text_len = state.font.text_width(state.textscript_vm.line_3.iter().copied(), &state.constants);
|
|
|
|
if text_len >= 284.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);
|
|
|
|
}
|
2020-08-27 23:34:28 +00:00
|
|
|
}
|
2020-08-27 05:12:14 +00:00
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
2020-09-06 15:55:07 +00:00
|
|
|
if remaining > 1 {
|
2021-01-02 02:39:50 +00:00
|
|
|
let ticks = if state.textscript_vm.flags.fast()
|
2021-01-16 13:51:52 +00:00
|
|
|
|| game_scene.player1.controller.skip()
|
2021-02-12 10:05:28 +00:00
|
|
|
|| game_scene.player2.controller.skip()
|
|
|
|
{
|
2020-10-03 00:06:02 +00:00
|
|
|
0
|
2020-11-28 19:25:51 +00:00
|
|
|
} else if game_scene.player1.controller.jump()
|
|
|
|
|| game_scene.player1.controller.shoot()
|
|
|
|
|| game_scene.player2.controller.jump()
|
2021-02-12 10:05:28 +00:00
|
|
|
|| game_scene.player2.controller.shoot()
|
|
|
|
{
|
2020-10-03 00:06:02 +00:00
|
|
|
1
|
|
|
|
} else {
|
|
|
|
4
|
|
|
|
};
|
|
|
|
|
|
|
|
if ticks > 0 {
|
|
|
|
state.sound_manager.play_sfx(2);
|
|
|
|
}
|
|
|
|
|
2021-02-12 10:05:28 +00:00
|
|
|
state.textscript_vm.state =
|
|
|
|
TextScriptExecutionState::Msg(event, cursor.position() as u32, remaining - 1, ticks);
|
2020-08-27 05:12:14 +00:00
|
|
|
} else {
|
2021-02-12 10:05:28 +00:00
|
|
|
state.textscript_vm.state =
|
|
|
|
TextScriptExecutionState::Running(event, cursor.position() as u32);
|
2020-08-27 05:12:14 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-08-27 02:43:21 +00:00
|
|
|
state.textscript_vm.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TextScriptExecutionState::WaitTicks(event, ip, ticks) => {
|
|
|
|
if ticks == 0 {
|
|
|
|
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
|
|
|
|
} else {
|
|
|
|
state.textscript_vm.state = TextScriptExecutionState::WaitTicks(event, ip, ticks - 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
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;
|
|
|
|
}
|
|
|
|
|
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()
|
|
|
|
{
|
2020-09-20 15:27:31 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
if game_scene.player1.controller.trigger_jump() || game_scene.player2.controller.trigger_jump() {
|
2020-09-20 15:27:31 +00:00
|
|
|
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 => {
|
|
|
|
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;
|
|
|
|
}
|
2020-09-10 13:24:04 +00:00
|
|
|
TextScriptExecutionState::WaitStanding(event, ip) => {
|
2020-11-28 19:25:51 +00:00
|
|
|
if game_scene.player1.flags.hit_bottom_wall() {
|
2020-09-10 13:24:04 +00:00
|
|
|
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);
|
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
if game_scene.player1.controller.trigger_jump()
|
|
|
|
|| game_scene.player1.controller.trigger_shoot()
|
2021-01-02 02:39:50 +00:00
|
|
|
|| game_scene.player1.controller.skip()
|
2020-11-28 19:25:51 +00:00
|
|
|
|| 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
|
|
|
|| game_scene.player2.controller.skip()
|
|
|
|
{
|
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;
|
|
|
|
}
|
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.start_intro(ctx)?;
|
|
|
|
break;
|
|
|
|
}
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-02-12 10:05:28 +00:00
|
|
|
pub fn execute(
|
|
|
|
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;
|
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
let state_ref = state as *mut SharedGameState;
|
|
|
|
let scripts = unsafe { &(*state_ref).textscript_vm.scripts };
|
|
|
|
|
|
|
|
if let Some(bytecode) = scripts.find_script(state.textscript_vm.mode, event) {
|
2020-08-27 02:43:21 +00:00
|
|
|
let mut cursor = Cursor::new(bytecode);
|
|
|
|
cursor.seek(SeekFrom::Start(ip as u64))?;
|
|
|
|
|
2021-02-12 10:05:28 +00:00
|
|
|
let op_maybe: Option<OpCode> =
|
|
|
|
FromPrimitive::from_i32(read_cur_varint(&mut cursor).unwrap_or_else(|_| OpCode::END as i32));
|
2020-08-27 02:43:21 +00:00
|
|
|
|
|
|
|
if let Some(op) = op_maybe {
|
2020-08-27 05:12:14 +00:00
|
|
|
println!("opcode: {:?}", op);
|
2020-08-27 02:43:21 +00:00
|
|
|
match op {
|
|
|
|
OpCode::_NOP => {
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::_UNI => {}
|
|
|
|
OpCode::_STR => {
|
2020-09-06 15:55:07 +00:00
|
|
|
let mut len = read_cur_varint(&mut cursor)? as u32;
|
2020-08-27 05:12:14 +00:00
|
|
|
if state.textscript_vm.flags.render() {
|
2021-02-12 23:12:33 +00:00
|
|
|
state.textscript_vm.prev_char = '\x00';
|
2020-08-27 05:12:14 +00:00
|
|
|
exec_state = TextScriptExecutionState::Msg(event, cursor.position() as u32, len, 4);
|
|
|
|
} else {
|
2020-09-06 15:55:07 +00:00
|
|
|
while len > 0 {
|
|
|
|
len -= 1;
|
|
|
|
let _ = read_cur_varint(&mut cursor)?;
|
|
|
|
}
|
2020-08-27 05:12:14 +00:00
|
|
|
// simply skip the text if we aren't in message mode.
|
2020-09-06 15:55:07 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
2020-08-27 05:12:14 +00:00
|
|
|
}
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
2020-09-25 12:55:28 +00:00
|
|
|
OpCode::_END => {
|
|
|
|
exec_state = TextScriptExecutionState::Ended;
|
|
|
|
}
|
|
|
|
OpCode::END => {
|
|
|
|
state.control_flags.set_tick_world(true);
|
2020-08-27 05:12:14 +00:00
|
|
|
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);
|
2020-09-25 22:28:37 +00:00
|
|
|
state.textscript_vm.stack.clear();
|
2020-09-06 00:37:42 +00:00
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.cond.set_interacted(false);
|
2020-11-29 12:06:10 +00:00
|
|
|
game_scene.player2.cond.set_interacted(false);
|
2020-09-09 13:06:11 +00:00
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
exec_state = TextScriptExecutionState::Ended;
|
|
|
|
}
|
2020-09-29 20:19:47 +00:00
|
|
|
OpCode::SLP => {
|
|
|
|
state.textscript_vm.set_mode(ScriptMode::StageSelect);
|
|
|
|
|
2021-02-12 10:05:28 +00:00
|
|
|
let event_num = if let Some(slot) =
|
|
|
|
state.teleporter_slots.get(game_scene.stage_select.current_teleport_slot as usize)
|
|
|
|
{
|
2020-09-29 20:19:47 +00:00
|
|
|
1000 + slot.0
|
|
|
|
} else {
|
|
|
|
1000
|
|
|
|
};
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
|
|
|
}
|
|
|
|
OpCode::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));
|
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-08-27 05:12:14 +00:00
|
|
|
OpCode::PRI => {
|
2020-09-25 12:55:28 +00:00
|
|
|
state.control_flags.set_tick_world(false);
|
2020-08-27 05:12:14 +00:00
|
|
|
state.control_flags.set_control_enabled(false);
|
2020-08-27 02:43:21 +00:00
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.shock_counter = 0;
|
2020-08-27 05:12:14 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::KEY => {
|
2020-09-25 12:55:28 +00:00
|
|
|
state.control_flags.set_tick_world(true);
|
2020-08-27 05:12:14 +00:00
|
|
|
state.control_flags.set_control_enabled(false);
|
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.up = false;
|
|
|
|
game_scene.player1.shock_counter = 0;
|
2020-08-27 05:12:14 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::FRE => {
|
2020-09-25 12:55:28 +00:00
|
|
|
state.control_flags.set_tick_world(true);
|
2020-08-27 05:12:14 +00:00
|
|
|
state.control_flags.set_control_enabled(true);
|
2020-08-28 02:12:13 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::MYD => {
|
|
|
|
let new_direction = read_cur_varint(&mut cursor)? as usize;
|
|
|
|
if let Some(direction) = Direction::from_int(new_direction) {
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.direction = direction;
|
2020-11-29 12:06:10 +00:00
|
|
|
game_scene.player2.direction = direction;
|
2020-08-28 02:12:13 +00:00
|
|
|
}
|
|
|
|
|
2020-11-29 12:06:10 +00:00
|
|
|
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);
|
|
|
|
}
|
2020-09-09 13:06:11 +00:00
|
|
|
OpCode::MYB => {
|
|
|
|
let new_direction = read_cur_varint(&mut cursor)? as usize;
|
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.vel_y = -0x200;
|
2020-11-29 12:06:10 +00:00
|
|
|
game_scene.player2.vel_y = -0x200;
|
2020-09-09 13:06:11 +00:00
|
|
|
|
2020-09-30 03:11:25 +00:00
|
|
|
if let Some(direction) = Direction::from_int_facing(new_direction) {
|
2020-09-09 13:06:11 +00:00
|
|
|
match direction {
|
2020-11-29 12:06:10 +00:00
|
|
|
Direction::Left => {
|
|
|
|
game_scene.player1.vel_x = 0x200;
|
|
|
|
game_scene.player2.vel_x = 0x200;
|
2020-12-07 19:20:19 +00:00
|
|
|
}
|
2020-11-29 12:06:10 +00:00
|
|
|
Direction::Up => {
|
|
|
|
game_scene.player1.vel_y = -0x200;
|
|
|
|
game_scene.player2.vel_y = -0x200;
|
2020-12-07 19:20:19 +00:00
|
|
|
}
|
2020-11-29 12:06:10 +00:00
|
|
|
Direction::Right => {
|
|
|
|
game_scene.player1.vel_x = -0x200;
|
|
|
|
game_scene.player2.vel_x = -0x200;
|
2020-12-07 19:20:19 +00:00
|
|
|
}
|
2020-11-29 12:06:10 +00:00
|
|
|
Direction::Bottom => {
|
|
|
|
game_scene.player1.vel_y = 0x200;
|
|
|
|
game_scene.player2.vel_y = 0x200;
|
2020-12-07 19:20:19 +00:00
|
|
|
}
|
2020-09-30 03:11:25 +00:00
|
|
|
Direction::FacingPlayer => {
|
|
|
|
// todo npc direction dependent bump
|
|
|
|
}
|
2020-09-09 13:06:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-08-28 02:12:13 +00:00
|
|
|
OpCode::SMC => {
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.cond.set_hidden(false);
|
2020-11-30 13:31:40 +00:00
|
|
|
game_scene.player2.cond.set_hidden(false);
|
2020-08-28 02:12:13 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::HMC => {
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.cond.set_hidden(true);
|
2020-11-30 13:31:40 +00:00
|
|
|
game_scene.player2.cond.set_hidden(true);
|
2020-08-28 02:12:13 +00:00
|
|
|
|
2020-08-27 05:12:14 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::WAI => {
|
2020-09-06 00:37:42 +00:00
|
|
|
let ticks = read_cur_varint(&mut cursor)? as u16;
|
2020-08-28 02:12:13 +00:00
|
|
|
|
2020-08-27 05:12:14 +00:00
|
|
|
exec_state = TextScriptExecutionState::WaitTicks(event, cursor.position() as u32, ticks);
|
|
|
|
}
|
2020-09-10 13:24:04 +00:00
|
|
|
OpCode::WAS => {
|
|
|
|
exec_state = TextScriptExecutionState::WaitStanding(event, cursor.position() as u32);
|
|
|
|
}
|
2020-08-27 02:43:21 +00:00
|
|
|
OpCode::NOD => {
|
2021-02-12 23:12:33 +00:00
|
|
|
exec_state = TextScriptExecutionState::WaitInput(event, cursor.position() as u32, 0);
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
|
|
|
OpCode::FLp | OpCode::FLm => {
|
|
|
|
let flag_num = read_cur_varint(&mut cursor)? as usize;
|
|
|
|
state.game_flags.set(flag_num, op == OpCode::FLp);
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-25 22:28:37 +00:00
|
|
|
OpCode::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 {
|
|
|
|
state.game_flags.set(flag, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-08-27 02:43:21 +00:00
|
|
|
OpCode::FLJ => {
|
|
|
|
let flag_num = read_cur_varint(&mut cursor)? as usize;
|
|
|
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
2021-02-10 20:14:09 +00:00
|
|
|
if state.get_flag(flag_num) {
|
2020-08-27 02:43:21 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
2020-08-27 05:12:14 +00:00
|
|
|
} else {
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-10 13:24:04 +00:00
|
|
|
OpCode::ITJ => {
|
|
|
|
let item_id = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
if game_scene.inventory_player1.has_item(item_id) {
|
2020-09-10 13:24:04 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
|
|
|
} else {
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
}
|
2020-09-25 22:28:37 +00:00
|
|
|
OpCode::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;
|
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
if game_scene.inventory_player1.has_item_amount(item_id, Ordering::Equal, amount) {
|
2020-09-25 22:28:37 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
|
|
|
} else {
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
}
|
2020-09-10 13:24:04 +00:00
|
|
|
OpCode::AMJ => {
|
2020-09-12 00:42:44 +00:00
|
|
|
let weapon = read_cur_varint(&mut cursor)? as u8;
|
2020-09-10 13:24:04 +00:00
|
|
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
2020-09-12 00:42:44 +00:00
|
|
|
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon);
|
2020-09-10 13:24:04 +00:00
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
if weapon_type.is_some() && game_scene.inventory_player1.has_weapon(weapon_type.unwrap()) {
|
2020-09-10 13:24:04 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
|
|
|
} else {
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
OpCode::NCJ => {
|
2020-11-04 15:37:00 +00:00
|
|
|
let npc_type = read_cur_varint(&mut cursor)? as u16;
|
2020-09-10 13:24:04 +00:00
|
|
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
if game_scene.npc_list.is_alive_by_type(npc_type) {
|
2020-09-10 13:24:04 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
|
|
|
} else {
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
OpCode::ECJ => {
|
|
|
|
let npc_event_num = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
if game_scene.npc_list.is_alive_by_event(npc_event_num) {
|
2020-09-10 13:24:04 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
|
|
|
} else {
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
}
|
2020-08-27 02:43:21 +00:00
|
|
|
OpCode::EVE => {
|
|
|
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
2020-08-28 02:12:13 +00:00
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
|
|
|
}
|
2020-09-25 22:28:37 +00:00
|
|
|
OpCode::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);
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
|
|
|
}
|
|
|
|
OpCode::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;
|
|
|
|
}
|
|
|
|
}
|
2020-08-27 02:43:21 +00:00
|
|
|
OpCode::MM0 => {
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.vel_x = 0;
|
2020-08-28 02:12:13 +00:00
|
|
|
|
2020-08-27 05:12:14 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
2020-11-01 19:08:52 +00:00
|
|
|
OpCode::SMP => {
|
|
|
|
let pos_x = read_cur_varint(&mut cursor)? as usize;
|
|
|
|
let pos_y = read_cur_varint(&mut cursor)? as usize;
|
2020-11-01 19:39:57 +00:00
|
|
|
|
2020-11-01 19:08:52 +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));
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-08-27 02:43:21 +00:00
|
|
|
OpCode::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;
|
|
|
|
|
2020-09-30 03:11:25 +00:00
|
|
|
if game_scene.stage.change_tile(pos_x, pos_y, tile_type) {
|
2020-12-25 22:39:41 +00:00
|
|
|
let mut npc = NPC::create(4, &state.npc_table);
|
2020-09-30 03:11:25 +00:00
|
|
|
npc.cond.set_alive(true);
|
2021-01-01 01:46:01 +00:00
|
|
|
npc.x = pos_x as i32 * 16 * 0x200;
|
|
|
|
npc.y = pos_y as i32 * 16 * 0x200;
|
2020-09-30 03:11:25 +00:00
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
game_scene.npc_list.spawn(0x100, npc.clone())?;
|
|
|
|
game_scene.npc_list.spawn(0x100, npc)?;
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::MLp => {
|
2020-09-16 13:21:30 +00:00
|
|
|
let life = read_cur_varint(&mut cursor)? as u16;
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.life += life;
|
|
|
|
game_scene.player1.max_life += life;
|
2020-11-29 12:06:10 +00:00
|
|
|
game_scene.player2.life += life;
|
|
|
|
game_scene.player2.max_life += life;
|
2020-08-28 02:12:13 +00:00
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-08-27 05:12:14 +00:00
|
|
|
OpCode::FAC => {
|
|
|
|
let face = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
state.textscript_vm.face = face;
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-08-27 22:29:10 +00:00
|
|
|
OpCode::CLR => {
|
|
|
|
state.textscript_vm.current_line = TextScriptLine::Line1;
|
|
|
|
state.textscript_vm.line_1.clear();
|
|
|
|
state.textscript_vm.line_2.clear();
|
2020-08-27 23:34:28 +00:00
|
|
|
state.textscript_vm.line_3.clear();
|
2020-08-27 22:29:10 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-08-28 02:24:18 +00:00
|
|
|
OpCode::MSG | OpCode::MS2 | OpCode::MS3 => {
|
2020-08-27 05:12:14 +00:00
|
|
|
state.textscript_vm.current_line = TextScriptLine::Line1;
|
|
|
|
state.textscript_vm.line_1.clear();
|
|
|
|
state.textscript_vm.line_2.clear();
|
2020-08-27 23:34:28 +00:00
|
|
|
state.textscript_vm.line_3.clear();
|
2020-08-27 05:12:14 +00:00
|
|
|
state.textscript_vm.flags.set_render(true);
|
2020-08-28 02:24:18 +00:00
|
|
|
state.textscript_vm.flags.set_background_visible(op != OpCode::MS2);
|
2020-10-03 00:06:02 +00:00
|
|
|
state.textscript_vm.flags.set_fast(state.textscript_vm.flags.perma_fast());
|
2020-08-28 02:24:18 +00:00
|
|
|
state.textscript_vm.flags.set_position_top(op != OpCode::MSG);
|
2020-09-09 22:07:36 +00:00
|
|
|
if op == OpCode::MS2 {
|
|
|
|
state.textscript_vm.face = 0;
|
|
|
|
}
|
2020-08-27 05:12:14 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-10-03 00:06:02 +00:00
|
|
|
OpCode::SAT | OpCode::CAT => {
|
|
|
|
state.textscript_vm.flags.set_perma_fast(true);
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
|
|
|
|
OpCode::TUR => {
|
|
|
|
state.textscript_vm.flags.set_fast(true);
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-08-28 01:41:14 +00:00
|
|
|
OpCode::CLO => {
|
|
|
|
state.textscript_vm.flags.set_render(false);
|
|
|
|
state.textscript_vm.flags.set_background_visible(false);
|
2020-10-03 00:06:02 +00:00
|
|
|
state.textscript_vm.flags.set_fast(false);
|
2020-08-28 01:41:14 +00:00
|
|
|
state.textscript_vm.flags.set_position_top(false);
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-05 02:56:29 +00:00
|
|
|
OpCode::YNJ => {
|
|
|
|
let event_no = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
2020-09-20 15:27:31 +00:00
|
|
|
state.sound_manager.play_sfx(5);
|
|
|
|
|
2021-02-12 10:05:28 +00:00
|
|
|
exec_state = TextScriptExecutionState::WaitConfirmation(
|
|
|
|
event,
|
|
|
|
cursor.position() as u32,
|
|
|
|
event_no,
|
|
|
|
16,
|
|
|
|
ConfirmSelection::Yes,
|
|
|
|
);
|
2020-09-05 02:56:29 +00:00
|
|
|
}
|
2021-01-16 13:51:52 +00:00
|
|
|
OpCode::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_vec();
|
|
|
|
|
|
|
|
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),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-06 00:37:42 +00:00
|
|
|
OpCode::GIT => {
|
|
|
|
let item = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
state.textscript_vm.item = item;
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-08-27 22:29:10 +00:00
|
|
|
OpCode::TRA => {
|
|
|
|
let map_id = read_cur_varint(&mut cursor)? as usize;
|
|
|
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
2021-01-01 01:46:01 +00:00
|
|
|
let pos_x = read_cur_varint(&mut cursor)? as i32 * 16 * 0x200;
|
|
|
|
let pos_y = read_cur_varint(&mut cursor)? as i32 * 16 * 0x200;
|
2020-08-27 22:29:10 +00:00
|
|
|
|
|
|
|
let mut new_scene = GameScene::new(state, ctx, map_id)?;
|
2020-10-31 13:40:07 +00:00
|
|
|
new_scene.intro_mode = game_scene.intro_mode;
|
2020-11-28 19:25:51 +00:00
|
|
|
new_scene.inventory_player1 = game_scene.inventory_player1.clone();
|
2020-11-29 12:06:10 +00:00
|
|
|
new_scene.inventory_player2 = game_scene.inventory_player2.clone();
|
2020-11-28 19:25:51 +00:00
|
|
|
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;
|
2020-11-29 12:06:10 +00:00
|
|
|
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;
|
2020-08-27 02:43:21 +00:00
|
|
|
|
2020-09-25 12:55:28 +00:00
|
|
|
state.control_flags.set_tick_world(true);
|
2020-09-09 22:07:36 +00:00
|
|
|
state.textscript_vm.flags.0 = 0;
|
|
|
|
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();
|
2020-08-27 22:29:10 +00:00
|
|
|
state.textscript_vm.suspend = true;
|
|
|
|
state.next_scene = Some(Box::new(new_scene));
|
2020-08-28 02:12:13 +00:00
|
|
|
|
2020-09-06 15:55:07 +00:00
|
|
|
log::info!("Transitioning to stage {}, with script #{:04}", map_id, event_num);
|
2020-08-27 22:29:10 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
|
|
|
}
|
2020-09-03 12:06:12 +00:00
|
|
|
OpCode::MOV => {
|
2021-01-01 01:46:01 +00:00
|
|
|
let pos_x = read_cur_varint(&mut cursor)? as i32 * 16 * 0x200;
|
|
|
|
let pos_y = read_cur_varint(&mut cursor)? as i32 * 16 * 0x200;
|
2020-09-03 12:06:12 +00:00
|
|
|
|
2020-11-30 13:31:40 +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;
|
|
|
|
}
|
2020-09-03 12:06:12 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-11-29 12:06:10 +00:00
|
|
|
OpCode::S2MV => {
|
|
|
|
let param = read_cur_varint(&mut cursor)? as usize;
|
|
|
|
|
2020-11-30 13:31:40 +00:00
|
|
|
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-11-29 12:06:10 +00:00
|
|
|
match param {
|
|
|
|
0 | 1 => {
|
2020-11-30 13:31:40 +00:00
|
|
|
partner.vel_x = 0;
|
|
|
|
partner.vel_y = 0;
|
|
|
|
partner.x = executor.x + if param == 0 { -16 * 0x200 } else { 16 * 0x200 };
|
|
|
|
partner.y = executor.y;
|
2020-11-29 12:06:10 +00:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
log::warn!("stub: <2MV unknown param");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-10 13:24:04 +00:00
|
|
|
OpCode::UNI => {
|
|
|
|
let control_mode = read_cur_varint(&mut cursor)? as u8;
|
|
|
|
|
|
|
|
let mode: Option<ControlMode> = FromPrimitive::from_u8(control_mode);
|
|
|
|
if let Some(mode) = mode {
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.control_mode = mode;
|
2020-09-10 13:24:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-08-28 01:41:14 +00:00
|
|
|
OpCode::FAI => {
|
|
|
|
let fade_type = read_cur_varint(&mut cursor)? as usize;
|
2020-09-10 13:24:04 +00:00
|
|
|
|
2020-08-28 01:41:14 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
OpCode::FAO => {
|
|
|
|
let fade_type = read_cur_varint(&mut cursor)? as usize;
|
2020-09-10 13:24:04 +00:00
|
|
|
|
2020-08-28 01:41:14 +00:00
|
|
|
if let Some(direction) = FadeDirection::from_int(fade_type) {
|
2020-09-05 01:14:58 +00:00
|
|
|
state.fade_state = FadeState::FadeOut(-15, direction.opposite());
|
2020-08-28 01:41:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::WaitFade(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-03 12:06:12 +00:00
|
|
|
OpCode::QUA => {
|
|
|
|
let count = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
|
|
|
state.quake_counter = count;
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-04 23:47:09 +00:00
|
|
|
OpCode::MNA => {
|
|
|
|
game_scene.display_map_name(160);
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-02 22:57:54 +00:00
|
|
|
OpCode::CMU => {
|
|
|
|
let song_id = read_cur_varint(&mut cursor)? as usize;
|
2021-02-12 10:05:28 +00:00
|
|
|
state.sound_manager.play_song(song_id, &state.constants, &state.settings, ctx)?;
|
2020-09-03 11:48:56 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::FMU => {
|
2021-02-12 10:05:28 +00:00
|
|
|
state.sound_manager.play_song(0, &state.constants, &state.settings, ctx)?;
|
2020-09-03 11:48:56 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::RMU => {
|
|
|
|
state.sound_manager.restore_state()?;
|
|
|
|
|
2020-09-02 22:57:54 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-16 19:55:32 +00:00
|
|
|
OpCode::SOU => {
|
|
|
|
let sound = read_cur_varint(&mut cursor)? as u8;
|
|
|
|
|
|
|
|
state.sound_manager.play_sfx(sound);
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-05 02:09:52 +00:00
|
|
|
OpCode::DNP => {
|
|
|
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
game_scene.npc_list.remove_by_event(event_num, state);
|
2020-09-05 02:09:52 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-10-31 14:32:13 +00:00
|
|
|
OpCode::DNA => {
|
2020-12-25 22:39:41 +00:00
|
|
|
let npc_remove_type = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
|
|
|
game_scene.npc_list.remove_by_type(npc_remove_type, state);
|
2020-10-31 14:32:13 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-11-01 19:39:57 +00:00
|
|
|
OpCode::FOB => {
|
|
|
|
let part_id = read_cur_varint(&mut cursor)? as u16;
|
2021-01-01 01:46:01 +00:00
|
|
|
let ticks = read_cur_varint(&mut cursor)? as i32;
|
2020-11-01 19:39:57 +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);
|
|
|
|
}
|
2020-09-06 00:37:42 +00:00
|
|
|
OpCode::FOM => {
|
2021-01-01 01:46:01 +00:00
|
|
|
let ticks = read_cur_varint(&mut cursor)? as i32;
|
2020-09-06 00:37:42 +00:00
|
|
|
game_scene.frame.wait = ticks;
|
2020-11-01 19:39:57 +00:00
|
|
|
game_scene.frame.update_target = UpdateTarget::Player;
|
2020-09-06 00:37:42 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::FON => {
|
|
|
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
2021-01-01 01:46:01 +00:00
|
|
|
let ticks = read_cur_varint(&mut cursor)? as i32;
|
2020-09-06 00:37:42 +00:00
|
|
|
game_scene.frame.wait = ticks;
|
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
for npc in game_scene.npc_list.iter() {
|
2020-11-04 23:25:18 +00:00
|
|
|
if event_num == npc.event_num {
|
|
|
|
game_scene.frame.update_target = UpdateTarget::NPC(npc.id);
|
|
|
|
break;
|
2020-09-06 00:37:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-11-02 14:01:30 +00:00
|
|
|
OpCode::BSL => {
|
|
|
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
|
|
|
if event_num == 0 {
|
2020-12-25 22:39:41 +00:00
|
|
|
game_scene.boss_life_bar.set_boss_target(&game_scene.boss);
|
2020-11-02 14:01:30 +00:00
|
|
|
} else {
|
2020-12-25 22:39:41 +00:00
|
|
|
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);
|
2020-11-04 23:25:18 +00:00
|
|
|
break;
|
2020-11-02 14:01:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-11-02 02:20:16 +00:00
|
|
|
OpCode::BOA => {
|
|
|
|
let action_num = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
game_scene.boss.parts[0].action_num = action_num;
|
2020-11-02 02:20:16 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-06 00:37:42 +00:00
|
|
|
OpCode::ANP => {
|
|
|
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
let action_num = read_cur_varint(&mut cursor)? as u16;
|
2020-11-01 19:05:29 +00:00
|
|
|
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
|
|
|
|
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
|
2020-09-06 00:37:42 +00:00
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
for npc in game_scene.npc_list.iter_alive() {
|
|
|
|
if npc.event_num == event_num {
|
2020-11-04 23:25:18 +00:00
|
|
|
npc.action_num = action_num;
|
|
|
|
npc.tsc_direction = tsc_direction as u16;
|
2020-09-06 00:37:42 +00:00
|
|
|
|
2020-11-04 23:25:18 +00:00
|
|
|
if direction == Direction::FacingPlayer {
|
2021-02-12 10:05:28 +00:00
|
|
|
npc.direction =
|
|
|
|
if game_scene.player1.x < npc.x { Direction::Right } else { Direction::Left };
|
2020-11-04 23:25:18 +00:00
|
|
|
} else {
|
|
|
|
npc.direction = direction;
|
2020-09-06 00:37:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-09 22:21:27 +00:00
|
|
|
OpCode::CNP | OpCode::INP => {
|
2020-09-06 00:37:42 +00:00
|
|
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
let new_type = read_cur_varint(&mut cursor)? as u16;
|
2020-11-01 19:05:29 +00:00
|
|
|
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
|
|
|
|
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
|
2020-09-06 00:37:42 +00:00
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
for npc in game_scene.npc_list.iter_alive() {
|
|
|
|
if npc.event_num == event_num {
|
2020-11-04 23:25:18 +00:00
|
|
|
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 == OpCode::INP {
|
|
|
|
npc.npc_flags.set_event_when_touched(true);
|
|
|
|
}
|
2020-09-06 00:37:42 +00:00
|
|
|
|
2020-11-04 23:25:18 +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;
|
2021-01-18 19:31:35 +00:00
|
|
|
npc.spritesheet_id = entry.spritesheet_id as u16;
|
2020-11-04 23:25:18 +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 {
|
2021-02-12 10:05:28 +00:00
|
|
|
npc.direction =
|
|
|
|
if game_scene.player1.x < npc.x { Direction::Right } else { Direction::Left };
|
2020-11-04 23:25:18 +00:00
|
|
|
} else {
|
|
|
|
npc.direction = direction;
|
2020-09-06 00:37:42 +00:00
|
|
|
}
|
2020-11-04 23:25:18 +00:00
|
|
|
|
2021-02-12 10:05:28 +00:00
|
|
|
npc.tick(
|
|
|
|
state,
|
|
|
|
(
|
|
|
|
[&mut game_scene.player1, &mut game_scene.player2],
|
|
|
|
&game_scene.npc_list,
|
|
|
|
&mut game_scene.stage,
|
|
|
|
&game_scene.bullet_manager,
|
|
|
|
),
|
|
|
|
)?;
|
2020-09-06 00:37:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-10 13:24:04 +00:00
|
|
|
OpCode::MNP => {
|
|
|
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
2021-01-01 01:46:01 +00:00
|
|
|
let x = read_cur_varint(&mut cursor)? as i32;
|
|
|
|
let y = read_cur_varint(&mut cursor)? as i32;
|
2020-11-01 19:05:29 +00:00
|
|
|
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
|
|
|
|
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
|
2020-09-10 13:24:04 +00:00
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
for npc in game_scene.npc_list.iter_alive() {
|
|
|
|
if npc.event_num == event_num {
|
2020-11-04 23:25:18 +00:00
|
|
|
npc.x = x * 16 * 0x200;
|
|
|
|
npc.y = y * 16 * 0x200;
|
|
|
|
npc.tsc_direction = tsc_direction as u16;
|
|
|
|
|
|
|
|
if direction == Direction::FacingPlayer {
|
2021-02-12 10:05:28 +00:00
|
|
|
npc.direction =
|
|
|
|
if game_scene.player1.x < npc.x { Direction::Right } else { Direction::Left };
|
2020-11-04 23:25:18 +00:00
|
|
|
} else {
|
|
|
|
npc.direction = direction;
|
2020-09-10 13:24:04 +00:00
|
|
|
}
|
2020-11-04 23:25:18 +00:00
|
|
|
|
|
|
|
break;
|
2020-09-10 13:24:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-10-31 14:32:13 +00:00
|
|
|
OpCode::SNP => {
|
|
|
|
let npc_type = read_cur_varint(&mut cursor)? as u16;
|
2021-01-01 01:46:01 +00:00
|
|
|
let x = read_cur_varint(&mut cursor)? as i32;
|
|
|
|
let y = read_cur_varint(&mut cursor)? as i32;
|
2020-11-01 19:05:29 +00:00
|
|
|
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
|
|
|
|
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
|
2020-10-31 14:32:13 +00:00
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
let mut npc = NPC::create(npc_type, &state.npc_table);
|
2020-10-31 14:32:13 +00:00
|
|
|
npc.cond.set_alive(true);
|
|
|
|
npc.x = x * 16 * 0x200;
|
|
|
|
npc.y = y * 16 * 0x200;
|
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 {
|
2021-02-12 10:05:28 +00:00
|
|
|
npc.direction =
|
|
|
|
if game_scene.player1.x < npc.x { Direction::Right } else { Direction::Left };
|
2020-10-31 14:32:13 +00:00
|
|
|
} else {
|
|
|
|
npc.direction = direction;
|
|
|
|
}
|
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
game_scene.npc_list.spawn(0x100, npc)?;
|
2020-10-31 14:32:13 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-09 22:21:27 +00:00
|
|
|
OpCode::LIp => {
|
2020-09-16 13:21:30 +00:00
|
|
|
let life = read_cur_varint(&mut cursor)? as u16;
|
2020-09-09 22:21:27 +00:00
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.life = clamp(game_scene.player1.life + life, 0, game_scene.player1.max_life);
|
2020-11-30 13:31:40 +00:00
|
|
|
game_scene.player2.life = clamp(game_scene.player2.life + life, 0, game_scene.player2.max_life);
|
2020-09-06 00:37:42 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-10 13:24:04 +00:00
|
|
|
OpCode::ITp => {
|
|
|
|
let item_id = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
if !game_scene.inventory_player1.has_item(item_id) {
|
|
|
|
game_scene.inventory_player1.add_item(item_id);
|
2020-09-25 22:28:37 +00:00
|
|
|
}
|
|
|
|
|
2020-11-30 13:31:40 +00:00
|
|
|
if !game_scene.inventory_player2.has_item(item_id) {
|
|
|
|
game_scene.inventory_player2.add_item(item_id);
|
|
|
|
}
|
|
|
|
|
2020-09-25 22:28:37 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::IpN => {
|
|
|
|
let item_id = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
let amount = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
if game_scene.inventory_player1.has_item_amount(item_id, Ordering::Less, amount) {
|
|
|
|
game_scene.inventory_player1.add_item(item_id);
|
2020-09-25 22:28:37 +00:00
|
|
|
}
|
2020-09-10 13:24:04 +00:00
|
|
|
|
2020-11-30 13:31:40 +00:00
|
|
|
if game_scene.inventory_player2.has_item_amount(item_id, Ordering::Less, amount) {
|
|
|
|
game_scene.inventory_player2.add_item(item_id);
|
|
|
|
}
|
|
|
|
|
2020-09-10 13:24:04 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::ITm => {
|
|
|
|
let item_id = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.inventory_player1.consume_item(item_id);
|
2020-11-30 13:31:40 +00:00
|
|
|
game_scene.inventory_player2.consume_item(item_id);
|
2020-09-10 13:24:04 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::AMp => {
|
2020-09-12 00:42:44 +00:00
|
|
|
let weapon_id = read_cur_varint(&mut cursor)? as u8;
|
2020-09-10 13:24:04 +00:00
|
|
|
let max_ammo = read_cur_varint(&mut cursor)? as u16;
|
2020-09-12 00:42:44 +00:00
|
|
|
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon_id);
|
2020-09-10 13:24:04 +00:00
|
|
|
|
2021-01-16 13:51:52 +00:00
|
|
|
state.textscript_vm.numbers[0] = max_ammo;
|
|
|
|
|
2020-09-12 00:42:44 +00:00
|
|
|
if let Some(wtype) = weapon_type {
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.inventory_player1.add_weapon(wtype, max_ammo);
|
2020-11-30 13:31:40 +00:00
|
|
|
game_scene.inventory_player2.add_weapon(wtype, max_ammo);
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
2020-09-10 13:24:04 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::AMm => {
|
2020-09-12 00:42:44 +00:00
|
|
|
let weapon_id = read_cur_varint(&mut cursor)? as u8;
|
|
|
|
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon_id);
|
2020-09-10 13:24:04 +00:00
|
|
|
|
2020-09-12 00:42:44 +00:00
|
|
|
if let Some(wtype) = weapon_type {
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.inventory_player1.remove_weapon(wtype);
|
2020-11-30 13:31:40 +00:00
|
|
|
game_scene.inventory_player2.remove_weapon(wtype);
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
2020-09-10 13:24:04 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::AEp => {
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.inventory_player1.refill_all_ammo();
|
2020-11-30 13:31:40 +00:00
|
|
|
game_scene.inventory_player2.refill_all_ammo();
|
2020-09-10 13:24:04 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2021-01-16 13:51:52 +00:00
|
|
|
OpCode::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);
|
|
|
|
}
|
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-10 13:24:04 +00:00
|
|
|
OpCode::ZAM => {
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.inventory_player1.reset_all_weapon_xp();
|
2020-11-30 13:31:40 +00:00
|
|
|
game_scene.inventory_player2.reset_all_weapon_xp();
|
2020-09-10 13:24:04 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-11 12:58:21 +00:00
|
|
|
OpCode::EQp => {
|
|
|
|
let mask = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.equip.0 |= mask;
|
2020-09-11 12:58:21 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
OpCode::EQm => {
|
|
|
|
let mask = read_cur_varint(&mut cursor)? as u16;
|
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.equip.0 &= !mask;
|
2020-09-11 12:58:21 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-11-01 19:39:57 +00:00
|
|
|
OpCode::INI => {
|
|
|
|
exec_state = TextScriptExecutionState::Reset;
|
|
|
|
}
|
|
|
|
OpCode::ESC => {
|
2020-09-20 15:27:31 +00:00
|
|
|
state.next_scene = Some(Box::new(TitleScene::new()));
|
2020-09-25 12:55:28 +00:00
|
|
|
state.control_flags.set_tick_world(false);
|
|
|
|
state.control_flags.set_control_enabled(false);
|
|
|
|
state.control_flags.set_interactions_disabled(true);
|
2020-09-20 15:27:31 +00:00
|
|
|
|
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
2020-10-30 01:29:53 +00:00
|
|
|
OpCode::SVP => {
|
|
|
|
exec_state = TextScriptExecutionState::SaveProfile(event, cursor.position() as u32);
|
|
|
|
}
|
2020-09-22 00:01:55 +00:00
|
|
|
OpCode::LDP => {
|
2020-09-25 12:55:28 +00:00
|
|
|
state.control_flags.set_tick_world(false);
|
|
|
|
state.control_flags.set_control_enabled(false);
|
|
|
|
state.control_flags.set_interactions_disabled(true);
|
|
|
|
|
2020-09-22 00:01:55 +00:00
|
|
|
exec_state = TextScriptExecutionState::LoadProfile;
|
|
|
|
}
|
2020-08-27 02:43:21 +00:00
|
|
|
// unimplemented opcodes
|
|
|
|
// Zero operands
|
2021-02-12 10:05:28 +00:00
|
|
|
OpCode::CIL
|
|
|
|
| OpCode::CPS
|
|
|
|
| OpCode::KE2
|
|
|
|
| OpCode::CRE
|
|
|
|
| OpCode::CSS
|
|
|
|
| OpCode::FLA
|
|
|
|
| OpCode::MLP
|
|
|
|
| OpCode::SPS
|
|
|
|
| OpCode::FR2
|
|
|
|
| OpCode::STC
|
|
|
|
| OpCode::HM2 => {
|
2020-08-28 01:41:14 +00:00
|
|
|
log::warn!("unimplemented opcode: {:?}", op);
|
2020-08-28 02:12:13 +00:00
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
// One operand codes
|
2021-02-12 10:05:28 +00:00
|
|
|
OpCode::MPp
|
|
|
|
| OpCode::SKm
|
|
|
|
| OpCode::SKp
|
|
|
|
| OpCode::UNJ
|
|
|
|
| OpCode::MPJ
|
|
|
|
| OpCode::XX1
|
|
|
|
| OpCode::SIL
|
|
|
|
| OpCode::SSS
|
|
|
|
| OpCode::ACH => {
|
2020-08-27 02:43:21 +00:00
|
|
|
let par_a = read_cur_varint(&mut cursor)?;
|
2020-08-28 02:12:13 +00:00
|
|
|
|
2020-08-28 01:41:14 +00:00
|
|
|
log::warn!("unimplemented opcode: {:?} {}", op, par_a);
|
2020-08-28 02:12:13 +00:00
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
// Two operand codes
|
2020-11-01 19:08:52 +00:00
|
|
|
OpCode::SKJ => {
|
2020-08-27 02:43:21 +00:00
|
|
|
let par_a = read_cur_varint(&mut cursor)?;
|
|
|
|
let par_b = read_cur_varint(&mut cursor)?;
|
2020-08-28 02:12:13 +00:00
|
|
|
|
2020-08-28 01:41:14 +00:00
|
|
|
log::warn!("unimplemented opcode: {:?} {} {}", op, par_a, par_b);
|
2020-08-28 02:12:13 +00:00
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
|
|
}
|
|
|
|
}
|
2020-08-27 05:12:14 +00:00
|
|
|
} else {
|
|
|
|
exec_state = TextScriptExecutionState::Ended;
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return Ok(TextScriptExecutionState::Ended);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(exec_state)
|
|
|
|
}
|
2020-08-26 01:06:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct TextScript {
|
|
|
|
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 {
|
|
|
|
let half = buf.len() / 2;
|
2021-02-12 10:05:28 +00:00
|
|
|
let key = if let Some(0) = buf.get(half) { 0xf9 } else { (-(*buf.get(half).unwrap() as isize)) as u8 };
|
2020-09-25 17:14:52 +00:00
|
|
|
log::info!("Decrypting TSC using key {:#x}", key);
|
2020-08-23 02:17:45 +00:00
|
|
|
|
2020-09-25 17:14:52 +00:00
|
|
|
for (idx, byte) in buf.iter_mut().enumerate() {
|
|
|
|
if idx == half {
|
|
|
|
continue;
|
|
|
|
}
|
2020-08-23 02:17:45 +00:00
|
|
|
|
2020-09-25 17:14:52 +00:00
|
|
|
*byte = byte.wrapping_add(key);
|
|
|
|
}
|
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> {
|
|
|
|
self.event_map.keys().copied().sorted().collect_vec()
|
|
|
|
}
|
|
|
|
|
2020-08-23 02:17:45 +00:00
|
|
|
/// Compiles a decrypted text script data into internal bytecode.
|
2020-09-25 17:14:52 +00:00
|
|
|
pub fn compile(data: &[u8], strict: bool, encoding: TextScriptEncoding) -> GameResult<TextScript> {
|
2020-09-06 15:55:07 +00:00
|
|
|
log::info!("data: {}", String::from_utf8_lossy(data));
|
2020-08-23 02:17:45 +00:00
|
|
|
|
2020-08-26 01:06:21 +00:00
|
|
|
let mut event_map = HashMap::new();
|
2020-08-27 02:43:21 +00:00
|
|
|
let mut iter = data.iter().copied().peekable();
|
2020-09-02 22:57:54 +00:00
|
|
|
let mut last_event = 0;
|
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
while let Some(&chr) = iter.peek() {
|
2020-08-26 01:06:21 +00:00
|
|
|
match chr {
|
|
|
|
b'#' => {
|
|
|
|
iter.next();
|
|
|
|
let event_num = TextScript::read_number(&mut iter)? as u16;
|
2020-09-25 20:02:35 +00:00
|
|
|
if iter.peek().is_some() {
|
|
|
|
TextScript::skip_until(b'\n', &mut iter)?;
|
|
|
|
}
|
2020-09-02 22:57:54 +00:00
|
|
|
last_event = event_num;
|
2020-08-26 01:06:21 +00:00
|
|
|
|
|
|
|
if event_map.contains_key(&event_num) {
|
2020-08-28 01:47:50 +00:00
|
|
|
if strict {
|
|
|
|
return Err(ParseError(format!("Event {} has been defined twice.", event_num)));
|
2020-08-28 02:12:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
match TextScript::skip_until(b'#', &mut iter).ok() {
|
2021-02-12 10:05:28 +00:00
|
|
|
Some(_) => {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
break;
|
|
|
|
}
|
2020-08-28 01:47:50 +00:00
|
|
|
}
|
2020-08-26 01:06:21 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 17:14:52 +00:00
|
|
|
let bytecode = TextScript::compile_event(&mut iter, strict, encoding)?;
|
2020-08-26 01:06:21 +00:00
|
|
|
log::info!("Successfully compiled event #{} ({} bytes generated).", event_num, bytecode.len());
|
|
|
|
event_map.insert(event_num, bytecode);
|
|
|
|
}
|
2020-08-28 02:12:13 +00:00
|
|
|
b'\r' | b'\n' | b' ' | b'\t' => {
|
2020-08-26 01:06:21 +00:00
|
|
|
iter.next();
|
|
|
|
}
|
|
|
|
n => {
|
2020-09-11 16:30:18 +00:00
|
|
|
// CS+ boss rush is the buggiest shit ever.
|
|
|
|
if !strict && last_event == 0 {
|
|
|
|
iter.next();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-09-02 22:57:54 +00:00
|
|
|
return Err(ParseError(format!("Unexpected token in event {}: {}", last_event, n as char)));
|
2020-08-26 01:06:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-12 10:05:28 +00:00
|
|
|
Ok(TextScript { event_map })
|
2020-08-26 01:06:21 +00:00
|
|
|
}
|
|
|
|
|
2021-02-12 10:05:28 +00:00
|
|
|
fn compile_event<I: Iterator<Item = u8>>(
|
|
|
|
iter: &mut Peekable<I>,
|
|
|
|
strict: bool,
|
|
|
|
encoding: TextScriptEncoding,
|
|
|
|
) -> GameResult<Vec<u8>> {
|
2020-08-26 01:06:21 +00:00
|
|
|
let mut bytecode = Vec::new();
|
|
|
|
let mut char_buf = Vec::with_capacity(16);
|
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
while let Some(&chr) = iter.peek() {
|
2020-08-26 01:06:21 +00:00
|
|
|
match chr {
|
|
|
|
b'#' => {
|
|
|
|
if !char_buf.is_empty() {
|
2020-09-13 03:30:56 +00:00
|
|
|
TextScript::put_string(&mut char_buf, &mut bytecode, encoding);
|
2020-08-26 01:06:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// some events end without <END marker.
|
2020-08-27 02:43:21 +00:00
|
|
|
TextScript::put_varint(OpCode::_END as i32, &mut bytecode);
|
2020-08-26 01:06:21 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
b'<' => {
|
|
|
|
if !char_buf.is_empty() {
|
2020-09-13 03:30:56 +00:00
|
|
|
TextScript::put_string(&mut char_buf, &mut bytecode, encoding);
|
2020-08-26 01:06:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
iter.next();
|
2021-02-12 10:05:28 +00:00
|
|
|
let n = iter
|
|
|
|
.next_tuple::<(u8, u8, u8)>()
|
2020-08-27 02:43:21 +00:00
|
|
|
.map(|t| [t.0, t.1, t.2])
|
2020-08-26 01:06:21 +00:00
|
|
|
.ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?;
|
|
|
|
|
2020-08-28 19:57:37 +00:00
|
|
|
let code = String::from_utf8_lossy(&n);
|
2020-08-26 01:06:21 +00:00
|
|
|
|
2020-08-28 19:57:37 +00:00
|
|
|
TextScript::compile_code(code.as_ref(), strict, iter, &mut bytecode)?;
|
2020-08-26 01:06:21 +00:00
|
|
|
}
|
2020-11-24 23:08:27 +00:00
|
|
|
b'\r' => {
|
|
|
|
iter.next();
|
|
|
|
}
|
2020-08-26 01:06:21 +00:00
|
|
|
_ => {
|
|
|
|
char_buf.push(chr);
|
|
|
|
|
|
|
|
iter.next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(bytecode)
|
|
|
|
}
|
|
|
|
|
2020-09-13 03:30:56 +00:00
|
|
|
fn put_string(buffer: &mut Vec<u8>, out: &mut Vec<u8>, encoding: TextScriptEncoding) {
|
2020-09-06 15:55:07 +00:00
|
|
|
let mut cursor: Cursor<&Vec<u8>> = Cursor::new(buffer);
|
|
|
|
let mut tmp_buf = Vec::new();
|
|
|
|
let mut remaining = buffer.len() as u32;
|
|
|
|
let mut chars = 0;
|
|
|
|
|
|
|
|
while remaining > 0 {
|
2020-09-25 17:14:52 +00:00
|
|
|
let (consumed, chr) = match encoding {
|
|
|
|
TextScriptEncoding::UTF8 => read_cur_wtf8(&mut cursor, remaining),
|
|
|
|
TextScriptEncoding::ShiftJIS => read_cur_shift_jis(&mut cursor, remaining),
|
2020-09-13 03:30:56 +00:00
|
|
|
};
|
2020-09-25 17:14:52 +00:00
|
|
|
|
2020-09-06 15:55:07 +00:00
|
|
|
remaining -= consumed;
|
|
|
|
chars += 1;
|
|
|
|
|
|
|
|
TextScript::put_varint(chr as i32, &mut tmp_buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer.clear();
|
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
TextScript::put_varint(OpCode::_STR as i32, out);
|
2020-09-06 15:55:07 +00:00
|
|
|
TextScript::put_varint(chars, out);
|
|
|
|
out.append(&mut tmp_buf);
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
|
|
|
|
2020-08-26 01:06:21 +00:00
|
|
|
fn put_varint(val: i32, out: &mut Vec<u8>) {
|
|
|
|
let mut x = ((val as u32) >> 31) ^ ((val as u32) << 1);
|
|
|
|
|
2020-09-06 15:55:07 +00:00
|
|
|
loop {
|
|
|
|
let mut n = (x & 0x7f) as u8;
|
2020-08-26 01:06:21 +00:00
|
|
|
x >>= 7;
|
|
|
|
|
2020-09-06 15:55:07 +00:00
|
|
|
if x != 0 {
|
|
|
|
n |= 0x80;
|
|
|
|
}
|
|
|
|
|
|
|
|
out.push(n);
|
|
|
|
|
2021-02-12 10:05:28 +00:00
|
|
|
if x == 0 {
|
|
|
|
break;
|
|
|
|
}
|
2020-09-06 15:55:07 +00:00
|
|
|
}
|
2020-08-26 01:06:21 +00:00
|
|
|
}
|
|
|
|
|
2021-02-10 11:53:49 +00:00
|
|
|
#[allow(unused)]
|
2021-02-12 10:05:28 +00:00
|
|
|
fn read_varint<I: Iterator<Item = u8>>(iter: &mut I) -> GameResult<i32> {
|
2020-08-26 01:06:21 +00:00
|
|
|
let mut result = 0u32;
|
|
|
|
|
|
|
|
for o in 0..5 {
|
2020-08-27 02:43:21 +00:00
|
|
|
let n = iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?;
|
2020-08-26 01:06:21 +00:00
|
|
|
result |= (n as u32 & 0x7f) << (o * 7);
|
|
|
|
|
2021-02-12 10:05:28 +00:00
|
|
|
if n & 0x80 == 0 {
|
|
|
|
break;
|
|
|
|
}
|
2020-08-26 01:06:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(((result << 31) ^ (result >> 1)) as i32)
|
|
|
|
}
|
|
|
|
|
2021-02-12 10:05:28 +00:00
|
|
|
fn compile_code<I: Iterator<Item = u8>>(
|
|
|
|
code: &str,
|
|
|
|
strict: bool,
|
|
|
|
iter: &mut Peekable<I>,
|
|
|
|
out: &mut Vec<u8>,
|
|
|
|
) -> GameResult {
|
2020-08-27 02:43:21 +00:00
|
|
|
let instr = OpCode::from_str(code).map_err(|_| ParseError(format!("Unknown opcode: {}", code)))?;
|
2020-08-26 01:06:21 +00:00
|
|
|
|
|
|
|
match instr {
|
|
|
|
// Zero operand codes
|
2021-02-12 10:05:28 +00:00
|
|
|
OpCode::AEp
|
|
|
|
| OpCode::CAT
|
|
|
|
| OpCode::CIL
|
|
|
|
| OpCode::CLO
|
|
|
|
| OpCode::CLR
|
|
|
|
| OpCode::CPS
|
|
|
|
| OpCode::CRE
|
|
|
|
| OpCode::CSS
|
|
|
|
| OpCode::END
|
|
|
|
| OpCode::ESC
|
|
|
|
| OpCode::FLA
|
|
|
|
| OpCode::FMU
|
|
|
|
| OpCode::FRE
|
|
|
|
| OpCode::HMC
|
|
|
|
| OpCode::INI
|
|
|
|
| OpCode::KEY
|
|
|
|
| OpCode::LDP
|
|
|
|
| OpCode::MLP
|
|
|
|
| OpCode::MM0
|
|
|
|
| OpCode::MNA
|
|
|
|
| OpCode::MS2
|
|
|
|
| OpCode::MS3
|
|
|
|
| OpCode::MSG
|
|
|
|
| OpCode::NOD
|
|
|
|
| OpCode::PRI
|
|
|
|
| OpCode::RMU
|
|
|
|
| OpCode::SAT
|
|
|
|
| OpCode::SLP
|
|
|
|
| OpCode::SMC
|
|
|
|
| OpCode::SPS
|
|
|
|
| OpCode::STC
|
|
|
|
| OpCode::SVP
|
|
|
|
| OpCode::TUR
|
|
|
|
| OpCode::WAS
|
|
|
|
| OpCode::ZAM
|
|
|
|
| OpCode::HM2
|
|
|
|
| OpCode::POP
|
|
|
|
| OpCode::KE2
|
|
|
|
| OpCode::FR2 => {
|
2020-08-26 01:06:21 +00:00
|
|
|
TextScript::put_varint(instr as i32, out);
|
|
|
|
}
|
|
|
|
// One operand codes
|
2021-02-12 10:05:28 +00:00
|
|
|
OpCode::BOA
|
|
|
|
| OpCode::BSL
|
|
|
|
| OpCode::FOB
|
|
|
|
| OpCode::FOM
|
|
|
|
| OpCode::QUA
|
|
|
|
| OpCode::UNI
|
|
|
|
| OpCode::MYB
|
|
|
|
| OpCode::MYD
|
|
|
|
| OpCode::FAI
|
|
|
|
| OpCode::FAO
|
|
|
|
| OpCode::WAI
|
|
|
|
| OpCode::FAC
|
|
|
|
| OpCode::GIT
|
|
|
|
| OpCode::NUM
|
|
|
|
| OpCode::DNA
|
|
|
|
| OpCode::DNP
|
|
|
|
| OpCode::FLm
|
|
|
|
| OpCode::FLp
|
|
|
|
| OpCode::MPp
|
|
|
|
| OpCode::SKm
|
|
|
|
| OpCode::SKp
|
|
|
|
| OpCode::EQp
|
|
|
|
| OpCode::EQm
|
|
|
|
| OpCode::MLp
|
|
|
|
| OpCode::ITp
|
|
|
|
| OpCode::ITm
|
|
|
|
| OpCode::AMm
|
|
|
|
| OpCode::UNJ
|
|
|
|
| OpCode::MPJ
|
|
|
|
| OpCode::YNJ
|
|
|
|
| OpCode::EVE
|
|
|
|
| OpCode::XX1
|
|
|
|
| OpCode::SIL
|
|
|
|
| OpCode::LIp
|
|
|
|
| OpCode::SOU
|
|
|
|
| OpCode::CMU
|
|
|
|
| OpCode::SSS
|
|
|
|
| OpCode::ACH
|
|
|
|
| OpCode::S2MV
|
|
|
|
| OpCode::PSH => {
|
2020-08-26 01:06:21 +00:00
|
|
|
let operand = TextScript::read_number(iter)?;
|
|
|
|
TextScript::put_varint(instr as i32, out);
|
|
|
|
TextScript::put_varint(operand as i32, out);
|
|
|
|
}
|
|
|
|
// Two operand codes
|
2021-02-12 10:05:28 +00:00
|
|
|
OpCode::FON
|
|
|
|
| OpCode::MOV
|
|
|
|
| OpCode::AMp
|
|
|
|
| OpCode::NCJ
|
|
|
|
| OpCode::ECJ
|
|
|
|
| OpCode::FLJ
|
|
|
|
| OpCode::ITJ
|
|
|
|
| OpCode::SKJ
|
|
|
|
| OpCode::AMJ
|
|
|
|
| OpCode::SMP
|
|
|
|
| OpCode::PSp
|
|
|
|
| OpCode::IpN
|
|
|
|
| OpCode::FFm => {
|
2020-08-26 01:06:21 +00:00
|
|
|
let operand_a = TextScript::read_number(iter)?;
|
2021-02-12 10:05:28 +00:00
|
|
|
if strict {
|
|
|
|
TextScript::expect_char(b':', iter)?;
|
|
|
|
} else {
|
|
|
|
iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?;
|
|
|
|
}
|
2020-08-26 01:06:21 +00:00
|
|
|
let operand_b = TextScript::read_number(iter)?;
|
|
|
|
|
|
|
|
TextScript::put_varint(instr as i32, out);
|
|
|
|
TextScript::put_varint(operand_a as i32, out);
|
|
|
|
TextScript::put_varint(operand_b as i32, out);
|
|
|
|
}
|
|
|
|
// Three operand codes
|
2020-09-25 17:14:52 +00:00
|
|
|
OpCode::ANP | OpCode::CNP | OpCode::INP | OpCode::TAM | OpCode::CMP | OpCode::INJ => {
|
2020-08-26 01:06:21 +00:00
|
|
|
let operand_a = TextScript::read_number(iter)?;
|
2021-02-12 10:05:28 +00:00
|
|
|
if strict {
|
|
|
|
TextScript::expect_char(b':', iter)?;
|
|
|
|
} else {
|
|
|
|
iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?;
|
|
|
|
}
|
2020-08-26 01:06:21 +00:00
|
|
|
let operand_b = TextScript::read_number(iter)?;
|
2021-02-12 10:05:28 +00:00
|
|
|
if strict {
|
|
|
|
TextScript::expect_char(b':', iter)?;
|
|
|
|
} else {
|
|
|
|
iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?;
|
|
|
|
}
|
2020-08-26 01:06:21 +00:00
|
|
|
let operand_c = TextScript::read_number(iter)?;
|
|
|
|
|
|
|
|
TextScript::put_varint(instr as i32, out);
|
|
|
|
TextScript::put_varint(operand_a as i32, out);
|
|
|
|
TextScript::put_varint(operand_b as i32, out);
|
|
|
|
TextScript::put_varint(operand_c as i32, out);
|
|
|
|
}
|
|
|
|
// Four operand codes
|
|
|
|
OpCode::TRA | OpCode::MNP | OpCode::SNP => {
|
|
|
|
let operand_a = TextScript::read_number(iter)?;
|
2021-02-12 10:05:28 +00:00
|
|
|
if strict {
|
|
|
|
TextScript::expect_char(b':', iter)?;
|
|
|
|
} else {
|
|
|
|
iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?;
|
|
|
|
}
|
2020-08-26 01:06:21 +00:00
|
|
|
let operand_b = TextScript::read_number(iter)?;
|
2021-02-12 10:05:28 +00:00
|
|
|
if strict {
|
|
|
|
TextScript::expect_char(b':', iter)?;
|
|
|
|
} else {
|
|
|
|
iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?;
|
|
|
|
}
|
2020-08-26 01:06:21 +00:00
|
|
|
let operand_c = TextScript::read_number(iter)?;
|
2021-02-12 10:05:28 +00:00
|
|
|
if strict {
|
|
|
|
TextScript::expect_char(b':', iter)?;
|
|
|
|
} else {
|
|
|
|
iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?;
|
|
|
|
}
|
2020-08-26 01:06:21 +00:00
|
|
|
let operand_d = TextScript::read_number(iter)?;
|
|
|
|
|
|
|
|
TextScript::put_varint(instr as i32, out);
|
|
|
|
TextScript::put_varint(operand_a as i32, out);
|
|
|
|
TextScript::put_varint(operand_b as i32, out);
|
|
|
|
TextScript::put_varint(operand_c as i32, out);
|
|
|
|
TextScript::put_varint(operand_d as i32, out);
|
|
|
|
}
|
2020-09-25 22:28:37 +00:00
|
|
|
OpCode::_NOP | OpCode::_UNI | OpCode::_STR | OpCode::_END => {
|
|
|
|
unreachable!()
|
2020-08-26 01:06:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-11-24 23:08:27 +00:00
|
|
|
pub fn decompile_event(&self, id: u16) -> GameResult<String> {
|
|
|
|
if let Some(bytecode) = self.event_map.get(&id) {
|
|
|
|
let mut result = String::new();
|
|
|
|
let mut cursor = Cursor::new(bytecode);
|
|
|
|
|
|
|
|
while let Ok(op_num) = read_cur_varint(&mut cursor) {
|
|
|
|
let op_maybe: Option<OpCode> = FromPrimitive::from_i32(op_num);
|
|
|
|
|
|
|
|
if let Some(op) = op_maybe {
|
|
|
|
match op {
|
|
|
|
// Zero operand codes
|
2021-02-12 10:05:28 +00:00
|
|
|
OpCode::AEp
|
|
|
|
| OpCode::CAT
|
|
|
|
| OpCode::CIL
|
|
|
|
| OpCode::CLO
|
|
|
|
| OpCode::CLR
|
|
|
|
| OpCode::CPS
|
|
|
|
| OpCode::CRE
|
|
|
|
| OpCode::CSS
|
|
|
|
| OpCode::END
|
|
|
|
| OpCode::ESC
|
|
|
|
| OpCode::FLA
|
|
|
|
| OpCode::FMU
|
|
|
|
| OpCode::FRE
|
|
|
|
| OpCode::HMC
|
|
|
|
| OpCode::INI
|
|
|
|
| OpCode::KEY
|
|
|
|
| OpCode::LDP
|
|
|
|
| OpCode::MLP
|
|
|
|
| OpCode::MM0
|
|
|
|
| OpCode::MNA
|
|
|
|
| OpCode::MS2
|
|
|
|
| OpCode::MS3
|
|
|
|
| OpCode::MSG
|
|
|
|
| OpCode::NOD
|
|
|
|
| OpCode::PRI
|
|
|
|
| OpCode::RMU
|
|
|
|
| OpCode::SAT
|
|
|
|
| OpCode::SLP
|
|
|
|
| OpCode::SMC
|
|
|
|
| OpCode::SPS
|
|
|
|
| OpCode::STC
|
|
|
|
| OpCode::SVP
|
|
|
|
| OpCode::TUR
|
|
|
|
| OpCode::WAS
|
|
|
|
| OpCode::ZAM
|
|
|
|
| OpCode::HM2
|
|
|
|
| OpCode::POP
|
|
|
|
| OpCode::KE2
|
|
|
|
| OpCode::FR2 => {
|
2020-11-24 23:08:27 +00:00
|
|
|
result.push_str(format!("{:?}()\n", op).as_str());
|
|
|
|
}
|
|
|
|
// One operand codes
|
2021-02-12 10:05:28 +00:00
|
|
|
OpCode::BOA
|
|
|
|
| OpCode::BSL
|
|
|
|
| OpCode::FOB
|
|
|
|
| OpCode::FOM
|
|
|
|
| OpCode::QUA
|
|
|
|
| OpCode::UNI
|
|
|
|
| OpCode::MYB
|
|
|
|
| OpCode::MYD
|
|
|
|
| OpCode::FAI
|
|
|
|
| OpCode::FAO
|
|
|
|
| OpCode::WAI
|
|
|
|
| OpCode::FAC
|
|
|
|
| OpCode::GIT
|
|
|
|
| OpCode::NUM
|
|
|
|
| OpCode::DNA
|
|
|
|
| OpCode::DNP
|
|
|
|
| OpCode::FLm
|
|
|
|
| OpCode::FLp
|
|
|
|
| OpCode::MPp
|
|
|
|
| OpCode::SKm
|
|
|
|
| OpCode::SKp
|
|
|
|
| OpCode::EQp
|
|
|
|
| OpCode::EQm
|
|
|
|
| OpCode::MLp
|
|
|
|
| OpCode::ITp
|
|
|
|
| OpCode::ITm
|
|
|
|
| OpCode::AMm
|
|
|
|
| OpCode::UNJ
|
|
|
|
| OpCode::MPJ
|
|
|
|
| OpCode::YNJ
|
|
|
|
| OpCode::EVE
|
|
|
|
| OpCode::XX1
|
|
|
|
| OpCode::SIL
|
|
|
|
| OpCode::LIp
|
|
|
|
| OpCode::SOU
|
|
|
|
| OpCode::CMU
|
|
|
|
| OpCode::SSS
|
|
|
|
| OpCode::ACH
|
|
|
|
| OpCode::S2MV
|
|
|
|
| OpCode::PSH => {
|
2020-11-24 23:08:27 +00:00
|
|
|
let par_a = read_cur_varint(&mut cursor)?;
|
|
|
|
|
|
|
|
result.push_str(format!("{:?}({})\n", op, par_a).as_str());
|
|
|
|
}
|
|
|
|
// Two operand codes
|
2021-02-12 10:05:28 +00:00
|
|
|
OpCode::FON
|
|
|
|
| OpCode::MOV
|
|
|
|
| OpCode::AMp
|
|
|
|
| OpCode::NCJ
|
|
|
|
| OpCode::ECJ
|
|
|
|
| OpCode::FLJ
|
|
|
|
| OpCode::ITJ
|
|
|
|
| OpCode::SKJ
|
|
|
|
| OpCode::AMJ
|
|
|
|
| OpCode::SMP
|
|
|
|
| OpCode::PSp
|
|
|
|
| OpCode::IpN
|
|
|
|
| OpCode::FFm => {
|
2020-11-24 23:08:27 +00:00
|
|
|
let par_a = read_cur_varint(&mut cursor)?;
|
|
|
|
let par_b = read_cur_varint(&mut cursor)?;
|
|
|
|
|
|
|
|
result.push_str(format!("{:?}({}, {})\n", op, par_a, par_b).as_str());
|
|
|
|
}
|
|
|
|
// Three operand codes
|
|
|
|
OpCode::ANP | OpCode::CNP | OpCode::INP | OpCode::TAM | OpCode::CMP | OpCode::INJ => {
|
|
|
|
let par_a = read_cur_varint(&mut cursor)?;
|
|
|
|
let par_b = read_cur_varint(&mut cursor)?;
|
|
|
|
let par_c = read_cur_varint(&mut cursor)?;
|
|
|
|
|
|
|
|
result.push_str(format!("{:?}({}, {}, {})\n", op, par_a, par_b, par_c).as_str());
|
|
|
|
}
|
|
|
|
// Four operand codes
|
|
|
|
OpCode::TRA | OpCode::MNP | OpCode::SNP => {
|
|
|
|
let par_a = read_cur_varint(&mut cursor)?;
|
|
|
|
let par_b = read_cur_varint(&mut cursor)?;
|
|
|
|
let par_c = read_cur_varint(&mut cursor)?;
|
|
|
|
let par_d = read_cur_varint(&mut cursor)?;
|
|
|
|
|
|
|
|
result.push_str(format!("{:?}({}, {}, {}, {})\n", op, par_a, par_b, par_c, par_d).as_str());
|
|
|
|
}
|
|
|
|
OpCode::_STR => {
|
|
|
|
let len = read_cur_varint(&mut cursor)?;
|
|
|
|
|
|
|
|
result.push_str(format!("%string(len = {}, value = \"", len).as_str());
|
|
|
|
for _ in 0..len {
|
|
|
|
let chr = std::char::from_u32(read_cur_varint(&mut cursor)? as u32).unwrap_or('?');
|
|
|
|
match chr {
|
|
|
|
'\n' => {
|
|
|
|
result.push_str("\\n");
|
|
|
|
}
|
|
|
|
'\r' => {
|
|
|
|
result.push_str("\\r");
|
|
|
|
}
|
|
|
|
'\t' => {
|
|
|
|
result.push_str("\\t");
|
|
|
|
}
|
|
|
|
'\u{0000}'..='\u{001f}' | '\u{0080}'..='\u{ffff}' => {
|
|
|
|
result.push_str(chr.escape_unicode().to_string().as_str());
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
result.push(chr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result.push_str("\")\n");
|
|
|
|
}
|
|
|
|
OpCode::_NOP => result.push_str("%no_op()\n"),
|
|
|
|
OpCode::_UNI => result.push_str("%unimplemented()\n"),
|
|
|
|
OpCode::_END => result.push_str("%end_marker()\n"),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
} else {
|
|
|
|
Err(InvalidValue("Unknown script.".to_string()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-12 10:05:28 +00:00
|
|
|
fn expect_char<I: Iterator<Item = u8>>(expect: u8, iter: &mut I) -> GameResult {
|
2020-08-27 02:43:21 +00:00
|
|
|
let res = iter.next();
|
2020-08-26 01:06:21 +00:00
|
|
|
|
|
|
|
match res {
|
2020-11-24 23:08:27 +00:00
|
|
|
Some(n) if n == expect => Ok(()),
|
|
|
|
Some(n) => Err(ParseError(format!("Expected {}, found {}", expect as char, n as char))),
|
|
|
|
None => Err(ParseError(str!("Script unexpectedly ended."))),
|
2020-08-26 01:06:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-12 10:05:28 +00:00
|
|
|
fn skip_until<I: Iterator<Item = u8>>(expect: u8, iter: &mut Peekable<I>) -> GameResult {
|
2020-09-02 22:57:54 +00:00
|
|
|
while let Some(&chr) = iter.peek() {
|
2020-08-26 01:06:21 +00:00
|
|
|
if chr == expect {
|
|
|
|
return Ok(());
|
2020-09-02 22:57:54 +00:00
|
|
|
} else {
|
|
|
|
iter.next();
|
2020-08-26 01:06:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(ParseError(str!("Script unexpectedly ended.")))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Reads a 4 digit TSC formatted number from iterator.
|
|
|
|
/// Intentionally does no '0'..'9' range checking, since it was often exploited by modders.
|
2021-02-12 10:05:28 +00:00
|
|
|
fn read_number<I: Iterator<Item = u8>>(iter: &mut Peekable<I>) -> GameResult<i32> {
|
2020-08-26 01:06:21 +00:00
|
|
|
Some(0)
|
2020-08-27 23:34:28 +00:00
|
|
|
.and_then(|result| iter.next().map(|v| result + 1000 * v.wrapping_sub(b'0') as i32))
|
|
|
|
.and_then(|result| iter.next().map(|v| result + 100 * v.wrapping_sub(b'0') as i32))
|
|
|
|
.and_then(|result| iter.next().map(|v| result + 10 * v.wrapping_sub(b'0') as i32))
|
|
|
|
.and_then(|result| iter.next().map(|v| result + v.wrapping_sub(b'0') as i32))
|
2020-08-26 01:06:21 +00:00
|
|
|
.ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn has_event(&self, id: u16) -> bool {
|
|
|
|
self.event_map.contains_key(&id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_varint() {
|
2020-09-06 15:55:07 +00:00
|
|
|
for n in -4000..=4000 {
|
2020-08-26 01:06:21 +00:00
|
|
|
let mut out = Vec::new();
|
|
|
|
TextScript::put_varint(n, &mut out);
|
2020-09-06 15:55:07 +00:00
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
let result = TextScript::read_varint(&mut out.iter().copied()).unwrap();
|
2020-08-26 01:06:21 +00:00
|
|
|
assert_eq!(result, n);
|
2020-09-06 15:55:07 +00:00
|
|
|
let mut cur = Cursor::new(&out);
|
|
|
|
let result = read_cur_varint(&mut cur).unwrap();
|
|
|
|
assert_eq!(result, n);
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
}
|