mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2024-11-16 10:52:44 +00:00
tsc refactor/optimization, credits interpreter
This commit is contained in:
parent
66106c7e82
commit
df1fbbf0d1
49
src/components/credits.rs
Normal file
49
src/components/credits.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use crate::common::Rect;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
pub struct Credits {}
|
||||
|
||||
impl Credits {
|
||||
pub fn new() -> Credits {
|
||||
Credits {}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<()> for Credits {
|
||||
fn tick(&mut self, state: &mut SharedGameState, custom: ()) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
if state.creditscript_vm.lines.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Casts")?;
|
||||
for line in state.creditscript_vm.lines.iter() {
|
||||
let x = (line.cast_id % 13) * 24;
|
||||
let y = ((line.cast_id / 13) & 0xff) * 24;
|
||||
let rect = Rect::new_size(x, y, 24, 24);
|
||||
|
||||
batch.add_rect(line.pos_x - 24.0, line.pos_y - 8.0, &rect);
|
||||
}
|
||||
batch.draw(ctx)?;
|
||||
|
||||
for line in state.creditscript_vm.lines.iter() {
|
||||
state.font.draw_text_with_shadow(
|
||||
line.text.chars(),
|
||||
line.pos_x,
|
||||
line.pos_y,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
pub mod boss_life_bar;
|
||||
pub mod credits;
|
||||
pub mod draw_common;
|
||||
pub mod flash;
|
||||
pub mod hud;
|
||||
|
|
|
@ -64,7 +64,7 @@ impl LiveDebugger {
|
|||
.resizable(false)
|
||||
.collapsed(true, Condition::FirstUseEver)
|
||||
.position([5.0, 5.0], Condition::FirstUseEver)
|
||||
.size([400.0, 170.0], Condition::FirstUseEver)
|
||||
.size([400.0, 190.0], Condition::FirstUseEver)
|
||||
.build(ui, || {
|
||||
ui.text(format!(
|
||||
"Player position: ({:.1},{:.1}), velocity: ({:.1},{:.1})",
|
||||
|
@ -112,7 +112,7 @@ impl LiveDebugger {
|
|||
}
|
||||
|
||||
ui.same_line(0.0);
|
||||
if ui.button(im_str!("Events"), [0.0, 0.0]) {
|
||||
if ui.button(im_str!("TSC Scripts"), [0.0, 0.0]) {
|
||||
self.events_visible = !self.events_visible;
|
||||
}
|
||||
|
||||
|
@ -124,7 +124,7 @@ impl LiveDebugger {
|
|||
#[cfg(feature = "scripting-lua")]
|
||||
{
|
||||
ui.same_line(0.0);
|
||||
if ui.button(im_str!("Reload Scripts"), [0.0, 0.0]) {
|
||||
if ui.button(im_str!("Reload Lua Scripts"), [0.0, 0.0]) {
|
||||
if let Err(err) = state.lua.reload_scripts(ctx) {
|
||||
log::error!("Error reloading scripts: {:?}", err);
|
||||
self.error = Some(ImString::new(err.to_string()));
|
||||
|
@ -204,39 +204,40 @@ impl LiveDebugger {
|
|||
}
|
||||
|
||||
if self.events_visible {
|
||||
Window::new(im_str!("Events"))
|
||||
Window::new(im_str!("TSC Scripts"))
|
||||
.resizable(false)
|
||||
.position([80.0, 80.0], Condition::Appearing)
|
||||
.size([300.0, 300.0], Condition::Appearing)
|
||||
.size([300.0, 320.0], Condition::Appearing)
|
||||
.build(ui, || {
|
||||
if self.events.is_empty() {
|
||||
self.event_ids.clear();
|
||||
|
||||
let vm = &state.textscript_vm;
|
||||
let scripts = state.textscript_vm.scripts.borrow();
|
||||
|
||||
for event in vm.scripts.scene_script.get_event_ids() {
|
||||
for event in scripts.scene_script.get_event_ids() {
|
||||
self.events.push(ImString::new(format!("Scene: #{:04}", event)));
|
||||
self.event_ids.push((ScriptType::Scene, event));
|
||||
}
|
||||
|
||||
for event in vm.scripts.global_script.get_event_ids() {
|
||||
for event in scripts.global_script.get_event_ids() {
|
||||
self.events.push(ImString::new(format!("Global: #{:04}", event)));
|
||||
self.event_ids.push((ScriptType::Global, event));
|
||||
}
|
||||
|
||||
for event in vm.scripts.inventory_script.get_event_ids() {
|
||||
for event in scripts.inventory_script.get_event_ids() {
|
||||
self.events.push(ImString::new(format!("Inventory: #{:04}", event)));
|
||||
self.event_ids.push((ScriptType::Inventory, event));
|
||||
}
|
||||
|
||||
for event in vm.scripts.stage_select_script.get_event_ids() {
|
||||
for event in scripts.stage_select_script.get_event_ids() {
|
||||
self.events.push(ImString::new(format!("Stage Select: #{:04}", event)));
|
||||
self.event_ids.push((ScriptType::StageSelect, event));
|
||||
}
|
||||
}
|
||||
let events: Vec<&ImStr> = self.events.iter().map(|e| e.as_ref()).collect();
|
||||
|
||||
ui.text_wrapped(&ImString::new(format!("Execution state: {:?}", state.textscript_vm.state)));
|
||||
ui.text_wrapped(&ImString::new(format!("TextScript execution state: {:?}", state.textscript_vm.state)));
|
||||
ui.text_wrapped(&ImString::new(format!("CreditScript execution state: {:?}", state.creditscript_vm.state)));
|
||||
|
||||
ui.push_item_width(-1.0);
|
||||
ui.list_box(im_str!(""), &mut self.selected_event, &events, 10);
|
||||
|
@ -256,11 +257,12 @@ impl LiveDebugger {
|
|||
if let Some((stype, event_num)) = self.event_ids.get(self.selected_event as usize) {
|
||||
let id = ((*stype as u32) << 16) | (*event_num as u32);
|
||||
if !self.text_windows.iter().any(|(e, _, _)| *e == id) {
|
||||
let scripts = state.textscript_vm.scripts.borrow();
|
||||
let script = match stype {
|
||||
ScriptType::Scene => &state.textscript_vm.scripts.scene_script,
|
||||
ScriptType::Global => &state.textscript_vm.scripts.global_script,
|
||||
ScriptType::Inventory => &state.textscript_vm.scripts.inventory_script,
|
||||
ScriptType::StageSelect => &state.textscript_vm.scripts.stage_select_script,
|
||||
ScriptType::Scene => &scripts.scene_script,
|
||||
ScriptType::Global => &scripts.global_script,
|
||||
ScriptType::Inventory => &scripts.inventory_script,
|
||||
ScriptType::StageSelect => &scripts.stage_select_script,
|
||||
};
|
||||
|
||||
match script.decompile_event(*event_num) {
|
||||
|
|
|
@ -5,6 +5,7 @@ use log::info;
|
|||
use crate::caret::CaretType;
|
||||
use crate::common::{interpolate_fix9_scale, Color, Direction, FadeDirection, FadeState, Rect};
|
||||
use crate::components::boss_life_bar::BossLifeBar;
|
||||
use crate::components::credits::Credits;
|
||||
use crate::components::draw_common::Alignment;
|
||||
use crate::components::flash::Flash;
|
||||
use crate::components::hud::HUD;
|
||||
|
@ -30,6 +31,7 @@ use crate::player::{Player, TargetPlayer};
|
|||
use crate::rng::XorShift;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
use crate::scene::Scene;
|
||||
use crate::scripting::tsc::credit_script::CreditScriptVM;
|
||||
use crate::shared_game_state::{SharedGameState, TileSize};
|
||||
use crate::stage::{BackgroundType, Stage};
|
||||
use crate::scripting::tsc::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptLine, TextScriptVM};
|
||||
|
@ -45,6 +47,7 @@ pub struct GameScene {
|
|||
pub boss_life_bar: BossLifeBar,
|
||||
pub stage_select: StageSelect,
|
||||
pub flash: Flash,
|
||||
pub credits: Credits,
|
||||
pub inventory_ui: InventoryUI,
|
||||
pub hud_player1: HUD,
|
||||
pub hud_player2: HUD,
|
||||
|
@ -132,6 +135,7 @@ impl GameScene {
|
|||
boss_life_bar: BossLifeBar::new(),
|
||||
stage_select: StageSelect::new(),
|
||||
flash: Flash::new(),
|
||||
credits: Credits::new(),
|
||||
inventory_ui: InventoryUI::new(),
|
||||
hud_player1: HUD::new(Alignment::Left),
|
||||
hud_player2: HUD::new(Alignment::Right),
|
||||
|
@ -1930,6 +1934,11 @@ impl Scene for GameScene {
|
|||
}
|
||||
}
|
||||
|
||||
if state.control_flags.credits_running() {
|
||||
self.skip_counter = 0;
|
||||
CreditScriptVM::run(state, ctx)?;
|
||||
}
|
||||
|
||||
match state.fade_state {
|
||||
FadeState::FadeOut(tick, direction) if tick < 15 => {
|
||||
state.fade_state = FadeState::FadeOut(tick + 1, direction);
|
||||
|
@ -2134,7 +2143,12 @@ impl Scene for GameScene {
|
|||
)?;
|
||||
}
|
||||
|
||||
if state.control_flags.credits_running() {
|
||||
self.credits.draw(state, ctx, &self.frame)?;
|
||||
}
|
||||
|
||||
self.draw_text_boxes(state, ctx)?;
|
||||
|
||||
if self.skip_counter > 0 {
|
||||
let text = format!("Hold {:?} to skip the cutscene", state.settings.player1_key_map.inventory);
|
||||
let width = state.font.text_width(text.chars(), &state.constants);
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::framework::filesystem;
|
|||
use crate::npc::NPCTable;
|
||||
use crate::scene::no_data_scene::NoDataScene;
|
||||
use crate::scene::Scene;
|
||||
use crate::scripting::tsc::credit_script::CreditScript;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::StageData;
|
||||
use crate::scripting::tsc::text_script::TextScript;
|
||||
|
@ -37,6 +38,10 @@ impl LoadingScene {
|
|||
let stage_select_script = TextScript::load_from(stage_select_tsc, &state.constants)?;
|
||||
state.textscript_vm.set_stage_select_script(stage_select_script);
|
||||
|
||||
let credit_tsc = filesystem::open(ctx, [&state.base_path, "/Credit.tsc"].join(""))?;
|
||||
let credit_script = CreditScript::load_from(credit_tsc, &state.constants)?;
|
||||
state.creditscript_vm.set_script(credit_script);
|
||||
|
||||
if ctx.headless {
|
||||
log::info!("Headless mode detected, skipping intro and loading last saved game.");
|
||||
state.load_or_start_game(ctx)?;
|
||||
|
|
|
@ -3,7 +3,6 @@ use std::io::{Cursor, Read};
|
|||
use crate::encoding::{read_cur_shift_jis, read_cur_wtf8};
|
||||
use crate::framework::error::GameError::ParseError;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::scripting::tsc::opcodes::OpCode;
|
||||
use crate::scripting::tsc::text_script::TextScriptEncoding;
|
||||
|
||||
pub fn put_varint(val: i32, out: &mut Vec<u8>) {
|
||||
|
@ -83,7 +82,6 @@ pub fn put_string(buffer: &mut Vec<u8>, out: &mut Vec<u8>, encoding: TextScriptE
|
|||
|
||||
buffer.clear();
|
||||
|
||||
put_varint(OpCode::_STR as i32, out);
|
||||
put_varint(chars, out);
|
||||
out.append(&mut tmp_buf);
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ use std::str::FromStr;
|
|||
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::encoding::read_cur_wtf8;
|
||||
use crate::framework::error::GameError::ParseError;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::scripting::tsc::bytecode_utils::{put_string, put_varint};
|
||||
use crate::scripting::tsc::opcodes::OpCode;
|
||||
use crate::scripting::tsc::credit_script::CreditScript;
|
||||
use crate::scripting::tsc::opcodes::{CreditOpCode, TSCOpCode};
|
||||
use crate::scripting::tsc::parse_utils::{expect_char, read_number, skip_until};
|
||||
use crate::scripting::tsc::text_script::{TextScript, TextScriptEncoding};
|
||||
|
||||
|
@ -80,11 +80,12 @@ impl TextScript {
|
|||
match chr {
|
||||
b'#' if allow_next_event => {
|
||||
if !char_buf.is_empty() {
|
||||
put_varint(TSCOpCode::_STR as i32, &mut bytecode);
|
||||
put_string(&mut char_buf, &mut bytecode, encoding);
|
||||
}
|
||||
|
||||
// some events end without <END marker.
|
||||
put_varint(OpCode::_END as i32, &mut bytecode);
|
||||
put_varint(TSCOpCode::_END as i32, &mut bytecode);
|
||||
break;
|
||||
}
|
||||
b'<' => {
|
||||
|
@ -98,6 +99,7 @@ impl TextScript {
|
|||
}
|
||||
|
||||
if !char_buf.is_empty() {
|
||||
put_varint(TSCOpCode::_STR as i32, &mut bytecode);
|
||||
put_string(&mut char_buf, &mut bytecode, encoding);
|
||||
}
|
||||
|
||||
|
@ -138,111 +140,111 @@ impl TextScript {
|
|||
iter: &mut Peekable<I>,
|
||||
out: &mut Vec<u8>,
|
||||
) -> GameResult {
|
||||
let instr = OpCode::from_str(code).map_err(|_| ParseError(format!("Unknown opcode: {}", code)))?;
|
||||
let instr = TSCOpCode::from_str(code).map_err(|_| ParseError(format!("Unknown opcode: {}", code)))?;
|
||||
|
||||
match instr {
|
||||
// Zero operand codes
|
||||
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 => {
|
||||
TSCOpCode::AEp
|
||||
| TSCOpCode::CAT
|
||||
| TSCOpCode::CIL
|
||||
| TSCOpCode::CLO
|
||||
| TSCOpCode::CLR
|
||||
| TSCOpCode::CPS
|
||||
| TSCOpCode::CRE
|
||||
| TSCOpCode::CSS
|
||||
| TSCOpCode::END
|
||||
| TSCOpCode::ESC
|
||||
| TSCOpCode::FLA
|
||||
| TSCOpCode::FMU
|
||||
| TSCOpCode::FRE
|
||||
| TSCOpCode::HMC
|
||||
| TSCOpCode::INI
|
||||
| TSCOpCode::KEY
|
||||
| TSCOpCode::LDP
|
||||
| TSCOpCode::MLP
|
||||
| TSCOpCode::MM0
|
||||
| TSCOpCode::MNA
|
||||
| TSCOpCode::MS2
|
||||
| TSCOpCode::MS3
|
||||
| TSCOpCode::MSG
|
||||
| TSCOpCode::NOD
|
||||
| TSCOpCode::PRI
|
||||
| TSCOpCode::RMU
|
||||
| TSCOpCode::SAT
|
||||
| TSCOpCode::SLP
|
||||
| TSCOpCode::SMC
|
||||
| TSCOpCode::SPS
|
||||
| TSCOpCode::STC
|
||||
| TSCOpCode::SVP
|
||||
| TSCOpCode::TUR
|
||||
| TSCOpCode::WAS
|
||||
| TSCOpCode::ZAM
|
||||
| TSCOpCode::HM2
|
||||
| TSCOpCode::POP
|
||||
| TSCOpCode::KE2
|
||||
| TSCOpCode::FR2 => {
|
||||
put_varint(instr as i32, out);
|
||||
}
|
||||
// One operand codes
|
||||
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::S2PJ
|
||||
| OpCode::PSH => {
|
||||
TSCOpCode::BOA
|
||||
| TSCOpCode::BSL
|
||||
| TSCOpCode::FOB
|
||||
| TSCOpCode::FOM
|
||||
| TSCOpCode::QUA
|
||||
| TSCOpCode::UNI
|
||||
| TSCOpCode::MYB
|
||||
| TSCOpCode::MYD
|
||||
| TSCOpCode::FAI
|
||||
| TSCOpCode::FAO
|
||||
| TSCOpCode::WAI
|
||||
| TSCOpCode::FAC
|
||||
| TSCOpCode::GIT
|
||||
| TSCOpCode::NUM
|
||||
| TSCOpCode::DNA
|
||||
| TSCOpCode::DNP
|
||||
| TSCOpCode::FLm
|
||||
| TSCOpCode::FLp
|
||||
| TSCOpCode::MPp
|
||||
| TSCOpCode::SKm
|
||||
| TSCOpCode::SKp
|
||||
| TSCOpCode::EQp
|
||||
| TSCOpCode::EQm
|
||||
| TSCOpCode::MLp
|
||||
| TSCOpCode::ITp
|
||||
| TSCOpCode::ITm
|
||||
| TSCOpCode::AMm
|
||||
| TSCOpCode::UNJ
|
||||
| TSCOpCode::MPJ
|
||||
| TSCOpCode::YNJ
|
||||
| TSCOpCode::EVE
|
||||
| TSCOpCode::XX1
|
||||
| TSCOpCode::SIL
|
||||
| TSCOpCode::LIp
|
||||
| TSCOpCode::SOU
|
||||
| TSCOpCode::CMU
|
||||
| TSCOpCode::SSS
|
||||
| TSCOpCode::ACH
|
||||
| TSCOpCode::S2MV
|
||||
| TSCOpCode::S2PJ
|
||||
| TSCOpCode::PSH => {
|
||||
let operand = read_number(iter)?;
|
||||
put_varint(instr as i32, out);
|
||||
put_varint(operand as i32, out);
|
||||
}
|
||||
// Two operand codes
|
||||
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 => {
|
||||
TSCOpCode::FON
|
||||
| TSCOpCode::MOV
|
||||
| TSCOpCode::AMp
|
||||
| TSCOpCode::NCJ
|
||||
| TSCOpCode::ECJ
|
||||
| TSCOpCode::FLJ
|
||||
| TSCOpCode::ITJ
|
||||
| TSCOpCode::SKJ
|
||||
| TSCOpCode::AMJ
|
||||
| TSCOpCode::SMP
|
||||
| TSCOpCode::PSp
|
||||
| TSCOpCode::IpN
|
||||
| TSCOpCode::FFm => {
|
||||
let operand_a = read_number(iter)?;
|
||||
if strict {
|
||||
expect_char(b':', iter)?;
|
||||
|
@ -256,7 +258,7 @@ impl TextScript {
|
|||
put_varint(operand_b as i32, out);
|
||||
}
|
||||
// Three operand codes
|
||||
OpCode::ANP | OpCode::CNP | OpCode::INP | OpCode::TAM | OpCode::CMP | OpCode::INJ => {
|
||||
TSCOpCode::ANP | TSCOpCode::CNP | TSCOpCode::INP | TSCOpCode::TAM | TSCOpCode::CMP | TSCOpCode::INJ => {
|
||||
let operand_a = read_number(iter)?;
|
||||
if strict {
|
||||
expect_char(b':', iter)?;
|
||||
|
@ -277,7 +279,7 @@ impl TextScript {
|
|||
put_varint(operand_c as i32, out);
|
||||
}
|
||||
// Four operand codes
|
||||
OpCode::TRA | OpCode::MNP | OpCode::SNP => {
|
||||
TSCOpCode::TRA | TSCOpCode::MNP | TSCOpCode::SNP => {
|
||||
let operand_a = read_number(iter)?;
|
||||
if strict {
|
||||
expect_char(b':', iter)?;
|
||||
|
@ -304,7 +306,7 @@ impl TextScript {
|
|||
put_varint(operand_c as i32, out);
|
||||
put_varint(operand_d as i32, out);
|
||||
}
|
||||
OpCode::_NOP | OpCode::_UNI | OpCode::_STR | OpCode::_END => {
|
||||
TSCOpCode::_NOP | TSCOpCode::_UNI | TSCOpCode::_STR | TSCOpCode::_END => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
@ -312,3 +314,102 @@ impl TextScript {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CreditScript {
|
||||
pub fn compile(data: &[u8], strict: bool, encoding: TextScriptEncoding) -> GameResult<CreditScript> {
|
||||
println!("data: {}", String::from_utf8_lossy(data));
|
||||
let mut labels = HashMap::new();
|
||||
let mut bytecode = Vec::new();
|
||||
let mut iter = data.iter().copied().peekable();
|
||||
|
||||
while let Some(chr) = iter.next() {
|
||||
match chr {
|
||||
b'/' => {
|
||||
put_varint(CreditOpCode::StopCredits as i32, &mut bytecode);
|
||||
}
|
||||
b'[' => {
|
||||
let mut char_buf = Vec::new();
|
||||
|
||||
while let Some(&chr) = iter.peek() {
|
||||
if chr == b']' {
|
||||
iter.next();
|
||||
break;
|
||||
}
|
||||
|
||||
char_buf.push(chr);
|
||||
iter.next();
|
||||
}
|
||||
|
||||
if let Ok(cast_tile) = read_number(&mut iter) {
|
||||
put_varint(CreditOpCode::PushLine as i32, &mut bytecode);
|
||||
put_varint((cast_tile as u16) as i32, &mut bytecode);
|
||||
put_string(&mut char_buf, &mut bytecode, encoding);
|
||||
}
|
||||
}
|
||||
b'-' => {
|
||||
let ticks = read_number(&mut iter)? as u16;
|
||||
|
||||
put_varint(CreditOpCode::Wait as i32, &mut bytecode);
|
||||
put_varint(ticks as i32, &mut bytecode);
|
||||
}
|
||||
b'+' => {
|
||||
let offset = read_number(&mut iter)?;
|
||||
|
||||
put_varint(CreditOpCode::ChangeXOffset as i32, &mut bytecode);
|
||||
put_varint(offset, &mut bytecode);
|
||||
}
|
||||
b'!' => {
|
||||
let music = read_number(&mut iter)? as u16;
|
||||
|
||||
put_varint(CreditOpCode::ChangeMusic as i32, &mut bytecode);
|
||||
put_varint(music as i32, &mut bytecode);
|
||||
}
|
||||
b'~' => {
|
||||
put_varint(CreditOpCode::FadeMusic as i32, &mut bytecode);
|
||||
}
|
||||
b'l' => {
|
||||
let label = read_number(&mut iter)? as u16;
|
||||
let pos = bytecode.len() as u32;
|
||||
|
||||
labels.insert(label, pos);
|
||||
}
|
||||
b'j' => {
|
||||
let label = read_number(&mut iter)? as u16;
|
||||
|
||||
put_varint(CreditOpCode::JumpLabel as i32, &mut bytecode);
|
||||
put_varint(label as i32, &mut bytecode);
|
||||
}
|
||||
b'f' => {
|
||||
let flag = read_number(&mut iter)? as u16;
|
||||
if strict {
|
||||
expect_char(b':', &mut iter)?;
|
||||
} else {
|
||||
iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?;
|
||||
}
|
||||
let label = read_number(&mut iter)? as u16;
|
||||
|
||||
put_varint(CreditOpCode::JumpFlag as i32, &mut bytecode);
|
||||
put_varint(flag as i32, &mut bytecode);
|
||||
put_varint(label as i32, &mut bytecode);
|
||||
}
|
||||
b'p' => {
|
||||
iter.next(); // idfk what's that for, in cs+ Credits.tsc it's '2'.
|
||||
|
||||
if strict {
|
||||
expect_char(b':', &mut iter)?;
|
||||
} else {
|
||||
iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?;
|
||||
}
|
||||
|
||||
let label = read_number(&mut iter)? as u16;
|
||||
|
||||
put_varint(CreditOpCode::JumpPlayer2 as i32, &mut bytecode);
|
||||
put_varint(label as i32, &mut bytecode);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CreditScript { labels, bytecode })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,216 @@
|
|||
pub struct CreditScript {
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::io::{Cursor, Seek, SeekFrom};
|
||||
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::scripting::tsc::bytecode_utils::{put_varint, read_cur_varint};
|
||||
use crate::scripting::tsc::encryption::decrypt_tsc;
|
||||
use crate::scripting::tsc::opcodes::CreditOpCode;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
pub struct CreditScript {
|
||||
pub(in crate::scripting::tsc) labels: HashMap<u16, u32>,
|
||||
pub(in crate::scripting::tsc) bytecode: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Default for CreditScript {
|
||||
fn default() -> Self {
|
||||
let mut bytecode = Vec::new();
|
||||
put_varint(CreditOpCode::StopCredits as i32, &mut bytecode);
|
||||
|
||||
CreditScript { labels: HashMap::new(), bytecode }
|
||||
}
|
||||
}
|
||||
|
||||
impl CreditScript {
|
||||
/// Loads, decrypts and compiles a credit script from specified stream.
|
||||
pub fn load_from<R: io::Read>(mut data: R, constants: &EngineConstants) -> GameResult<CreditScript> {
|
||||
let mut buf = Vec::new();
|
||||
data.read_to_end(&mut buf)?;
|
||||
|
||||
if constants.textscript.encrypted {
|
||||
decrypt_tsc(&mut buf);
|
||||
}
|
||||
|
||||
CreditScript::compile(&buf, false, constants.textscript.encoding)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum CreditScriptExecutionState {
|
||||
Ended,
|
||||
Running(u32),
|
||||
WaitTicks(u32, u16),
|
||||
}
|
||||
|
||||
pub struct CreditScriptLine {
|
||||
pub pos_x: f32,
|
||||
pub pos_y: f32,
|
||||
pub cast_id: u16,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
pub struct CreditScriptVM {
|
||||
pub state: CreditScriptExecutionState,
|
||||
pub lines: Vec<CreditScriptLine>,
|
||||
pub text_offset: f32,
|
||||
script: CreditScript,
|
||||
}
|
||||
|
||||
impl CreditScriptVM {
|
||||
pub fn new() -> CreditScriptVM {
|
||||
CreditScriptVM {
|
||||
state: CreditScriptExecutionState::Ended,
|
||||
lines: Vec::new(),
|
||||
text_offset: 0.0,
|
||||
script: CreditScript::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_script(&mut self, script: CreditScript) {
|
||||
self.reset();
|
||||
self.script = script;
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
self.reset();
|
||||
self.state = CreditScriptExecutionState::Running(0);
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.lines.clear();
|
||||
self.text_offset = 0.0;
|
||||
self.state = CreditScriptExecutionState::Ended;
|
||||
}
|
||||
|
||||
pub fn run(state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if state.creditscript_vm.state != CreditScriptExecutionState::Ended {
|
||||
for line in state.creditscript_vm.lines.iter_mut() {
|
||||
line.pos_y -= 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
state.creditscript_vm.lines.retain(|l| l.pos_y > -16.0);
|
||||
|
||||
loop {
|
||||
match state.creditscript_vm.state {
|
||||
CreditScriptExecutionState::Ended => {
|
||||
break;
|
||||
}
|
||||
CreditScriptExecutionState::Running(ip) => {
|
||||
let mut cursor = Cursor::new(&state.creditscript_vm.script.bytecode);
|
||||
cursor.seek(SeekFrom::Start(ip as u64))?;
|
||||
|
||||
let op: CreditOpCode = if let Some(op) = FromPrimitive::from_i32(
|
||||
read_cur_varint(&mut cursor).unwrap_or_else(|_| CreditOpCode::StopCredits as i32),
|
||||
) {
|
||||
op
|
||||
} else {
|
||||
state.creditscript_vm.reset();
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
match op {
|
||||
CreditOpCode::_NOP => {
|
||||
state.creditscript_vm.state = CreditScriptExecutionState::Running(cursor.position() as u32);
|
||||
}
|
||||
CreditOpCode::StopCredits => {
|
||||
state.creditscript_vm.state = CreditScriptExecutionState::Ended;
|
||||
}
|
||||
CreditOpCode::PushLine => {
|
||||
let cast_id = read_cur_varint(&mut cursor)? as u16;
|
||||
let text_len = read_cur_varint(&mut cursor)?;
|
||||
let mut text = String::new();
|
||||
text.reserve(text_len as usize);
|
||||
|
||||
for _ in 0..text_len {
|
||||
let chr =
|
||||
std::char::from_u32(read_cur_varint(&mut cursor)? as u32).unwrap_or('\u{fffd}');
|
||||
text.push(chr);
|
||||
}
|
||||
|
||||
let line = CreditScriptLine {
|
||||
pos_x: state.creditscript_vm.text_offset,
|
||||
pos_y: 256.0,
|
||||
cast_id,
|
||||
text,
|
||||
};
|
||||
|
||||
state.creditscript_vm.lines.push(line);
|
||||
state.creditscript_vm.state = CreditScriptExecutionState::Running(cursor.position() as u32);
|
||||
}
|
||||
CreditOpCode::Wait => {
|
||||
let ticks = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
state.creditscript_vm.state =
|
||||
CreditScriptExecutionState::WaitTicks(cursor.position() as u32, ticks);
|
||||
}
|
||||
CreditOpCode::ChangeXOffset => {
|
||||
let offset = read_cur_varint(&mut cursor)?;
|
||||
|
||||
state.creditscript_vm.text_offset = offset as f32;
|
||||
state.creditscript_vm.state = CreditScriptExecutionState::Running(cursor.position() as u32);
|
||||
}
|
||||
CreditOpCode::ChangeMusic => {
|
||||
let song = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
state.sound_manager.play_song(song as usize, &state.constants, &state.settings, ctx)?;
|
||||
|
||||
state.creditscript_vm.state = CreditScriptExecutionState::Running(cursor.position() as u32);
|
||||
}
|
||||
CreditOpCode::FadeMusic => {
|
||||
// todo
|
||||
|
||||
state.creditscript_vm.state = CreditScriptExecutionState::Running(cursor.position() as u32);
|
||||
}
|
||||
CreditOpCode::JumpLabel => {
|
||||
let label = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
if let Some(target) = state.creditscript_vm.script.labels.get(&label) {
|
||||
state.creditscript_vm.state = CreditScriptExecutionState::Running(*target);
|
||||
continue;
|
||||
}
|
||||
|
||||
state.creditscript_vm.state = CreditScriptExecutionState::Running(cursor.position() as u32);
|
||||
}
|
||||
CreditOpCode::JumpFlag => {
|
||||
let flag = read_cur_varint(&mut cursor)? as u16;
|
||||
let label = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
if state.get_flag(flag as usize) {
|
||||
if let Some(target) = state.creditscript_vm.script.labels.get(&label) {
|
||||
state.creditscript_vm.state = CreditScriptExecutionState::Running(*target);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
state.creditscript_vm.state = CreditScriptExecutionState::Running(cursor.position() as u32);
|
||||
}
|
||||
CreditOpCode::JumpPlayer2 => {
|
||||
let _label = read_cur_varint(&mut cursor)? as u16;
|
||||
// todo
|
||||
|
||||
state.creditscript_vm.state = CreditScriptExecutionState::Running(cursor.position() as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
CreditScriptExecutionState::WaitTicks(ip, ticks) => {
|
||||
if ticks == 0 {
|
||||
state.creditscript_vm.state = CreditScriptExecutionState::Running(ip);
|
||||
} else if ticks != 9999 {
|
||||
state.creditscript_vm.state = CreditScriptExecutionState::WaitTicks(ip, ticks - 1);
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use num_traits::FromPrimitive;
|
|||
use crate::framework::error::GameError::InvalidValue;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::scripting::tsc::bytecode_utils::read_cur_varint;
|
||||
use crate::scripting::tsc::opcodes::OpCode;
|
||||
use crate::scripting::tsc::opcodes::TSCOpCode;
|
||||
use crate::scripting::tsc::text_script::TextScript;
|
||||
|
||||
impl TextScript {
|
||||
|
@ -15,119 +15,119 @@ impl TextScript {
|
|||
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);
|
||||
let op_maybe: Option<TSCOpCode> = FromPrimitive::from_i32(op_num);
|
||||
|
||||
if let Some(op) = op_maybe {
|
||||
match op {
|
||||
// Zero operand codes
|
||||
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 => {
|
||||
TSCOpCode::AEp
|
||||
| TSCOpCode::CAT
|
||||
| TSCOpCode::CIL
|
||||
| TSCOpCode::CLO
|
||||
| TSCOpCode::CLR
|
||||
| TSCOpCode::CPS
|
||||
| TSCOpCode::CRE
|
||||
| TSCOpCode::CSS
|
||||
| TSCOpCode::END
|
||||
| TSCOpCode::ESC
|
||||
| TSCOpCode::FLA
|
||||
| TSCOpCode::FMU
|
||||
| TSCOpCode::FRE
|
||||
| TSCOpCode::HMC
|
||||
| TSCOpCode::INI
|
||||
| TSCOpCode::KEY
|
||||
| TSCOpCode::LDP
|
||||
| TSCOpCode::MLP
|
||||
| TSCOpCode::MM0
|
||||
| TSCOpCode::MNA
|
||||
| TSCOpCode::MS2
|
||||
| TSCOpCode::MS3
|
||||
| TSCOpCode::MSG
|
||||
| TSCOpCode::NOD
|
||||
| TSCOpCode::PRI
|
||||
| TSCOpCode::RMU
|
||||
| TSCOpCode::SAT
|
||||
| TSCOpCode::SLP
|
||||
| TSCOpCode::SMC
|
||||
| TSCOpCode::SPS
|
||||
| TSCOpCode::STC
|
||||
| TSCOpCode::SVP
|
||||
| TSCOpCode::TUR
|
||||
| TSCOpCode::WAS
|
||||
| TSCOpCode::ZAM
|
||||
| TSCOpCode::HM2
|
||||
| TSCOpCode::POP
|
||||
| TSCOpCode::KE2
|
||||
| TSCOpCode::FR2 => {
|
||||
result.push_str(format!("{:?}()\n", op).as_str());
|
||||
}
|
||||
// One operand codes
|
||||
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::S2PJ
|
||||
| OpCode::PSH => {
|
||||
TSCOpCode::BOA
|
||||
| TSCOpCode::BSL
|
||||
| TSCOpCode::FOB
|
||||
| TSCOpCode::FOM
|
||||
| TSCOpCode::QUA
|
||||
| TSCOpCode::UNI
|
||||
| TSCOpCode::MYB
|
||||
| TSCOpCode::MYD
|
||||
| TSCOpCode::FAI
|
||||
| TSCOpCode::FAO
|
||||
| TSCOpCode::WAI
|
||||
| TSCOpCode::FAC
|
||||
| TSCOpCode::GIT
|
||||
| TSCOpCode::NUM
|
||||
| TSCOpCode::DNA
|
||||
| TSCOpCode::DNP
|
||||
| TSCOpCode::FLm
|
||||
| TSCOpCode::FLp
|
||||
| TSCOpCode::MPp
|
||||
| TSCOpCode::SKm
|
||||
| TSCOpCode::SKp
|
||||
| TSCOpCode::EQp
|
||||
| TSCOpCode::EQm
|
||||
| TSCOpCode::MLp
|
||||
| TSCOpCode::ITp
|
||||
| TSCOpCode::ITm
|
||||
| TSCOpCode::AMm
|
||||
| TSCOpCode::UNJ
|
||||
| TSCOpCode::MPJ
|
||||
| TSCOpCode::YNJ
|
||||
| TSCOpCode::EVE
|
||||
| TSCOpCode::XX1
|
||||
| TSCOpCode::SIL
|
||||
| TSCOpCode::LIp
|
||||
| TSCOpCode::SOU
|
||||
| TSCOpCode::CMU
|
||||
| TSCOpCode::SSS
|
||||
| TSCOpCode::ACH
|
||||
| TSCOpCode::S2MV
|
||||
| TSCOpCode::S2PJ
|
||||
| TSCOpCode::PSH => {
|
||||
let par_a = read_cur_varint(&mut cursor)?;
|
||||
|
||||
result.push_str(format!("{:?}({})\n", op, par_a).as_str());
|
||||
}
|
||||
// Two operand codes
|
||||
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 => {
|
||||
TSCOpCode::FON
|
||||
| TSCOpCode::MOV
|
||||
| TSCOpCode::AMp
|
||||
| TSCOpCode::NCJ
|
||||
| TSCOpCode::ECJ
|
||||
| TSCOpCode::FLJ
|
||||
| TSCOpCode::ITJ
|
||||
| TSCOpCode::SKJ
|
||||
| TSCOpCode::AMJ
|
||||
| TSCOpCode::SMP
|
||||
| TSCOpCode::PSp
|
||||
| TSCOpCode::IpN
|
||||
| TSCOpCode::FFm => {
|
||||
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 => {
|
||||
TSCOpCode::ANP | TSCOpCode::CNP | TSCOpCode::INP | TSCOpCode::TAM | TSCOpCode::CMP | TSCOpCode::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)?;
|
||||
|
@ -135,7 +135,7 @@ impl TextScript {
|
|||
result.push_str(format!("{:?}({}, {}, {})\n", op, par_a, par_b, par_c).as_str());
|
||||
}
|
||||
// Four operand codes
|
||||
OpCode::TRA | OpCode::MNP | OpCode::SNP => {
|
||||
TSCOpCode::TRA | TSCOpCode::MNP | TSCOpCode::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)?;
|
||||
|
@ -143,7 +143,7 @@ impl TextScript {
|
|||
|
||||
result.push_str(format!("{:?}({}, {}, {}, {})\n", op, par_a, par_b, par_c, par_d).as_str());
|
||||
}
|
||||
OpCode::_STR => {
|
||||
TSCOpCode::_STR => {
|
||||
let len = read_cur_varint(&mut cursor)?;
|
||||
|
||||
result.push_str(format!("%string(len = {}, value = \"", len).as_str());
|
||||
|
@ -169,9 +169,9 @@ impl TextScript {
|
|||
}
|
||||
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"),
|
||||
TSCOpCode::_NOP => result.push_str("%no_op()\n"),
|
||||
TSCOpCode::_UNI => result.push_str("%unimplemented()\n"),
|
||||
TSCOpCode::_END => result.push_str("%end_marker()\n"),
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
|
|
13
src/scripting/tsc/encryption.rs
Normal file
13
src/scripting/tsc/encryption.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
pub fn decrypt_tsc(buf: &mut [u8]) {
|
||||
let half = buf.len() / 2;
|
||||
let key = if let Some(0) = buf.get(half) { 0xf9 } else { (-(*buf.get(half).unwrap() as isize)) as u8 };
|
||||
log::info!("Decrypting TSC using key {:#x}", key);
|
||||
|
||||
for (idx, byte) in buf.iter_mut().enumerate() {
|
||||
if idx == half {
|
||||
continue;
|
||||
}
|
||||
|
||||
*byte = byte.wrapping_add(key);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ mod bytecode_utils;
|
|||
mod compiler;
|
||||
pub mod credit_script;
|
||||
mod decompiler;
|
||||
mod encryption;
|
||||
mod opcodes;
|
||||
mod parse_utils;
|
||||
pub mod text_script;
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use num_derive::FromPrimitive;
|
||||
|
||||
/// Engine's text script VM operation codes.
|
||||
#[derive(EnumString, Debug, FromPrimitive, PartialEq)]
|
||||
#[repr(i32)]
|
||||
pub enum OpCode {
|
||||
#[derive(EnumString, Debug, FromPrimitive, PartialEq, Copy, Clone)]
|
||||
pub enum TSCOpCode {
|
||||
// ---- Internal opcodes (used by bytecode, no TSC representation)
|
||||
/// internal: no operation
|
||||
_NOP = 0,
|
||||
|
@ -267,3 +266,54 @@ pub enum OpCode {
|
|||
FR2,
|
||||
// ---- Custom opcodes, for use by modders ----
|
||||
}
|
||||
|
||||
#[derive(FromPrimitive, PartialEq, Copy, Clone)]
|
||||
pub enum CreditOpCode {
|
||||
/// Internal, no operation
|
||||
_NOP = 0,
|
||||
/// `/`
|
||||
///
|
||||
/// Arguments: `()`
|
||||
StopCredits,
|
||||
|
||||
/// `[{text: string}]{cast_tile: number}`
|
||||
///
|
||||
/// Arguments: `(cast_tile: varint, text_len: varint, text: [varint; text_len])`
|
||||
PushLine,
|
||||
|
||||
/// `-{ticks: number}`
|
||||
///
|
||||
/// Arguments: `(ticks: varint)`
|
||||
Wait,
|
||||
|
||||
/// `+{offset: number}`
|
||||
///
|
||||
/// Arguments: `(offset: varint)`
|
||||
ChangeXOffset,
|
||||
|
||||
/// `!{music_id: number}`
|
||||
///
|
||||
/// Arguments: `(music_id: varint)`
|
||||
ChangeMusic,
|
||||
|
||||
/// `~`
|
||||
///
|
||||
/// Arguments: `()`
|
||||
FadeMusic,
|
||||
|
||||
/// `j{label: number}`
|
||||
///
|
||||
/// Arguments: `(label: varint)`
|
||||
JumpLabel,
|
||||
|
||||
/// `f{flag: number}:{label: number}`
|
||||
///
|
||||
/// Arguments: `(flag: varint, label: varint)`
|
||||
JumpFlag,
|
||||
|
||||
// ---- Cave Story+ (Switch) specific opcodes ----
|
||||
/// `p2:{label: number}`
|
||||
///
|
||||
/// Arguments: `(label: varint)`
|
||||
JumpPlayer2,
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -25,6 +25,7 @@ use crate::scene::title_scene::TitleScene;
|
|||
use crate::scene::Scene;
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
use crate::scripting::lua::LuaScriptingState;
|
||||
use crate::scripting::tsc::credit_script::CreditScriptVM;
|
||||
use crate::settings::Settings;
|
||||
use crate::sound::SoundManager;
|
||||
use crate::stage::StageData;
|
||||
|
@ -137,6 +138,7 @@ pub struct SharedGameState {
|
|||
pub preferred_viewport_size: (f32, f32),
|
||||
pub next_scene: Option<Box<dyn Scene>>,
|
||||
pub textscript_vm: TextScriptVM,
|
||||
pub creditscript_vm: CreditScriptVM,
|
||||
pub lightmap_canvas: Option<Box<dyn BackendTexture>>,
|
||||
pub season: Season,
|
||||
pub constants: EngineConstants,
|
||||
|
@ -237,6 +239,7 @@ impl SharedGameState {
|
|||
preferred_viewport_size: (320.0, 240.0),
|
||||
next_scene: None,
|
||||
textscript_vm: TextScriptVM::new(),
|
||||
creditscript_vm: CreditScriptVM::new(),
|
||||
lightmap_canvas: None,
|
||||
season,
|
||||
constants,
|
||||
|
|
Loading…
Reference in a new issue