mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-03-24 02:49:21 +00:00
initial switch data support
This commit is contained in:
parent
2b148fe3ed
commit
449a503fc5
|
@ -200,6 +200,8 @@ pub struct NPCConsts {
|
|||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TextScriptConsts {
|
||||
pub encoding: TextScriptEncoding,
|
||||
pub encrypted: bool,
|
||||
pub animated_face_pics: bool,
|
||||
pub textbox_rect_top: Rect<usize>,
|
||||
pub textbox_rect_middle: Rect<usize>,
|
||||
pub textbox_rect_bottom: Rect<usize>,
|
||||
|
@ -230,6 +232,7 @@ pub struct TitleConsts {
|
|||
#[derive(Debug)]
|
||||
pub struct EngineConstants {
|
||||
pub is_cs_plus: bool,
|
||||
pub is_switch: bool,
|
||||
pub my_char: MyCharConsts,
|
||||
pub booster: BoosterConsts,
|
||||
pub caret: CaretConsts,
|
||||
|
@ -249,6 +252,7 @@ impl Clone for EngineConstants {
|
|||
fn clone(&self) -> EngineConstants {
|
||||
EngineConstants {
|
||||
is_cs_plus: self.is_cs_plus,
|
||||
is_switch: self.is_switch,
|
||||
my_char: self.my_char,
|
||||
booster: self.booster,
|
||||
caret: self.caret.clone(),
|
||||
|
@ -270,6 +274,7 @@ impl EngineConstants {
|
|||
pub fn defaults() -> Self {
|
||||
EngineConstants {
|
||||
is_cs_plus: false,
|
||||
is_switch: false,
|
||||
my_char: MyCharConsts {
|
||||
display_bounds: Rect { left: 8 * 0x200, top: 8 * 0x200, right: 8 * 0x200, bottom: 8 * 0x200 },
|
||||
hit_bounds: Rect { left: 5 * 0x200, top: 8 * 0x200, right: 5 * 0x200, bottom: 8 * 0x200 },
|
||||
|
@ -1069,6 +1074,11 @@ impl EngineConstants {
|
|||
"Face_0" => (288, 240), // nxengine
|
||||
"Face_1" => (288, 240), // nxengine
|
||||
"Face_2" => (288, 240), // nxengine
|
||||
"Face1" => (288, 240), // switch
|
||||
"Face2" => (288, 240), // switch
|
||||
"Face3" => (288, 240), // switch
|
||||
"Face4" => (288, 240), // switch
|
||||
"Face5" => (288, 240), // switch
|
||||
"Fade" => (256, 32),
|
||||
"ItemImage" => (256, 128),
|
||||
"Loading" => (64, 8),
|
||||
|
@ -1154,7 +1164,9 @@ impl EngineConstants {
|
|||
"Title" => (320, 48),
|
||||
},
|
||||
textscript: TextScriptConsts {
|
||||
encoding: TextScriptEncoding::UTF8,
|
||||
encoding: TextScriptEncoding::ShiftJIS,
|
||||
encrypted: true,
|
||||
animated_face_pics: false,
|
||||
textbox_rect_top: Rect { left: 0, top: 0, right: 244, bottom: 8 },
|
||||
textbox_rect_middle: Rect { left: 0, top: 8, right: 244, bottom: 16 },
|
||||
textbox_rect_bottom: Rect { left: 0, top: 16, right: 244, bottom: 24 },
|
||||
|
@ -1183,7 +1195,7 @@ impl EngineConstants {
|
|||
font_space_offset: -3.0,
|
||||
organya_paths: vec![
|
||||
str!("/org/"), // NXEngine
|
||||
str!("/base/Org/"), // CS+
|
||||
str!("/base/Org/"), // CS+ PC
|
||||
str!("/Resource/ORG/"), // CSE2E
|
||||
],
|
||||
}
|
||||
|
@ -1205,5 +1217,13 @@ impl EngineConstants {
|
|||
|
||||
pub fn apply_csplus_nx_patches(&mut self) {
|
||||
info!("Applying Switch-specific Cave Story+ constants patches...");
|
||||
|
||||
self.is_switch = true;
|
||||
self.tex_sizes.insert(str!("bkMoon"), (427, 240));
|
||||
self.tex_sizes.insert(str!("bkFog"), (427, 240));
|
||||
self.title.logo_rect = Rect { left: 0, top: 0, right: 214, bottom: 62 };
|
||||
self.textscript.encoding = TextScriptEncoding::UTF8;
|
||||
self.textscript.encrypted = false;
|
||||
self.textscript.animated_face_pics = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::path;
|
||||
#[cfg(debug_assertions)]
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
/// We re-export winit so it's easy for people to use the same version as we are
|
||||
/// without having to mess around figuring it out.
|
||||
pub use winit;
|
||||
|
@ -7,7 +12,7 @@ use crate::ggez::conf;
|
|||
use crate::ggez::error::GameResult;
|
||||
use crate::ggez::event::winit_event;
|
||||
use crate::ggez::filesystem::Filesystem;
|
||||
use crate::ggez::graphics::{self, Point2};
|
||||
use crate::ggez::graphics::{self, FilterMode, Point2};
|
||||
use crate::ggez::input::{gamepad, keyboard, mouse};
|
||||
use crate::ggez::timer;
|
||||
|
||||
|
@ -56,6 +61,8 @@ pub struct Context {
|
|||
/// Set this with `ggez::event::quit()`.
|
||||
pub continuing: bool,
|
||||
|
||||
pub filter_mode: FilterMode,
|
||||
|
||||
/// Context-specific unique ID.
|
||||
/// Compiles to nothing in release mode, and so
|
||||
/// vanishes; meanwhile we get dead-code warnings.
|
||||
|
@ -102,6 +109,7 @@ impl Context {
|
|||
keyboard_context,
|
||||
gamepad_context,
|
||||
mouse_context,
|
||||
filter_mode: FilterMode::Nearest,
|
||||
|
||||
debug_id,
|
||||
};
|
||||
|
@ -143,12 +151,12 @@ impl Context {
|
|||
}
|
||||
winit_event::WindowEvent::KeyboardInput {
|
||||
input:
|
||||
winit::KeyboardInput {
|
||||
state,
|
||||
virtual_keycode: Some(keycode),
|
||||
modifiers,
|
||||
..
|
||||
},
|
||||
winit::KeyboardInput {
|
||||
state,
|
||||
virtual_keycode: Some(keycode),
|
||||
modifiers,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
let pressed = match state {
|
||||
|
@ -176,9 +184,6 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::path;
|
||||
|
||||
/// A builder object for creating a [`Context`](struct.Context.html).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ContextBuilder {
|
||||
|
@ -237,8 +242,8 @@ impl ContextBuilder {
|
|||
/// Add a new read-only filesystem path to the places to search
|
||||
/// for resources.
|
||||
pub fn add_resource_path<T>(mut self, path: T) -> Self
|
||||
where
|
||||
T: Into<path::PathBuf>,
|
||||
where
|
||||
T: Into<path::PathBuf>,
|
||||
{
|
||||
self.paths.push(path.into());
|
||||
self
|
||||
|
@ -271,8 +276,6 @@ impl ContextBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
#[cfg(debug_assertions)]
|
||||
static DEBUG_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
|
@ -285,6 +288,7 @@ static DEBUG_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
|||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) struct DebugId(u32);
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub(crate) struct DebugId;
|
||||
|
|
|
@ -472,7 +472,7 @@ impl DrawMode {
|
|||
}
|
||||
|
||||
/// Specifies what blending method to use when scaling up/down images.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum FilterMode {
|
||||
/// Use linear interpolation (ie, smooth)
|
||||
Linear,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::weapon::{Weapon, WeaponLevel, WeaponType};
|
||||
|
@ -41,6 +43,11 @@ impl Inventory {
|
|||
pub fn add_item(&mut self, item_id: u16) {
|
||||
if !self.has_item(item_id) {
|
||||
self.items.push(Item(item_id, 1));
|
||||
} else {
|
||||
if let Some(item) = self.get_item(item_id) {
|
||||
item.1 += 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,6 +74,16 @@ impl Inventory {
|
|||
self.items.iter().any(|item| item.0 == item_id)
|
||||
}
|
||||
|
||||
pub fn has_item_amount(&self, item_id: u16, operator: Ordering, amount: u16) -> bool {
|
||||
let result = self.items.iter().any(|item| item.0 == item_id && item.1.cmp(&amount) == operator);
|
||||
|
||||
if (operator == Ordering::Equal && amount == 0 && !result) || (operator == Ordering::Less && !self.has_item(item_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn add_weapon(&mut self, weapon_id: WeaponType, max_ammo: u16) -> &mut Weapon {
|
||||
if !self.has_weapon(weapon_id) {
|
||||
self.weapons.push(Weapon::new(
|
||||
|
@ -230,3 +247,34 @@ impl Inventory {
|
|||
self.weapons.iter().any(|weapon| weapon.wtype == wtype)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inventory_test() {
|
||||
let mut inventory = Inventory::new();
|
||||
|
||||
inventory.add_item(3);
|
||||
assert_eq!(inventory.has_item(2), false);
|
||||
assert_eq!(inventory.has_item(3), true);
|
||||
|
||||
assert_eq!(inventory.has_item_amount(3, Ordering::Equal, 1), true);
|
||||
assert_eq!(inventory.has_item_amount(3, Ordering::Less, 2), true);
|
||||
inventory.consume_item(3);
|
||||
|
||||
assert_eq!(inventory.has_item_amount(3, Ordering::Equal, 0), true);
|
||||
assert_eq!(inventory.has_item_amount(3, Ordering::Less, 2), true);
|
||||
|
||||
inventory.add_item(2);
|
||||
assert_eq!(inventory.has_item(2), true);
|
||||
assert_eq!(inventory.has_item_amount(2, Ordering::Equal, 1), true);
|
||||
assert_eq!(inventory.has_item_amount(2, Ordering::Less, 1), false);
|
||||
|
||||
inventory.add_item(4);
|
||||
inventory.add_item(4);
|
||||
inventory.add_item(4);
|
||||
inventory.add_item(4);
|
||||
|
||||
assert_eq!(inventory.has_item(4), true);
|
||||
assert_eq!(inventory.has_item_amount(4, Ordering::Greater, 3), true);
|
||||
assert_eq!(inventory.has_item_amount(4, Ordering::Equal, 4), true);
|
||||
assert_eq!(inventory.has_item_amount(4, Ordering::Less, 2), false);
|
||||
}
|
||||
|
|
|
@ -178,12 +178,14 @@ impl Game {
|
|||
pub fn main() -> GameResult {
|
||||
pretty_env_logger::env_logger::init_from_env(Env::default().default_filter_or("info"));
|
||||
|
||||
let resource_dir = if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
|
||||
let resource_dir = if let Ok(data_dir) = env::var("CAVESTORY_DATA_DIR") {
|
||||
path::PathBuf::from(data_dir)
|
||||
} else if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
|
||||
let mut path = path::PathBuf::from(manifest_dir);
|
||||
path.push("data");
|
||||
path
|
||||
} else {
|
||||
path::PathBuf::from(&env::var("CAVESTORY_DATA_DIR").unwrap_or(str!("data")))
|
||||
path::PathBuf::from("data")
|
||||
};
|
||||
|
||||
info!("Resource directory: {:?}", resource_dir);
|
||||
|
|
|
@ -50,6 +50,9 @@ pub enum Alignment {
|
|||
Right,
|
||||
}
|
||||
|
||||
static FACE_TEX: &str = "Face";
|
||||
static SWITCH_FACE_TEX: [&str; 4] = ["Face1", "Face2", "Face3", "Face4"];
|
||||
|
||||
impl GameScene {
|
||||
pub fn new(state: &mut SharedGameState, ctx: &mut Context, id: usize) -> GameResult<Self> {
|
||||
info!("Loading stage {} ({})", id, &state.stages[id].map);
|
||||
|
@ -454,7 +457,12 @@ impl GameScene {
|
|||
}
|
||||
|
||||
if state.textscript_vm.face != 0 {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Face")?;
|
||||
let tex_name = if state.constants.textscript.animated_face_pics {
|
||||
SWITCH_FACE_TEX[0]
|
||||
} else {
|
||||
FACE_TEX
|
||||
};
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, tex_name)?;
|
||||
|
||||
batch.add_rect(left_pos + 14.0, top_pos + 8.0, &Rect::<usize>::new_size(
|
||||
(state.textscript_vm.face as usize % 6) * 48,
|
||||
|
@ -668,7 +676,7 @@ impl GameScene {
|
|||
|
||||
impl Scene for GameScene {
|
||||
fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
state.textscript_vm.set_scene_script(self.stage.load_text_script(&state.base_path, ctx)?);
|
||||
state.textscript_vm.set_scene_script(self.stage.load_text_script(&state.base_path, &state.constants, ctx)?);
|
||||
state.textscript_vm.suspend = false;
|
||||
|
||||
let npcs = self.stage.load_npcs(&state.base_path, ctx)?;
|
||||
|
|
|
@ -24,9 +24,11 @@ impl Scene for LoadingScene {
|
|||
if self.tick == 1 {
|
||||
let stages = StageData::load_stage_table(ctx, &state.base_path)?;
|
||||
state.stages = stages;
|
||||
let npc_table = NPCTable::load_from(filesystem::open(ctx, [&state.base_path, "/npc.tbl"].join(""))?)?;
|
||||
let npc_tbl = filesystem::open(ctx, [&state.base_path, "/npc.tbl"].join(""))?;
|
||||
let npc_table = NPCTable::load_from(npc_tbl)?;
|
||||
state.npc_table = npc_table;
|
||||
let head_script = TextScript::load_from(filesystem::open(ctx, [&state.base_path, "/Head.tsc"].join(""))?)?;
|
||||
let head_tsc = filesystem::open(ctx, [&state.base_path, "/Head.tsc"].join(""))?;
|
||||
let head_script = TextScript::load_from(head_tsc, &state.constants)?;
|
||||
state.textscript_vm.set_global_script(head_script);
|
||||
|
||||
state.next_scene = Some(Box::new(TitleScene::new()));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::common::Rect;
|
||||
use crate::ggez::{Context, GameResult, graphics};
|
||||
use crate::ggez::graphics::Color;
|
||||
use crate::ggez::graphics::{Color, FilterMode};
|
||||
use crate::menu::{Menu, MenuEntry, MenuSelectionResult};
|
||||
use crate::scene::Scene;
|
||||
use crate::shared_game_state::{SharedGameState, TimingMode};
|
||||
|
@ -91,6 +91,7 @@ impl Scene for TitleScene {
|
|||
self.main_menu.push_entry(MenuEntry::Active("Quit".to_string()));
|
||||
|
||||
self.option_menu.push_entry(MenuEntry::Toggle("50 FPS timing".to_string(), state.timing_mode == TimingMode::_50Hz));
|
||||
self.option_menu.push_entry(MenuEntry::Toggle("Linear scaling".to_string(), ctx.filter_mode == FilterMode::Linear));
|
||||
self.option_menu.push_entry(MenuEntry::Toggle("2x Speed hack".to_string(), state.speed_hack));
|
||||
self.option_menu.push_entry(MenuEntry::Active("Join our Discord".to_string()));
|
||||
self.option_menu.push_entry(MenuEntry::Disabled(DISCORD_LINK.to_owned()));
|
||||
|
@ -140,17 +141,27 @@ impl Scene for TitleScene {
|
|||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(1, toggle) => {
|
||||
if let MenuEntry::Toggle(_, value) = toggle {
|
||||
match ctx.filter_mode {
|
||||
FilterMode::Linear => { ctx.filter_mode = FilterMode::Nearest }
|
||||
FilterMode::Nearest => { ctx.filter_mode = FilterMode::Linear }
|
||||
}
|
||||
|
||||
*value = ctx.filter_mode == FilterMode::Linear;
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(2, toggle) => {
|
||||
if let MenuEntry::Toggle(_, value) = toggle {
|
||||
*value = !(*value);
|
||||
state.set_speed_hack(*value);
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(2, _) => {
|
||||
MenuSelectionResult::Selected(3, _) => {
|
||||
if let Err(e) = webbrowser::open(DISCORD_LINK) {
|
||||
log::warn!("Error opening web browser: {}", e);
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(4, _) | MenuSelectionResult::Canceled => {
|
||||
MenuSelectionResult::Selected(5, _) | MenuSelectionResult::Canceled => {
|
||||
self.current_menu = CurrentMenu::MainMenu;
|
||||
}
|
||||
_ => {}
|
||||
|
@ -192,7 +203,9 @@ impl Scene for TitleScene {
|
|||
|
||||
self.draw_text_centered(ENGINE_VERSION, state.canvas_size.1 - 15.0, state, ctx)?;
|
||||
|
||||
if state.constants.is_cs_plus {
|
||||
if state.constants.is_switch {
|
||||
self.draw_text_centered(COPYRIGHT_NICALIS_SWITCH, state.canvas_size.1 - 30.0, state, ctx)?;
|
||||
} else if state.constants.is_cs_plus {
|
||||
self.draw_text_centered(COPYRIGHT_NICALIS, state.canvas_size.1 - 30.0, state, ctx)?;
|
||||
} else {
|
||||
self.draw_text_centered(COPYRIGHT_PIXEL, state.canvas_size.1 - 30.0, state, ctx)?;
|
||||
|
|
|
@ -5,11 +5,12 @@ use byteorder::LE;
|
|||
use byteorder::ReadBytesExt;
|
||||
use log::info;
|
||||
|
||||
use crate::encoding::read_cur_shift_jis;
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::ggez::{Context, filesystem, GameResult};
|
||||
use crate::ggez::GameError::ResourceLoadError;
|
||||
use crate::map::{Map, NPCData};
|
||||
use crate::text_script::TextScript;
|
||||
use crate::encoding::read_cur_shift_jis;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NpcType {
|
||||
|
@ -385,9 +386,9 @@ impl Stage {
|
|||
Ok(stage)
|
||||
}
|
||||
|
||||
pub fn load_text_script(&mut self, root: &str, ctx: &mut Context) -> GameResult<TextScript> {
|
||||
pub fn load_text_script(&mut self, root: &str, constants: &EngineConstants, ctx: &mut Context) -> GameResult<TextScript> {
|
||||
let tsc_file = filesystem::open(ctx, [root, "Stage/", &self.data.map, ".tsc"].join(""))?;
|
||||
let text_script = TextScript::load_from(tsc_file)?;
|
||||
let text_script = TextScript::load_from(tsc_file, constants)?;
|
||||
|
||||
Ok(text_script)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ use num_traits::{clamp, FromPrimitive};
|
|||
use crate::bitfield;
|
||||
use crate::common::{Direction, FadeDirection, FadeState};
|
||||
use crate::encoding::{read_cur_shift_jis, read_cur_wtf8};
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::ggez::{Context, GameResult};
|
||||
use crate::ggez::GameError::ParseError;
|
||||
|
@ -166,6 +167,18 @@ pub enum OpCode {
|
|||
/// <ACHXXXX, triggers a Steam achievement.
|
||||
ACH,
|
||||
|
||||
|
||||
// ---- Cave Story+ (Switch) specific opcodes ----
|
||||
/// <HM2, in "you've never been seen again" script, name and context of other opcodes suggests it might be second player related
|
||||
/// HMC for player 2 i think?
|
||||
HM2,
|
||||
/// <2MV:xxxx, context suggests it's probably MOV for player 2 but what's the operand for?
|
||||
#[strum(serialize = "2MV")]
|
||||
S2MV,
|
||||
/// <INJ:xxxx:yyyy:zzzz, xxxx = item id, yyyy = ???, zzzz = event id, a variant of <ITJ
|
||||
/// seems like a ITJ which jumps if there's a specific number of items but i'm not sure
|
||||
INJ,
|
||||
|
||||
// ---- Custom opcodes, for use by modders ----
|
||||
}
|
||||
|
||||
|
@ -1067,7 +1080,7 @@ impl TextScriptVM {
|
|||
OpCode::CAT | OpCode::CIL | OpCode::CPS |
|
||||
OpCode::CRE | OpCode::CSS | OpCode::FLA | OpCode::MLP |
|
||||
OpCode::SAT | OpCode::SLP | OpCode::SPS |
|
||||
OpCode::STC | OpCode::SVP | OpCode::TUR => {
|
||||
OpCode::STC | OpCode::SVP | OpCode::TUR | OpCode::HM2 => {
|
||||
log::warn!("unimplemented opcode: {:?}", op);
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
|
@ -1076,7 +1089,7 @@ impl TextScriptVM {
|
|||
OpCode::BOA | OpCode::BSL | OpCode::FOB | OpCode::NUM | OpCode::DNA |
|
||||
OpCode::MPp | OpCode::SKm | OpCode::SKp |
|
||||
OpCode::UNJ | OpCode::MPJ | OpCode::XX1 | OpCode::SIL |
|
||||
OpCode::SSS | OpCode::ACH => {
|
||||
OpCode::SSS | OpCode::ACH | OpCode::S2MV => {
|
||||
let par_a = read_cur_varint(&mut cursor)?;
|
||||
|
||||
log::warn!("unimplemented opcode: {:?} {}", op, par_a);
|
||||
|
@ -1093,7 +1106,7 @@ impl TextScriptVM {
|
|||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
// Three operand codes
|
||||
OpCode::TAM => {
|
||||
OpCode::TAM | 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)?;
|
||||
|
@ -1157,26 +1170,29 @@ impl TextScript {
|
|||
}
|
||||
|
||||
/// Loads, decrypts and compiles a text script from specified stream.
|
||||
pub fn load_from<R: io::Read>(mut data: R) -> GameResult<TextScript> {
|
||||
pub fn load_from<R: io::Read>(mut data: R, constants: &EngineConstants) -> GameResult<TextScript> {
|
||||
let mut buf = Vec::new();
|
||||
data.read_to_end(&mut buf)?;
|
||||
|
||||
let half = buf.len() / 2;
|
||||
let key = if let Some(0) = buf.get(half) {
|
||||
0xf9
|
||||
} else {
|
||||
(-(*buf.get(half).unwrap() as isize)) as u8
|
||||
};
|
||||
if constants.textscript.encrypted {
|
||||
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;
|
||||
for (idx, byte) in buf.iter_mut().enumerate() {
|
||||
if idx == half {
|
||||
continue;
|
||||
}
|
||||
|
||||
*byte = byte.wrapping_add(key);
|
||||
}
|
||||
|
||||
*byte = byte.wrapping_add(key);
|
||||
}
|
||||
|
||||
TextScript::compile(&buf, false)
|
||||
TextScript::compile(&buf, false, constants.textscript.encoding)
|
||||
}
|
||||
|
||||
pub fn get_event_ids(&self) -> Vec<u16> {
|
||||
|
@ -1184,7 +1200,7 @@ impl TextScript {
|
|||
}
|
||||
|
||||
/// Compiles a decrypted text script data into internal bytecode.
|
||||
pub fn compile(data: &[u8], strict: bool) -> GameResult<TextScript> {
|
||||
pub fn compile(data: &[u8], strict: bool, encoding: TextScriptEncoding) -> GameResult<TextScript> {
|
||||
log::info!("data: {}", String::from_utf8_lossy(data));
|
||||
|
||||
let mut event_map = HashMap::new();
|
||||
|
@ -1210,7 +1226,7 @@ impl TextScript {
|
|||
}
|
||||
}
|
||||
|
||||
let bytecode = TextScript::compile_event(&mut iter, strict, TextScriptEncoding::ShiftJIS)?;
|
||||
let bytecode = TextScript::compile_event(&mut iter, strict, encoding)?;
|
||||
log::info!("Successfully compiled event #{} ({} bytes generated).", event_num, bytecode.len());
|
||||
event_map.insert(event_num, bytecode);
|
||||
}
|
||||
|
@ -1281,11 +1297,11 @@ impl TextScript {
|
|||
let mut chars = 0;
|
||||
|
||||
while remaining > 0 {
|
||||
let (consumed, chr) = if encoding == TextScriptEncoding::UTF8 {
|
||||
read_cur_wtf8(&mut cursor, remaining)
|
||||
} else {
|
||||
read_cur_shift_jis(&mut cursor, remaining)
|
||||
let (consumed, chr) = match encoding {
|
||||
TextScriptEncoding::UTF8 => read_cur_wtf8(&mut cursor, remaining),
|
||||
TextScriptEncoding::ShiftJIS => read_cur_shift_jis(&mut cursor, remaining),
|
||||
};
|
||||
|
||||
remaining -= consumed;
|
||||
chars += 1;
|
||||
|
||||
|
@ -1339,7 +1355,7 @@ impl TextScript {
|
|||
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::STC | OpCode::SVP | OpCode::TUR | OpCode::WAS | OpCode::ZAM | OpCode::HM2 => {
|
||||
TextScript::put_varint(instr as i32, out);
|
||||
}
|
||||
// One operand codes
|
||||
|
@ -1349,7 +1365,7 @@ impl TextScript {
|
|||
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::SSS | OpCode::ACH | OpCode::S2MV => {
|
||||
let operand = TextScript::read_number(iter)?;
|
||||
TextScript::put_varint(instr as i32, out);
|
||||
TextScript::put_varint(operand as i32, out);
|
||||
|
@ -1366,7 +1382,7 @@ impl TextScript {
|
|||
TextScript::put_varint(operand_b as i32, out);
|
||||
}
|
||||
// Three operand codes
|
||||
OpCode::ANP | OpCode::CNP | OpCode::INP | OpCode::TAM | OpCode::CMP => {
|
||||
OpCode::ANP | OpCode::CNP | OpCode::INP | OpCode::TAM | OpCode::CMP | OpCode::INJ => {
|
||||
let operand_a = TextScript::read_number(iter)?;
|
||||
if strict { TextScript::expect_char(b':', iter)?; } else { iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?; }
|
||||
let operand_b = TextScript::read_number(iter)?;
|
||||
|
|
|
@ -96,7 +96,7 @@ impl SizedBatch {
|
|||
}
|
||||
|
||||
pub fn draw(&mut self, ctx: &mut Context) -> GameResult {
|
||||
self.batch.set_filter(FilterMode::Nearest);
|
||||
self.batch.set_filter(ctx.filter_mode);
|
||||
self.batch.draw(ctx, DrawParam::new())?;
|
||||
self.batch.clear();
|
||||
Ok(())
|
||||
|
|
Loading…
Reference in a new issue