From 4a7559e2bbd97f9862778be92901d3c853da8b0f Mon Sep 17 00:00:00 2001 From: Aubrey Taylor Date: Wed, 12 Feb 2025 10:43:23 -0600 Subject: [PATCH] work on enemies, stage loading and rendering --- Cargo.lock | 11 + Cargo.toml | 9 +- flake.nix | 6 + instructions/Cargo.toml | 10 + .../vm/opcodes.rs => instructions/src/anm.rs | 82 +- instructions/src/ecl.rs | 1087 +++++++++++++++++ instructions/src/lib.rs | 5 + {src/game => instructions/src}/param.rs | 0 instructions/src/std.rs | 149 +++ macros/src/lib.rs | 139 ++- src/engine.rs | 31 +- src/game/anm/loaded_file.rs | 21 +- src/game/anm/manager/loading.rs | 15 +- src/game/anm/manager/mod.rs | 16 +- src/game/anm/manager/rendering.rs | 69 +- src/game/anm/vm/execute.rs | 29 +- src/game/anm/vm/mod.rs | 24 +- src/game/enemy/manager.rs | 4 - src/game/enemy/mod.rs | 3 - src/game/enemy/vm/opcodes.rs | 343 ------ src/game/mod.rs | 15 +- src/game/snd/bgm/format.rs | 6 +- src/game/snd/bgm/mod.rs | 2 +- src/game/stage/enemy/execute.rs | 6 + src/game/{enemy/vm => stage/enemy}/flags.rs | 5 +- src/game/{ => stage}/enemy/loaded_file.rs | 39 +- src/game/{enemy/vm => stage/enemy}/mod.rs | 5 +- src/game/stage/mod.rs | 71 ++ src/game/stage/vm/loaded_file.rs | 58 + src/game/stage/vm/mod.rs | 27 + src/game/states/gameplay/mod.rs | 49 +- src/game/states/loading.rs | 35 +- src/game/states/mod.rs | 19 +- src/game/states/title_mof/main_menu.rs | 218 ++++ src/game/states/title_mof/mod.rs | 72 ++ src/game/states/{title.rs => title_sa.rs} | 26 +- src/main.rs | 16 +- src/utils/context.rs | 10 +- utils/th10.eclm | 2 +- 39 files changed, 2197 insertions(+), 537 deletions(-) create mode 100644 instructions/Cargo.toml rename src/game/anm/vm/opcodes.rs => instructions/src/anm.rs (91%) create mode 100644 instructions/src/ecl.rs create mode 100644 instructions/src/lib.rs rename {src/game => instructions/src}/param.rs (100%) create mode 100644 instructions/src/std.rs delete mode 100644 src/game/enemy/manager.rs delete mode 100644 src/game/enemy/mod.rs delete mode 100644 src/game/enemy/vm/opcodes.rs create mode 100644 src/game/stage/enemy/execute.rs rename src/game/{enemy/vm => stage/enemy}/flags.rs (70%) rename src/game/{ => stage}/enemy/loaded_file.rs (61%) rename src/game/{enemy/vm => stage/enemy}/mod.rs (86%) create mode 100644 src/game/stage/mod.rs create mode 100644 src/game/stage/vm/loaded_file.rs create mode 100644 src/game/stage/vm/mod.rs create mode 100644 src/game/states/title_mof/main_menu.rs create mode 100644 src/game/states/title_mof/mod.rs rename src/game/states/{title.rs => title_sa.rs} (57%) diff --git a/Cargo.lock b/Cargo.lock index e797640..e6af4ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -611,6 +611,7 @@ dependencies = [ "glam", "heapless", "identconv", + "instructions", "log", "macros", "memmap2", @@ -1594,6 +1595,16 @@ dependencies = [ "hashbrown 0.15.0", ] +[[package]] +name = "instructions" +version = "0.1.0" +dependencies = [ + "macros", + "num-derive", + "num-traits", + "truth", +] + [[package]] name = "is-terminal" version = "0.4.13" diff --git a/Cargo.toml b/Cargo.toml index 345b03c..5f83106 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,9 @@ +[workspace] +resolver = "2" +members = ["instructions", "macros"] +[workspace.dependencies] +truth = { git = "https://github.com/ExpHP/truth" } + [package] name = "chireiden-thing" version = "0.1.0" @@ -13,7 +19,7 @@ ndarray = "0.16.1" num-derive = "0.4.2" num-traits = "0.2.19" rand = "0.8.5" -truth = { git = "https://github.com/ExpHP/truth" } +truth = { workspace = true } anyhow = { version = "1.0", features = ["backtrace"] } glam = { version = "0.29", features = ["bytemuck"] } bytemuck = { version = "1.18", features = ["derive"] } @@ -32,6 +38,7 @@ atomic_refcell = "0.1.13" pin-project = "1.1.7" heapless = "0.8.0" bitfield-struct = "0.9.2" +instructions = { version = "0.1.0", path = "instructions" } [dev-dependencies] csv = "1.3.0" diff --git a/flake.nix b/flake.nix index b6a9954..957d4be 100644 --- a/flake.nix +++ b/flake.nix @@ -40,6 +40,12 @@ alsa-lib (pkgs.callPackage ./thtk.nix {}) (pkgs.callPackage ./truth.nix {}) + + xorg.libX11 + xorg.libXcursor + xorg.libxcb + xorg.libXi + libxkbcommon ]; LD_LIBRARY_PATH = libPath; }; diff --git a/instructions/Cargo.toml b/instructions/Cargo.toml new file mode 100644 index 0000000..51449cb --- /dev/null +++ b/instructions/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "instructions" +version = "0.1.0" +edition = "2021" + +[dependencies] +macros = { version = "0.1.0", path = "../macros" } +num-derive = "0.4.2" +num-traits = "0.2.19" +truth = { workspace = true } diff --git a/src/game/anm/vm/opcodes.rs b/instructions/src/anm.rs similarity index 91% rename from src/game/anm/vm/opcodes.rs rename to instructions/src/anm.rs index 1d1cf0c..188b450 100644 --- a/src/game/anm/vm/opcodes.rs +++ b/instructions/src/anm.rs @@ -2,9 +2,8 @@ use macros::decode_args; use num_derive::FromPrimitive; use num_traits::FromPrimitive; use truth::llir::RawInstr; -use wgpu::naga::FastHashMap; -use crate::game::param::Param; +use crate::Param; #[derive(Debug, FromPrimitive)] enum Opcode { @@ -93,21 +92,21 @@ enum Opcode { Color2Time = 78, Alpha2Time = 79, ColorMode = 80, - CaseReturn = 81, + ReturnFromInterrupt = 81, RotateAuto = 82, Ins83CopyPos = 83, TexCircle = 84, UnknownBitflag = 85, SlowdownImmune = 86, RandMode = 87, - ScriptNew = 88, + NewChildBack = 88, ResampleMode = 89, - ScriptNewUI = 90, - ScriptNewFront = 91, - ScriptNewUIFront = 92, + NewChildUIBack = 90, + NewChildFront = 91, + NewChildUIFront = 92, ScrollXTime = 93, ScrollYTime = 94, - ScriptNewRoot = 95, + NewRootBack = 95, ScriptNewPos = 96, ScriptNewRootPos = 97, MoveBezier = 100, @@ -163,6 +162,8 @@ pub enum SpriteType { Rotate3D = 2, RotateZ3D = 3, RotateBillboard = 4, + Unknown3DA = 6, + Unknown3DB = 8, } #[derive(Debug, Clone, Copy)] @@ -312,38 +313,43 @@ pub enum Op { Visible { value: i8, }, - ZWriteDisable { - value: i32, - }, + ZWriteDisable(bool), StdRelatedBitflag { enable: i32, }, Wait { time: i32, }, - CaseReturn, + ReturnFromInterrupt, UnknownBitflag { enable: i32, }, RandMode { mode: i32, }, - ScriptNew { + NewChildBack { script: i32, }, ResampleMode { mode: i32, }, - ScriptNewUI { + NewChildUIBack { script: i32, }, - ScriptNewFront { + NewChildFront { script: i32, }, - ScriptNewUIFront { + NewChildUIFront { script: i32, }, ScrollXTime, + NewRootBack { + script: i32, + }, + SpriteRand { + sprite: i32, + end: Param, + }, } #[derive(Debug, Clone, Copy)] @@ -353,7 +359,7 @@ pub struct Instruction { } impl Instruction { - pub fn from_raw(inst: RawInstr, instruction_offsets: &FastHashMap) -> Self { + pub fn from_raw(inst: RawInstr, instruction_offsets: &Vec<(u32, usize)>) -> Self { let param_int = |value: i32, index: u16| { if inst.param_mask & (1 << index) == 0 { Param::Value(value) @@ -385,7 +391,8 @@ impl Instruction { ); value as i32 }; - let offset_to_index = |value: i32| instruction_offsets[&(value as u32)]; + let offset_to_index = + |value: i32| instruction_offsets.binary_search_by_key(&(value as u32), |a| a.0).expect("failed to find entry"); let args = &inst.args_blob; let opcode_raw = inst.opcode; @@ -694,6 +701,18 @@ impl Instruction { Op::Layer { layer } } Opcode::StopHide => Op::StopHide, + Opcode::ScrollX => { + let value = decode_args!(args, "f"); + Op::ScrollX { value } + } + Opcode::ScrollY => { + let value = decode_args!(args, "f"); + Op::ScrollY { value } + } + Opcode::ZWriteDisable => { + let value = decode_args!(args, "S"); + Op::ZWriteDisable(value != 0) + } Opcode::UnknownBitflag => { let enable = decode_args!(args, "S"); @@ -703,31 +722,40 @@ impl Instruction { let time = decode_args!(args, "S"); Op::Wait { time } } - Opcode::CaseReturn => Op::CaseReturn, + Opcode::ReturnFromInterrupt => Op::ReturnFromInterrupt, Opcode::RandMode => { let mode = decode_args!(args, "S"); Op::RandMode { mode } } - Opcode::ScriptNew => { + Opcode::NewChildBack => { let script = decode_args!(args, "S"); - Op::ScriptNew { script } + Op::NewChildBack { script } } Opcode::ResampleMode => { let mode = decode_args!(args, "S"); Op::ResampleMode { mode } } - Opcode::ScriptNewUI => { + Opcode::NewChildUIBack => { let script = decode_args!(args, "S"); - Op::ScriptNewUI { script } + Op::NewChildUIBack { script } } - Opcode::ScriptNewFront => { + Opcode::NewChildFront => { let script = decode_args!(args, "S"); - Op::ScriptNewFront { script } + Op::NewChildFront { script } } - Opcode::ScriptNewUIFront => { + Opcode::NewChildUIFront => { let script = decode_args!(args, "S"); - Op::ScriptNewUIFront { script } + Op::NewChildUIFront { script } + } + Opcode::NewRootBack => { + let script = decode_args!(args, "S"); + Op::NewRootBack { script } + } + Opcode::SpriteRand => { + let (sprite, end) = decode_args!(args, "SS"); + let end = param_int(end, 1); + Op::SpriteRand { sprite, end } } opcode => panic!("unsupported instruction {opcode:?} ({opcode_raw})"), }; diff --git a/instructions/src/ecl.rs b/instructions/src/ecl.rs new file mode 100644 index 0000000..71b0a28 --- /dev/null +++ b/instructions/src/ecl.rs @@ -0,0 +1,1087 @@ +use macros::{decode_args, spawner_param_decode_args}; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use truth::llir::RawInstr; + +use crate::Param; + +#[derive(Debug, FromPrimitive, Clone, Copy)] +pub enum Opcode { + Nop = 0, + + // control flow + Delete = 1, + Ret = 10, + Call = 11, + Jump = 12, + JumpEq = 13, + JumpNeq = 14, + CallAsync = 15, + CallAsyncById = 16, + KillAsync = 17, + KillAllAsync = 21, + + // stack math + StackAlloc = 40, + PushInt = 42, + PopInt = 43, + PushFloat = 44, + PopFloat = 45, + AddInt = 50, + AddFloat = 51, + SubInt = 52, + SubFloat = 53, + MulInt = 54, + MulFloat = 55, + DivInt = 56, + DivFloat = 57, + ModInt = 58, + EqualInt = 59, + EqualFloat = 60, + NotEqualInt = 61, + NotEqualFloat = 62, + LessInt = 63, + LessFloat = 64, + LessEqualInt = 65, + LessEqualFloat = 66, + GreaterInt = 67, + GreaterFloat = 68, + GreaterEqualInt = 69, + GreaterEqualFloat = 70, + NotInt = 71, + NotFloat = 72, + LogicalOr = 73, + LogicalAnd = 74, + BitwiseXor = 75, + BitwiseOr = 76, + BitwiseAnd = 77, + DecrementInt = 78, + Sin = 79, + Cos = 80, + CirclePos = 81, + NormalizeRad = 82, + Wait = 83, + NegateInt = 84, + NegateFloat = 85, + SquareSum = 86, + GetAngle = 87, + SquareRoot = 88, + Linear = 89, + PointRotate = 90, + FloatTime = 91, + Math92 = 92, + Math93 = 93, + + EnemyCreateRel = 256, + EnemyCreateAbs = 257, + EnemySelectAnm = 258, + EnemySetAnmScript = 259, + EnemyCreateMirroredRel = 260, + EnemyCreateMirroredAbs = 261, + EnemySetMainAnmScript = 262, + EnemyPlayAnmRel = 263, + EnemyPlayAnmAbs = 264, + EnemyCreateFillerRel = 265, + EnemyCreateFillerAbs = 266, + EnemyCreateFillerMirroredRel = 267, + EnemyCreateFillerMirroredAbs = 268, + EnemyPlaySelected = 269, + EnemySwitchAnm = 275, + EnemyResetAnm = 276, + EnemyInst277 = 277, + EnemyInst278 = 278, + + MovePosAbs = 280, + MovePosAbsTime = 281, + MovePosRel = 282, + MovePosRelTime = 283, + MoveVelAbs = 284, + MoveVelAbsTime = 285, + MoveVelRel = 286, + MoveVelRelTime = 287, + MoveCircle = 288, + MoveCircleTime = 289, + MoveCircleRel = 290, + MoveCircleRelTime = 291, + MoveRandAbs = 292, + MoveRandRel = 293, + MoveAdd = 294, + MoveAddRel = 295, + MoveEllipse = 300, + MoveEllipseRel = 301, + MoveBezier = 305, + MoveReset = 307, + + SetHurtbox = 320, + SetHitbox = 321, + FlagSet = 322, + FlagClear = 323, + SetMoveBounds = 324, + ResetMoveBounds = 325, + ClearExtraDrops = 326, + AddExtraDrops = 327, + SetItemDropArea = 328, + DropItems = 329, + SetItemReward = 330, + SetHealth = 331, + SetBoss = 332, + ResetBossTimer = 333, + SetInterrupt = 334, + SetInvulnerable = 335, + PlaySound = 336, + ShakeScreen = 337, + StartDialogue = 338, + WaitForDialogue = 339, + WaitForDeath = 340, + SetTimeoutInterrupt = 341, + StartSpell = 342, + EndSpell = 343, + SetChapter = 344, + KillAllEnemies = 345, + ProtectPlayer = 346, + LifeMarker = 347, + SetByDifficultyInt = 355, + SetByDifficultyFloat = 356, + StartSpellDifficulty = 357, + StartSpellDifficultyM1 = 358, + StartSpellDifficultyM2 = 359, + SetBossLifeCount = 360, + + SpawnerReset = 400, + SpawnerShoot = 401, + SpawnerSetSprite = 402, + SpawnerSetOffset = 403, + SpawnerSetAngle = 404, + SpawnerSetSpeed = 405, + SpawnerSetCount = 406, + SpawnerSetAimMode = 407, + SpawnerSetSounds = 408, + SpawnerSetEffects = 409, + SpawnerCancel = 410, + SpawnerCopy = 411, + LaserLineCreate = 412, + LaserInfCreate = 413, + LaserOffset = 414, + LaserTarget = 415, + LaserSpeed = 416, + LaserWidth = 417, + LaserAngle = 418, + LaserRotate = 419, + BulletCancelRadius = 420, + BulletClearRadius = 421, + SpawnerSpeedRank3 = 422, + SpawnerSpeedRank5 = 423, + SpawnerSpeedRankLerp = 424, + SpawnerCountRank3 = 425, + SpawnerCountRank5 = 426, + SpawnerCountRankLerp = 427, + LaserLineCreateNoBlend = 428, + LaserInfCreateNoBlend = 429, + SetFloatAngleToPlayerFromPoint = 430, // cmon can we think of a better name than that??? + LaserLineExCreate = 431, + LaserInfExCreate = 432, + LaserLineExCreateNoBlend = 433, + LaserInfExCreateNoBlend = 434, + SpawnerSpeedDifficulty = 435, + SpawnerCountDifficulty = 436, +} + +#[derive(Debug)] +pub enum PrimOp { + Add, + Sub, + Mul, + Div, + Equal, + NotEqual, + Less, + LessEqual, + Greater, + GreaterEqual, + Not, + Negate, +} + +#[derive(Debug)] +pub enum Op { + Nop, + Delete, + Return, + Call(String, Vec), + Jump { + index: usize, + time: i32, + }, + JumpEqual { + index: usize, + time: i32, + }, + JumpNotEqual { + index: usize, + time: i32, + }, + CallAsync(String, Vec), + CallAsyncById(String, i32, Vec), + KillAsync(i32), + KillAllAsync, + StackAlloc(usize), + PushInt(Param), + PopInt(i32), + PushFloat(Param), + PopFloat(i32), + IntOp(PrimOp), + ModuloInt, + FloatOp(PrimOp), + Or, + And, + BitwiseXor, + BitwiseOr, + BitwiseAnd, + DecrementInt(i32), + Sin, + Cos, + CirclePos { + x: Param, + y: Param, + angle: Param, + radius: Param, + }, + NormalizeRad(i32), + Wait(Param), + + EnemyCreate { + relative: bool, + mirrored: bool, + filler: bool, + subroutine: String, + x: Param, + y: Param, + health: i32, + score: i32, + item: i32, + }, + EnemySelectAnm { + source: i32, + }, + EnemySetAnmScript { + slot: Param, + script: Param, + }, + EnemySetMainAnmScript { + slot: Param, + script: Param, + }, + EnemyPlayAnm { + relative: bool, + slot: Param, + script: Param, + }, + EnemyPlaySelected { + slot: Param, + }, + + MovePos { + relative: bool, + x: Param, + y: Param, + }, + MovePosTime { + relative: bool, + time: Param, + mode: Param, + x: Param, + y: Param, + }, + + MoveVel { + relative: bool, + x: Param, + y: Param, + }, + MoveVelTime { + relative: bool, + time: Param, + mode: Param, + x: Param, + y: Param, + }, + MoveRand { + relative: bool, + time: Param, + mode: Param, + speed: Param, + }, + MoveOrbit { + relative: bool, + initial_angle: Param, + speed: Param, + radius: Param, + radius_growth: Param, + }, + MoveOrbitTime { + relative: bool, + time: Param, + mode: Param, + radius: Param, + radius_growth: Param, + }, + SetHurtbox { + x: f32, + y: f32, + }, + SetHitbox { + x: f32, + y: f32, + }, + SetFlag(i32), + ClearFlag(i32), + SetMoveBounds { + x: f32, + y: f32, + width: f32, + height: f32, + }, + ResetMoveBounds, + ClearExtraDrops, + AddExtraDrops { + drop: i32, + item: i32, + }, + SetItemDropArea { + width: i32, + height: i32, + }, + DropItems, + SetItemReward(i32), + SetHealth(Param), + SetBoss(i32), + ResetBossTimer, + SetInterrupt { + slot: i32, + hp: i32, + time: i32, + subroutine: String, + }, + SetInvulnerable(Param), + PlaySound(i32), + ShakeScreen { + time: Param, + // are these name correct? + min_intensity: Param, + max_intensity: Param, + }, + StartDialogue(i32), + WaitForDialogue, + WaitForDeath, + SetTimeoutInterrupt { + slot: i32, + subroutine: String, + }, + StartSpell { + id: i32, + time: i32, + bonus: i32, + name: String, + }, + EndSpell, + SetChapter(i32), + LifeMarker { + slot: i32, + hp: i32, + color: i32, + }, + SetByDifficultyInt { + var: i32, + easy: i32, + normal: i32, + hard: i32, + lunatic: i32, + }, + SetByDifficultyFloat { + var: i32, + easy: f32, + normal: f32, + hard: f32, + lunatic: f32, + }, + StartSpellDifficulty { + id: i32, + time: i32, + bonus: i32, + name: String, + }, + StartSpellDifficultyM1 { + id: i32, + time: i32, + bonus: i32, + name: String, + }, + StartSpellDifficultyM2 { + id: i32, + time: i32, + bonus: i32, + name: String, + }, + SetBossLifeCount(Param), + + SpawnerReset { + spawner: Param, + }, + SpawnerShoot { + spawner: Param, + }, + SpawnerSetSprite { + spawner: Param, + sprite: Param, + color: Param, + }, + SpawnerSetOffset { + spawner: Param, + x: Param, + y: Param, + }, + SpawnerSetAngle { + spawner: Param, + angle: [Param; 2], + }, + SpawnerSetSpeed { + spawner: Param, + speed: [Param; 2], + }, + SpawnerSetCount { + spawner: Param, + count: [Param; 2], + }, + SpawnerSetAimMode { + spawner: Param, + mode: Param, + }, + SpawnerSetSounds { + spawner: Param, + fire_sound: Param, + transform_sound: Param, + }, + SpawnerSetEffects { + spawner: Param, + effect_index: Param, + is_async: Param, + id: Param, + int_1: Param, + int_2: Param, + float_1: Param, + float_2: Param, + }, + SpawnerCancel, + SpawnerCopy { + dest_spawner: Param, + src_spawner: Param, + }, + LaserCreate { + opcode: Opcode, + arguments: Vec, + }, + LaserOffset, + LaserTarget, + LaserSpeed, + LaserWidth, + LaserAngle, + LaserRotate, + BulletCancelRadius(f32), + BulletClearRadius(f32), + SpawnerSpeedRank3 { + spawner: Param, + speeds: [[Param; 2]; 3], + }, + SpawnerSpeedRank5 { + spawner: Param, + speeds: [[Param; 2]; 5], + }, + SpawnerSpeedRankLerp, + SpawnerCountRank3{ + spawner: Param, + counts: [[Param; 2]; 3], + }, + SpawnerCountRank5{ + spawner: Param, + counts: [[Param; 2]; 5], + }, + SpawnerCountRankLerp, +} + +#[derive(Debug)] +pub struct Instruction { + pub time: i32, + pub difficulty: u8, + pub op: Op, +} + +impl Instruction { + pub fn from_raw(inst: RawInstr, offset: u32, instruction_offsets: &Vec<(u32, usize)>) -> Self { + let param_int = |value: i32, index: u16| { + if inst.param_mask & (1 << index) == 0 { + Param::Value(value) + } else { + Param::Variable(value) + } + }; + let param_float = |value: f32, index: u16| { + if inst.param_mask & (1 << index) == 0 { + Param::Value(value) + } else { + Param::Variable(value as i32) + } + }; + let var_int = |value: i32, index: u16| { + assert!(inst.param_mask & (1 << index) != 0, "param must be a var"); + // assert!(value <= -9932 && value >= -10000, "value must be a var"); + value + }; + let var_float = |value: f32, index: u16| { + assert!(inst.param_mask & (1 << index) != 0, "param must be a var"); + // assert!( + // value.trunc() == value && value <= -9932. && value >= -10000., + // "value must be a var" + // ); + value as i32 + }; + let offset_to_index = |value: i32| { + let final_offset = offset.checked_add_signed(value).expect("add overflowed"); + instruction_offsets + [instruction_offsets.binary_search_by_key(&final_offset, |entry| entry.0).expect("failed to locate offset")] + .1 + }; + + let args = inst.args_blob; + let opcode_raw = inst.opcode; + let opcode = Opcode::from_u16(opcode_raw).unwrap_or_else(|| todo!("failed to convert opcode {opcode_raw}")); + let op = match opcode { + Opcode::Nop => Op::Nop, + Opcode::Delete => Op::Delete, + Opcode::Ret => Op::Return, + Opcode::Call => { + let (name, args) = decode_args!(args, "P(bs=4)v"); + Op::Call(name, args) + } + Opcode::Jump => { + let (offset, time) = decode_args!(args, "ot"); + + Op::Jump { + index: offset_to_index(offset), + time, + } + } + Opcode::JumpEq => { + let (offset, time) = decode_args!(args, "ot"); + + Op::JumpEqual { + index: offset_to_index(offset), + time, + } + } + Opcode::JumpNeq => { + let (offset, time) = decode_args!(args, "ot"); + + Op::JumpNotEqual { + index: offset_to_index(offset), + time, + } + } + Opcode::CallAsync => { + let (name, args) = decode_args!(args, "P(bs=4)v"); + Op::CallAsync(name, args) + } + Opcode::CallAsyncById => { + let (name, id, args) = decode_args!(args, "P(bs=4)Sv"); + Op::CallAsyncById(name, id, args) + } + Opcode::KillAsync => { + let id = decode_args!(args, "S"); + Op::KillAsync(id) + } + Opcode::KillAllAsync => Op::KillAllAsync, + Opcode::StackAlloc => Op::StackAlloc(decode_args!(args, "S") as usize), + Opcode::PushInt => { + let value = decode_args!(args, "S"); + Op::PushInt(param_int(value, 0)) + } + Opcode::PopInt => { + let value = decode_args!(args, "S"); + Op::PopInt(var_int(value, 0)) + } + Opcode::PushFloat => { + let float = decode_args!(args, "f"); + Op::PushFloat(param_float(float, 0)) + } + Opcode::PopFloat => { + let var = decode_args!(args, "f"); + Op::PopFloat(var_float(var, 0)) + } + Opcode::AddInt => Op::IntOp(PrimOp::Add), + Opcode::AddFloat => Op::FloatOp(PrimOp::Add), + Opcode::SubInt => Op::IntOp(PrimOp::Sub), + Opcode::SubFloat => Op::FloatOp(PrimOp::Sub), + Opcode::MulInt => Op::IntOp(PrimOp::Mul), + Opcode::MulFloat => Op::FloatOp(PrimOp::Mul), + Opcode::DivInt => Op::IntOp(PrimOp::Div), + Opcode::DivFloat => Op::FloatOp(PrimOp::Div), + Opcode::ModInt => Op::ModuloInt, + Opcode::EqualInt => Op::IntOp(PrimOp::Equal), + Opcode::EqualFloat => Op::FloatOp(PrimOp::Equal), + Opcode::NotEqualInt => Op::IntOp(PrimOp::NotEqual), + Opcode::NotEqualFloat => Op::FloatOp(PrimOp::NotEqual), + Opcode::LessInt => Op::IntOp(PrimOp::Less), + Opcode::LessFloat => Op::FloatOp(PrimOp::Less), + Opcode::LessEqualInt => Op::IntOp(PrimOp::LessEqual), + Opcode::LessEqualFloat => Op::FloatOp(PrimOp::LessEqual), + Opcode::GreaterInt => Op::IntOp(PrimOp::Greater), + Opcode::GreaterFloat => Op::FloatOp(PrimOp::Greater), + Opcode::GreaterEqualInt => Op::IntOp(PrimOp::GreaterEqual), + Opcode::GreaterEqualFloat => Op::FloatOp(PrimOp::GreaterEqual), + Opcode::NotInt => Op::IntOp(PrimOp::Not), + Opcode::NotFloat => Op::FloatOp(PrimOp::Not), + Opcode::LogicalOr => Op::Or, + Opcode::LogicalAnd => Op::And, + Opcode::BitwiseXor => Op::BitwiseXor, + Opcode::BitwiseOr => Op::BitwiseOr, + Opcode::BitwiseAnd => Op::BitwiseAnd, + Opcode::DecrementInt => { + let by = decode_args!(args, "S"); + Op::DecrementInt(var_int(by, 0)) + } + Opcode::Sin => Op::Sin, + Opcode::Cos => Op::Cos, + Opcode::CirclePos => { + let (x, y, angle, radius) = decode_args!(args, "ffff"); + let (x, y, angle, radius) = ( + param_float(x, 0), + param_float(y, 1), + param_float(angle, 2), + param_float(radius, 3), + ); + Op::CirclePos { x, y, angle, radius } + } + Opcode::NormalizeRad => { + let var = decode_args!(args, "S"); + Op::NormalizeRad(var_int(var, 0)) + } + Opcode::Wait => { + let time = decode_args!(args, "S"); + Op::Wait(param_int(time, 0)) + } + Opcode::NegateInt => Op::IntOp(PrimOp::Negate), + Opcode::NegateFloat => Op::FloatOp(PrimOp::Negate), + Opcode::EnemyCreateRel + | Opcode::EnemyCreateAbs + | Opcode::EnemyCreateMirroredRel + | Opcode::EnemyCreateMirroredAbs + | Opcode::EnemyCreateFillerRel + | Opcode::EnemyCreateFillerAbs + | Opcode::EnemyCreateFillerMirroredRel + | Opcode::EnemyCreateFillerMirroredAbs => { + let (subroutine, x, y, health, score, item) = decode_args!(args, "P(bs=4)ffSSS"); + let (x, y) = (param_float(x, 0), param_float(y, 1)); + Op::EnemyCreate { + relative: matches!( + opcode, + Opcode::EnemyCreateRel + | Opcode::EnemyCreateMirroredRel + | Opcode::EnemyCreateFillerRel + | Opcode::EnemyCreateFillerMirroredRel + ), + mirrored: matches!( + opcode, + Opcode::EnemyCreateMirroredRel + | Opcode::EnemyCreateMirroredAbs + | Opcode::EnemyCreateFillerMirroredRel + | Opcode::EnemyCreateFillerMirroredAbs + ), + filler: matches!( + opcode, + Opcode::EnemyCreateFillerRel + | Opcode::EnemyCreateFillerAbs + | Opcode::EnemyCreateFillerMirroredRel + | Opcode::EnemyCreateFillerMirroredAbs + ), + subroutine, + x, + y, + health, + score, + item, + } + } + Opcode::EnemySelectAnm => { + let source = decode_args!(args, "S"); + Op::EnemySelectAnm { source } + } + Opcode::EnemySetAnmScript => { + let (slot, script) = decode_args!(args, "SS"); + let (slot, script) = (param_int(slot, 0), param_int(script, 1)); + Op::EnemySetAnmScript { slot, script } + } + Opcode::EnemySetMainAnmScript => { + let (slot, script) = decode_args!(args, "SS"); + let (slot, script) = (param_int(slot, 0), param_int(script, 1)); + Op::EnemySetMainAnmScript { slot, script } + } + Opcode::EnemyPlayAnmRel | Opcode::EnemyPlayAnmAbs => { + let (slot, script) = decode_args!(args, "SS"); + let (slot, script) = (param_int(slot, 0), param_int(script, 1)); + Op::EnemyPlayAnm { + relative: matches!(opcode, Opcode::EnemyPlayAnmRel), + slot, + script, + } + } + Opcode::EnemyPlaySelected => { + let slot = decode_args!(args, "S"); + Op::EnemyPlaySelected { + slot: param_int(slot, 0), + } + } + Opcode::MovePosAbs | Opcode::MovePosRel => { + let (x, y) = decode_args!(args, "ff"); + let (x, y) = (param_float(x, 0), param_float(y, 1)); + Op::MovePos { + relative: matches!(opcode, Opcode::MovePosRel), + x, + y, + } + } + Opcode::MovePosAbsTime | Opcode::MovePosRelTime => { + let (time, mode, x, y) = decode_args!(args, "SSff"); + let (time, mode, x, y) = ( + param_int(time, 0), + param_int(mode, 1), + param_float(x, 2), + param_float(y, 3), + ); + Op::MovePosTime { + relative: matches!(opcode, Opcode::MovePosRelTime), + time, + mode, + x, + y, + } + } + Opcode::MoveVelAbs | Opcode::MoveVelRel => { + let (x, y) = decode_args!(args, "ff"); + let (x, y) = (param_float(x, 0), param_float(y, 1)); + Op::MoveVel { + relative: matches!(opcode, Opcode::MoveVelRel), + x, + y, + } + } + Opcode::MoveVelAbsTime | Opcode::MoveVelRelTime => { + let (time, mode, x, y) = decode_args!(args, "SSff"); + let (time, mode, x, y) = ( + param_int(time, 0), + param_int(mode, 1), + param_float(x, 2), + param_float(y, 3), + ); + Op::MoveVelTime { + relative: matches!(opcode, Opcode::MoveVelRelTime), + time, + mode, + x, + y, + } + } + Opcode::MoveRandAbs | Opcode::MoveRandRel => { + let (time, mode, speed) = decode_args!(args, "SSf"); + let (time, mode, speed) = (param_int(time, 0), param_int(mode, 1), param_float(speed, 2)); + Op::MoveRand { + relative: matches!(opcode, Opcode::MoveRandRel), + time, + mode, + speed, + } + } + Opcode::SetHurtbox => { + let (x, y) = decode_args!(args, "ff"); + Op::SetHurtbox { x, y } + } + Opcode::SetHitbox => { + let (x, y) = decode_args!(args, "ff"); + Op::SetHitbox { x, y } + } + Opcode::FlagSet => { + let flag = decode_args!(args, "S"); + Op::SetFlag(flag) + } + Opcode::FlagClear => { + let flag = decode_args!(args, "S"); + Op::ClearFlag(flag) + } + Opcode::SetMoveBounds => { + let (x, y, width, height) = decode_args!(args, "ffff"); + Op::SetMoveBounds { x, y, width, height } + } + Opcode::ResetMoveBounds => Op::ResetMoveBounds, + Opcode::ClearExtraDrops => Op::ClearExtraDrops, + Opcode::AddExtraDrops => { + let (drop, item) = decode_args!(args, "SS"); + Op::AddExtraDrops { drop, item } + } + Opcode::SetItemDropArea => { + let (width, height) = decode_args!(args, "SS"); + Op::SetItemDropArea { width, height } + } + Opcode::DropItems => Op::DropItems, + Opcode::SetItemReward => { + let reward = decode_args!(args, "S"); + Op::SetItemReward(reward) + } + Opcode::SetHealth => { + let health = decode_args!(args, "S"); + Op::SetHealth(param_int(health, 0)) + } + Opcode::SetBoss => { + let boss = decode_args!(args, "S"); + Op::SetBoss(boss) + } + Opcode::ResetBossTimer => Op::ResetBossTimer, + Opcode::SetInterrupt => { + let (slot, hp, time, subroutine) = decode_args!(args, "SSSP(bs=4)"); + Op::SetInterrupt { + slot, + hp, + time, + subroutine, + } + } + Opcode::SetInvulnerable => { + let timer = decode_args!(args, "S"); + Op::SetInvulnerable(param_int(timer, 0)) + } + Opcode::PlaySound => { + let sound = decode_args!(args, "S"); + Op::PlaySound(sound) + } + Opcode::ShakeScreen => { + let (time, min_intensity, max_intensity) = decode_args!(args, "SSS"); + let (time, min_intensity, max_intensity) = ( + param_int(time, 0), + param_int(min_intensity, 1), + param_int(max_intensity, 2), + ); + Op::ShakeScreen { + time, + min_intensity, + max_intensity, + } + } + Opcode::StartDialogue => { + let id = decode_args!(args, "S"); + Op::StartDialogue(id) + } + Opcode::WaitForDialogue => Op::WaitForDialogue, + Opcode::WaitForDeath => Op::WaitForDeath, + Opcode::SetTimeoutInterrupt => { + let (slot, subroutine) = decode_args!(args, "SP(bs=4)"); + + Op::SetTimeoutInterrupt { slot, subroutine } + } + Opcode::StartSpell => { + let (id, time, bonus, name) = decode_args!(args, "SSSp(bs=4;mask=0x77,7,16)"); + Op::StartSpell { id, time, bonus, name } + } + Opcode::EndSpell => Op::EndSpell, + Opcode::SetChapter => { + let chapter = decode_args!(args, "S"); + Op::SetChapter(chapter) + } + Opcode::LifeMarker => { + let (slot, hp, color) = decode_args!(args, "SSS"); + Op::LifeMarker { slot, hp, color } + } + Opcode::SetByDifficultyInt => { + let (var, easy, normal, hard, lunatic) = decode_args!(args, "SSSSS"); + Op::SetByDifficultyInt { + var: var_int(var, 0), + easy, + normal, + hard, + lunatic, + } + } + Opcode::SetByDifficultyFloat => { + let (var, easy, normal, hard, lunatic) = decode_args!(args, "fffff"); + Op::SetByDifficultyFloat { + var: var_float(var, 0), + easy, + normal, + hard, + lunatic, + } + } + Opcode::StartSpellDifficulty => { + let (id, time, bonus, name) = decode_args!(args, "SSSp(bs=4;mask=0x77,7,16)"); + Op::StartSpellDifficulty { id, time, bonus, name } + } + Opcode::StartSpellDifficultyM1 => { + let (id, time, bonus, name) = decode_args!(args, "SSSp(bs=4;mask=0x77,7,16)"); + Op::StartSpellDifficultyM1 { id, time, bonus, name } + } + Opcode::StartSpellDifficultyM2 => { + let (id, time, bonus, name) = decode_args!(args, "SSSp(bs=4;mask=0x77,7,16)"); + Op::StartSpellDifficultyM2 { id, time, bonus, name } + } + Opcode::SetBossLifeCount => { + let count = decode_args!(args, "S"); + Op::SetBossLifeCount(param_int(count, 0)) + } + Opcode::SpawnerReset => { + let spawner = decode_args!(args, "S"); + Op::SpawnerReset { + spawner: param_int(spawner, 0), + } + } + Opcode::SpawnerShoot => { + let spawner = decode_args!(args, "S"); + Op::SpawnerShoot { + spawner: param_int(spawner, 0), + } + } + Opcode::SpawnerSetSprite => { + let (spawner, sprite, color) = decode_args!(args, "SSS"); + let (spawner, sprite, color) = (param_int(spawner, 0), param_int(sprite, 1), param_int(color, 2)); + Op::SpawnerSetSprite { spawner, sprite, color } + } + Opcode::SpawnerSetOffset => { + let (spawner, x, y) = decode_args!(args, "Sff"); + let (spawner, x, y) = (param_int(spawner, 0), param_float(x, 1), param_float(y, 2)); + Op::SpawnerSetOffset { spawner, x, y } + } + Opcode::SpawnerSetAngle => { + let (spawner, angle_1, angle_2) = decode_args!(args, "Sff"); + let (spawner, angle_1, angle_2) = (param_int(spawner, 0), param_float(angle_1, 1), param_float(angle_2, 2)); + Op::SpawnerSetAngle { + spawner, + angle: [angle_1, angle_2], + } + } + Opcode::SpawnerSetSpeed => { + let (spawner, speed_1, speed_2) = decode_args!(args, "Sff"); + let (spawner, speed_1, speed_2) = (param_int(spawner, 0), param_float(speed_1, 1), param_float(speed_2, 2)); + Op::SpawnerSetSpeed { + spawner, + speed: [speed_1, speed_2], + } + } + Opcode::SpawnerSetCount => { + let (spawner, count_1, count_2) = decode_args!(args, "SSS"); + let (spawner, count_1, count_2) = (param_int(spawner, 0), param_int(count_1, 1), param_int(count_2, 2)); + Op::SpawnerSetCount { + spawner, + count: [count_1, count_2], + } + } + Opcode::SpawnerSetAimMode => { + let (spawner, mode) = decode_args!(args, "SS"); + let (spawner, mode) = (param_int(spawner, 0), param_int(mode, 1)); + Op::SpawnerSetAimMode { spawner, mode } + } + Opcode::SpawnerSetSounds => { + let (spawner, fire_sound, transform_sound) = decode_args!(args, "SSS"); + let (spawner, fire_sound, transform_sound) = ( + param_int(spawner, 0), + param_int(fire_sound, 1), + param_int(transform_sound, 2), + ); + Op::SpawnerSetSounds { + spawner, + fire_sound, + transform_sound, + } + } + Opcode::SpawnerSetEffects => { + let (spawner, effect_index, is_async, id, int_1, int_2, float_1, float_2) = decode_args!(args, "SSSSSSff"); + let (spawner, effect_index, is_async, id, int_1, int_2, float_1, float_2) = ( + param_int(spawner, 0), + param_int(effect_index, 1), + param_int(is_async, 2), + param_int(id, 3), + param_int(int_1, 4), + param_int(int_2, 5), + param_float(float_1, 6), + param_float(float_2, 7), + ); + Op::SpawnerSetEffects { + spawner, + effect_index, + is_async, + id, + int_1, + int_2, + float_1, + float_2, + } + } + Opcode::SpawnerCancel => Op::SpawnerCancel, + Opcode::SpawnerCopy => { + let (dest_spawner, src_spawner) = decode_args!(args, "SS"); + let (dest_spawner, src_spawner) = (param_int(dest_spawner, 0), param_int(src_spawner, 1)); + Op::SpawnerCopy { + dest_spawner, + src_spawner, + } + } + Opcode::LaserLineCreate + | Opcode::LaserLineCreateNoBlend + | Opcode::LaserLineExCreate + | Opcode::LaserLineExCreateNoBlend + | Opcode::LaserInfCreate + | Opcode::LaserInfCreateNoBlend + | Opcode::LaserInfExCreate + | Opcode::LaserInfExCreateNoBlend => Op::LaserCreate { + opcode, + arguments: args, + }, + Opcode::BulletCancelRadius => { + let radius = decode_args!(args, "f"); + Op::BulletCancelRadius(radius) + } + Opcode::BulletClearRadius => { + let radius = decode_args!(args, "f"); + Op::BulletClearRadius(radius) + } + Opcode::SpawnerSpeedRank3 => { + let (spawner, speeds) = spawner_param_decode_args!(args, "S", [[f32; 2]; 3]); + let spawner = param_int(spawner, 0); + Op::SpawnerSpeedRank3 { spawner, speeds } + } + Opcode::SpawnerSpeedRank5 => { + let (spawner, speeds) = spawner_param_decode_args!(args, "S", [[f32; 2]; 5]); + let spawner = param_int(spawner, 0); + Op::SpawnerSpeedRank5 { spawner, speeds } + } + // Opcode::SpawnerSpeedRankLerp => { + // let (spawner, speeds) = spawner_param_decode_args!(args, "S", [[f32; 2]; 5]); + // let spawner = param_int(spawner, 0); + // Op::SpawnerSpeedRankLerp { spawner, speeds } + // } + Opcode::SpawnerCountRank3 => { + let (spawner, counts) = spawner_param_decode_args!(args, "S", [[i32; 2]; 3]); + let spawner = param_int(spawner, 0); + Op::SpawnerCountRank3 { spawner, counts } + } + Opcode::SpawnerCountRank5 => { + let (spawner, counts) = spawner_param_decode_args!(args, "S", [[i32; 2]; 5]); + let spawner = param_int(spawner, 0); + Op::SpawnerCountRank5 { spawner, counts } + } + _ => { + unimplemented!("opcode {opcode:?} ({opcode_raw}) not implemented"); + } + }; + + Self { + time: inst.time, + difficulty: inst.difficulty, + op, + } + } +} diff --git a/instructions/src/lib.rs b/instructions/src/lib.rs new file mode 100644 index 0000000..933622a --- /dev/null +++ b/instructions/src/lib.rs @@ -0,0 +1,5 @@ +pub mod anm; +mod param; +pub mod ecl; +pub mod std; +pub use param::*; diff --git a/src/game/param.rs b/instructions/src/param.rs similarity index 100% rename from src/game/param.rs rename to instructions/src/param.rs diff --git a/instructions/src/std.rs b/instructions/src/std.rs new file mode 100644 index 0000000..d17aa2b --- /dev/null +++ b/instructions/src/std.rs @@ -0,0 +1,149 @@ +use macros::decode_args; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use truth::llir::RawInstr; + +#[derive(Debug, FromPrimitive)] +enum Opcode { + Halt = 0, + Jump = 1, + Position = 2, + PositionInterp = 3, + Facing = 4, + FacingInterp = 5, + Rotation = 6, + FieldOfView = 7, + Fog = 8, + FogInterp = 9, + PositionInterpBezier = 10, + FacingInterpBezier = 11, + ShakingMode = 12, + BackgroundFillColor = 13, + BackgroundSprite = 14, +} + +#[derive(Debug)] +pub enum Op { + Halt, + Jump { + index: usize, + time: i32, + }, + Position { + x: f32, + y: f32, + z: f32, + }, + PositionInterp { + time: i32, + mode: i32, + x: f32, + y: f32, + z: f32, + }, + Facing { + x: f32, + y: f32, + z: f32, + }, + FacingInterp { + time: i32, + mode: i32, + x: f32, + y: f32, + z: f32, + }, + Rotation { + x: f32, + y: f32, + z: f32, + }, + FieldOfView(f32), + Fog { + color: i32, + start: f32, + end: f32, + }, + FogInterp { + time: i32, + mode: i32, + color: i32, + start: f32, + end: f32, + }, + ShakingMode { + mode: i32, + }, +} + +pub struct Instruction { + pub time: i32, + pub op: Op, +} + +impl Instruction { + pub fn from_raw(inst: RawInstr, instruction_offsets: &Vec<(u32, usize)>) -> Self { + let offset_to_index = + |value: i32| instruction_offsets.binary_search_by_key(&(value as u32), |a| a.0).expect("failed to find entry"); + + let args = &inst.args_blob; + let opcode_raw = inst.opcode; + let opcode = Opcode::from_u16(opcode_raw).expect("failed to convert opcode"); + + let op = match opcode { + Opcode::Halt => Op::Halt, + Opcode::Jump => { + let (offset, time) = decode_args!(args, "ot"); + Op::Jump { + index: offset_to_index(offset), + time, + } + } + Opcode::Position => { + let (x, y, z) = decode_args!(args, "fff"); + Op::Position { x, y, z } + } + Opcode::PositionInterp => { + let (time, mode, x, y, z) = decode_args!(args, "SUfff"); + Op::PositionInterp { time, mode, x, y, z } + } + Opcode::Facing => { + let (x, y, z) = decode_args!(args, "fff"); + Op::Facing { x, y, z } + } + Opcode::FacingInterp => { + let (time, mode, x, y, z) = decode_args!(args, "SUfff"); + Op::FacingInterp { time, mode, x, y, z } + } + Opcode::Rotation => { + let (x, y, z) = decode_args!(args, "fff"); + Op::Rotation { x, y, z } + } + Opcode::FieldOfView => { + let fov = decode_args!(args, "f"); + Op::FieldOfView(fov) + } + Opcode::Fog => { + let (color, start, end) = decode_args!(args, "Sff"); + Op::Fog { color, start, end } + } + Opcode::FogInterp => { + let (time, mode, color, start, end) = decode_args!(args, "SUSff"); + Op::FogInterp { + time, + mode, + color, + start, + end, + } + } + Opcode::PositionInterpBezier => todo!(), + Opcode::FacingInterpBezier => todo!(), + Opcode::ShakingMode => todo!(), + Opcode::BackgroundFillColor => todo!(), + Opcode::BackgroundSprite => todo!(), + }; + + Instruction { time: inst.time, op } + } +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 6c59bf8..f72f254 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,20 +1,21 @@ -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ - parse::{ParseStream, Parser}, Expr, LitStr, Token + parse::{ParseStream, Parser}, + spanned::Spanned, + Expr, ExprLit, Ident, Lit, LitStr, Token, Type, TypeArray, }; use truth::{ - context::RootEmitter, llir::{ArgEncoding, InstrAbi, StringArgSize}, pos::SourceStr + context::RootEmitter, + llir::{ArgEncoding, InstrAbi, StringArgSize}, + pos::SourceStr, }; #[proc_macro] pub fn decode_args(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - match blob_impl.parse(input.clone()) { + match blob_impl.parse(input) { Ok(data) => data.into(), - Err(error) => { - // error.span().unwrap().error(error.to_string()); - error.into_compile_error().into() - } + Err(error) => error.into_compile_error().into(), } } @@ -22,11 +23,14 @@ fn blob_impl(input: ParseStream) -> syn::Result { let reader: Expr = input.parse()?; let _: Token![,] = input.parse()?; let sig_str: LitStr = input.parse()?; - let sig = InstrAbi::parse( - SourceStr::from_full_source(None, sig_str.value().as_str()), - &RootEmitter::new_stderr(), - ) - .unwrap(); + let sig_str_span = sig_str.span(); + let sig_str = sig_str.value(); + let (sig_str, variadic) = if sig_str.ends_with('v') { + (&sig_str.as_str()[..sig_str.len() - 1], true) + } else { + (sig_str.as_str(), false) + }; + let sig = InstrAbi::parse(SourceStr::from_full_source(None, sig_str), &RootEmitter::new_stderr()).unwrap(); let encodings: Vec<_> = sig .arg_encodings() .map(|enc| { @@ -43,22 +47,127 @@ fn blob_impl(input: ParseStream) -> syn::Result { } ArgEncoding::JumpTime => quote!(cursor.read_i32().unwrap()), ArgEncoding::JumpOffset => quote!(cursor.read_i32().unwrap()), + ArgEncoding::Color => quote!(cursor.read_u32().unwrap()), ArgEncoding::String { size: StringArgSize::Fixed { len, .. }, .. - } => quote!(cursor.read_cstring_exact(#len)), - _ => return Err(syn::Error::new(sig_str.span(), "failed...")), + } => quote!(cursor.read_cstring_exact(#len).unwrap().decode(truth::io::DEFAULT_ENCODING).unwrap()), + ArgEncoding::String { + size: StringArgSize::Pascal { block_size }, + .. + } => quote!(cursor.read_cstring_blockwise(#block_size).unwrap().decode(truth::io::DEFAULT_ENCODING).unwrap()), + _ => return Err(syn::Error::new(sig_str_span, "failed...")), }) }) .collect::>()?; + let variadic = variadic.then(|| { + quote! { + , { + let file_size = cursor.file_size().unwrap(); + let offset = (file_size - cursor.pos().unwrap()) as usize; + cursor.read_byte_vec(offset).unwrap() + } + } + }); + Ok(quote! { { use truth::io::BinRead; let mut cursor = ::std::io::Cursor::new(#reader); ( #(#encodings),* + #variadic ) } }) } + +//spawner_rank_decode_args!(args, "S", [[f32; 2]; 3]) + +#[proc_macro] +pub fn spawner_param_decode_args(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + match spawner_param_decode_args_impl.parse(input) { + Ok(data) => data.into(), + Err(error) => error.into_compile_error().into(), + } +} + +fn spawner_param_decode_args_impl(input: ParseStream) -> syn::Result { + let reader: Expr = input.parse()?; + let _: Token![,] = input.parse()?; + let sig_str: LitStr = input.parse()?; + let _: Token![,] = input.parse()?; + let array = input.parse::()?; + + let Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) = array.len else { + return Err(syn::Error::new(array.len.span(), "length must be a constant number")); + }; + let rank_count = int.base10_parse::()?; + let Type::Array(TypeArray { + elem, + len: Expr::Lit(ExprLit { lit: Lit::Int(int), .. }), + .. + }) = array.elem.as_ref() + else { + return Err(syn::Error::new( + array.elem.span(), + "element was not an array with constant number ", + )); + }; + let entry_count = int.base10_parse::()?; + let Type::Path(path) = elem.as_ref() else { + return Err(syn::Error::new(elem.span(), "expected path as element")); + }; + let ident = path.path.get_ident().ok_or(syn::Error::new(path.span(), "path was not a single ident"))?; + let type_id = ident.to_string(); + enum ParamType { + Float, + Int, + } + let param_ty = match type_id.as_str() { + "f32" => ParamType::Float, + "i32" => ParamType::Int, + _ => return Err(syn::Error::new(ident.span(), "expected i32 or f32")), + }; + + let total = entry_count * rank_count; + let mut arguments = sig_str.value(); + + arguments.extend(std::iter::repeat_n( + match param_ty { + ParamType::Int => 'S', + ParamType::Float => 'f', + }, + total, + )); + + let rank_list: Vec> = (0..rank_count) + .map(|rank| { + (0..entry_count).map(move |entry| Ident::new(&format!("__a_{rank}_{entry}"), Span::call_site())).collect() + }) + .collect(); + + let tuple_pat = rank_list.iter().flatten(); + let rank_array = rank_list.iter().enumerate().map(|(rank_index, list)| { + let list = list.iter().enumerate().map(|(entry_index, entry)| { + let param_index = rank_index * entry_count + entry_index + 1; + let param_index = param_index as u16; + let kind = match param_ty { + ParamType::Float => quote!(param_float), + ParamType::Int => quote!(param_int), + }; + quote!(#kind(#entry, #param_index)) + }); + quote! { + [#(#list),*] + } + }); + + Ok(quote! { + { + let (id, #(#tuple_pat),*) = decode_args!(#reader, #arguments); + (id, [#(#rank_array),*]) + } + }) +} diff --git a/src/engine.rs b/src/engine.rs index 96b9c1b..b2a538c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,5 +1,5 @@ use std::{ - sync::{Arc, LazyLock, Mutex}, time::{Duration, Instant} + cell::Cell, ops::ControlFlow, sync::{Arc, LazyLock}, time::{Duration, Instant} }; use wgpu::{ @@ -23,6 +23,7 @@ pub struct Engine<'a> { last_frame_instant: Instant, last_second: Instant, frames: u32, + should_exit: Cell, state: GameRunner, focused: bool, @@ -48,6 +49,7 @@ impl KeyState { #[derive(Default)] pub struct Keys { values: FastHashMap, + down_count: u32, } impl Keys { @@ -55,8 +57,10 @@ impl Keys { let key_state = self.values.entry(key).or_default(); if key_state.is_down() != state.is_pressed() { *key_state = if state.is_pressed() { + self.down_count += 1; KeyState::Pressed } else { + self.down_count -= 1; KeyState::Released } } @@ -85,6 +89,10 @@ impl Keys { pub fn was_key_pressed(&self, key: KeyCode) -> bool { matches!(self.values.get(&key), Some(KeyState::Pressed)) } + + pub fn is_any_key_down(&self) -> bool { + self.down_count > 0 + } } impl<'a> Engine<'a> { @@ -127,7 +135,7 @@ impl<'a> Engine<'a> { let caps = surface.get_capabilities(&adapter); let format = caps.formats.iter().find(|f| matches!(f, TextureFormat::Bgra8Unorm)).cloned().unwrap_or(caps.formats[0]); - let config = (SurfaceConfiguration { + let config = SurfaceConfiguration { usage: TextureUsages::RENDER_ATTACHMENT, format, width: size.width, @@ -136,17 +144,19 @@ impl<'a> Engine<'a> { desired_maximum_frame_latency: 2, alpha_mode: caps.alpha_modes[0], view_formats: vec![], - }); + }; surface.configure(&device, &config); let keys = Keys::default(); + let should_exit = Cell::new(false); let state = GameRunner::new(&UpdateContext { device: &device, queue: &queue, window: &window, keys: &keys, config: &config, + should_exit: &should_exit, }); let now = Instant::now(); @@ -163,6 +173,7 @@ impl<'a> Engine<'a> { frames: 0, keys: Keys::default(), focused: false, + should_exit, state, } @@ -206,19 +217,20 @@ impl<'a> Engine<'a> { keys: &self.keys, window: &self.window, config: &self.config, + should_exit: &self.should_exit }, new_size, ); } } - pub fn update(&mut self) { - // fixed step + pub fn update(&mut self) -> ControlFlow<()> { + // target game at fixed step 60fps always, const TARGET_FRAMERATE: LazyLock = LazyLock::new(|| Duration::from_secs_f64(1. / 60.)); let now = Instant::now(); let since_last_frame = now - self.last_frame_instant; if since_last_frame < *TARGET_FRAMERATE { - return; + return ControlFlow::Continue(()); } self.last_frame_instant = now; self.frames += 1; @@ -229,6 +241,7 @@ impl<'a> Engine<'a> { keys: &self.keys, window: &self.window, config: &self.config, + should_exit: &self.should_exit }); self.keys.tick_keys(); @@ -236,6 +249,11 @@ impl<'a> Engine<'a> { self.last_second = now; self.frames = 0; } + + match self.should_exit.get() { + true => ControlFlow::Break(()), + false => ControlFlow::Continue(()) + } } pub fn render(&self) -> Result<(), wgpu::SurfaceError> { @@ -258,6 +276,7 @@ pub struct UpdateContext<'a> { pub keys: &'a Keys, pub window: &'a Arc, pub config: &'a SurfaceConfiguration, + pub should_exit: &'a Cell, } pub trait EngineState { diff --git a/src/game/anm/loaded_file.rs b/src/game/anm/loaded_file.rs index 9e7b25a..fc860ff 100644 --- a/src/game/anm/loaded_file.rs +++ b/src/game/anm/loaded_file.rs @@ -1,8 +1,9 @@ -use std::{io::Cursor, ops::Add, path::Path, sync::Arc}; +use std::{io::Cursor, ops::Add, sync::Arc}; use async_std::fs; use bytemuck::{Pod, Zeroable}; use glam::Vec2; +use instructions::anm::{Instruction, Op}; use nonoverlapping_interval_tree::NonOverlappingIntervalTree; use truth::{context::RootEmitter, io::BinReader, AnmFile, Game as TruthGame}; use wgpu::{ @@ -14,10 +15,7 @@ use winit::dpi::PhysicalSize; use crate::utils::game::Game; -use super::{ - image::{produce_image_from_entry, Image}, - vm::opcodes::{Instruction, Op}, -}; +use super::image::{produce_image_from_entry, Image}; pub struct LoadedEntry { _texture: Texture, @@ -169,10 +167,10 @@ pub struct LoadedScript { impl LoadedScript { fn load(script: &truth::anm::Script) -> Arc { - let mut offset_instructions: FastHashMap<_, _> = Default::default(); + let mut offset_instructions: Vec<(_, _)> = Default::default(); let mut current_offset = 0; for (index, inst) in script.instrs.iter().enumerate() { - offset_instructions.insert(current_offset, index); + offset_instructions.push((current_offset, index)); current_offset += 8 + inst.args_blob.len() as u32 } @@ -204,11 +202,12 @@ pub struct LoadedFile { } impl LoadedFile { - pub async fn load(device: &Device, queue: &Queue, size: PhysicalSize, game: Game, file_name: &str) -> Self { - let file_data = fs::read(game.asset_path(file_name)).await.expect("failed to load anm file"); + pub async fn load(device: Arc, queue: Arc, size: PhysicalSize, game: Game, file_name: impl Into + Send) -> Self { + let file_name = file_name.into(); + let file_data = fs::read(game.asset_path(&file_name)).await.expect("failed to load anm file"); let file = AnmFile::read_from_stream( - &mut BinReader::from_reader(&RootEmitter::new_stderr(), file_name, Cursor::new(file_data)), + &mut BinReader::from_reader(&RootEmitter::new_stderr(), &file_name, Cursor::new(file_data)), match game { Game::Mof => TruthGame::Th10, Game::Sa => TruthGame::Th11, @@ -220,7 +219,7 @@ impl LoadedFile { let scripts = file.entries.iter().flat_map(|entry| &entry.scripts).map(|(_, script)| LoadedScript::load(script)).collect(); - let entries = file.entries.iter().map(|entry| LoadedEntry::load(device, queue, size, entry)).collect::>(); + let entries = file.entries.iter().map(|entry| LoadedEntry::load(&device, &queue, size, entry)).collect::>(); let mut sprite_entries = NonOverlappingIntervalTree::new(); diff --git a/src/game/anm/manager/loading.rs b/src/game/anm/manager/loading.rs index a8f243f..d5ecf3f 100644 --- a/src/game/anm/manager/loading.rs +++ b/src/game/anm/manager/loading.rs @@ -13,8 +13,8 @@ use super::Manager; impl Manager { pub fn start_load_anm( &mut self, - device: Arc, - queue: Arc, + device: &Arc, + queue: &Arc, size: PhysicalSize, game: Game, file_name: impl Into, @@ -25,8 +25,9 @@ impl Manager { } let sender = self.anm_sender.clone(); + let (device, queue) = (device.clone(), queue.clone()); Soon::new(async move { - let file = Arc::new(LoadedFile::load(&device, &queue, size, game, &file_name).await); + let file = Arc::new(LoadedFile::load(device, queue, size, game, &file_name).await); sender.send((file_name, file.clone())).unwrap(); @@ -42,8 +43,8 @@ impl Manager { pub fn load_anm( &mut self, - device: &Device, - queue: &Queue, + device: &Arc, + queue: &Arc, window: &Window, game: Game, file_name: impl Into, @@ -54,8 +55,8 @@ impl Manager { } let loaded_anm = Arc::new(async_std::task::block_on(LoadedFile::load( - device, - queue, + device.clone(), + queue.clone(), window.inner_size(), game, &file_name, diff --git a/src/game/anm/manager/mod.rs b/src/game/anm/manager/mod.rs index 530e80b..605b89d 100644 --- a/src/game/anm/manager/mod.rs +++ b/src/game/anm/manager/mod.rs @@ -3,7 +3,8 @@ use bytemuck::{Pod, Zeroable}; use crossbeam::channel::{Receiver, Sender}; use glam::Mat4; use std::{ - collections::VecDeque, sync::{Arc, Weak} + collections::VecDeque, + sync::{Arc, Weak}, }; use wgpu::{naga::FastHashMap, BindGroup, Buffer, PipelineLayout, RenderPipeline, ShaderModule, Texture}; @@ -27,14 +28,14 @@ pub struct Manager { anm_files: FastHashMap>, anm_sender: Sender<(String, Arc)>, anm_receiver: Receiver<(String, Arc)>, - + world_backbuffer_anm: WeakVm, ui_vms: VecDeque, world_vms: VecDeque, depth_texture: Texture, ui_uniform: Uniform, - _world_uniform: Uniform, + world_uniform: Uniform, uniform_buffer: Buffer, render_bind_group: BindGroup, render_pipeline_layout: PipelineLayout, @@ -62,8 +63,6 @@ impl Manager { vm.borrow_mut().interrupt(interrupt); } - self.update_single(&vm); - let mut context = ManagerUpdate::new(); vm.borrow_mut().tick(&mut context); @@ -83,12 +82,17 @@ impl Manager { vm } - fn update_single(&mut self, vm: &Vm) { + pub fn update_single(&mut self, vm: &Vm) { let mut context = ManagerUpdate::new(); vm.borrow_mut().tick(&mut context); context.apply_lists(&mut self.ui_vms, &mut self.world_vms); } + pub fn interrupt_immediately(&mut self, vm: &Vm, interrupt: u32) { + vm.borrow_mut().interrupt(interrupt); + self.update_single(vm); + } + fn update_list(list: &mut VecDeque, context: &mut ManagerUpdate) { list.retain(|value| { if let Some(rc) = value.upgrade() { diff --git a/src/game/anm/manager/rendering.rs b/src/game/anm/manager/rendering.rs index c0611c9..53469b2 100644 --- a/src/game/anm/manager/rendering.rs +++ b/src/game/anm/manager/rendering.rs @@ -4,12 +4,20 @@ use bytemuck::{bytes_of, Pod, Zeroable}; use glam::{Mat4, Vec2, Vec3, Vec4}; use num_traits::FloatConst; use wgpu::{ - include_wgsl, naga::FastHashMap, util::{BufferInitDescriptor, DeviceExt}, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BlendState, BufferDescriptor, BufferUsages, Color, ColorTargetState, ColorWrites, DepthStencilState, Device, Extent3d, FragmentState, LoadOp, Operations, PipelineLayoutDescriptor, PrimitiveState, RenderPass, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, ShaderStages, StoreOp, TextureDescriptor, TextureFormat, TextureUsages, VertexAttribute, VertexBufferLayout, VertexState + include_wgsl, + naga::FastHashMap, + util::{BufferInitDescriptor, DeviceExt}, + BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BlendState, + BufferDescriptor, BufferUsages, Color, ColorTargetState, ColorWrites, DepthStencilState, Device, Extent3d, + FragmentState, LoadOp, Operations, PipelineLayoutDescriptor, PrimitiveState, RenderPass, RenderPassColorAttachment, + RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, ShaderStages, + StoreOp, TextureDescriptor, TextureFormat, TextureUsages, VertexAttribute, VertexBufferLayout, VertexState, }; use winit::dpi::PhysicalSize; use crate::{ - engine::{Engine, UpdateContext}, game::anm::{loaded_file::SpriteUvs, vm::RenderingState, AnmVm} + engine::{Engine, UpdateContext}, + game::anm::{loaded_file::SpriteUvs, vm::RenderingState, AnmVm}, }; use super::{Manager, Uniform, WeakVm}; @@ -203,7 +211,7 @@ impl Manager { depth_texture, ui_uniform, - _world_uniform: world_uniform, + world_uniform, uniform_buffer, render_bind_group, render_pipeline_layout: pipeline_layout, @@ -292,13 +300,6 @@ impl Manager { render_pipeline } - pub fn render_vms(&self, engine: &Engine, pass: &mut RenderPass) { - self.render_layer(engine, pass, &self.ui_vms, 21); - self.render_layer(engine, pass, &self.ui_vms, 22); - self.render_layer(engine, pass, &self.ui_vms, 23); - self.render_layer(engine, pass, &self.ui_vms, 29); - } - fn render_layer(&self, engine: &Engine, pass: &mut RenderPass, list: &VecDeque, layer: u32) { for vm in list { if let Some(vm) = vm.upgrade() { @@ -369,7 +370,7 @@ impl Manager { fn render_ui(&self, engine: &Engine, encoder: &mut wgpu::CommandEncoder, surface: &wgpu::Texture) { engine.queue.write_buffer(&self.uniform_buffer, 0, bytes_of(&self.ui_uniform)); let mut pass = encoder.begin_render_pass(&RenderPassDescriptor { - label: Some("anm"), + label: Some("ui anms"), color_attachments: &[Some(RenderPassColorAttachment { view: &surface.create_view(&Default::default()), resolve_target: None, @@ -398,7 +399,50 @@ impl Manager { ..Default::default() }); - self.render_vms(engine, &mut pass); + self.render_layer(engine, &mut pass, &self.ui_vms, 21); + self.render_layer(engine, &mut pass, &self.ui_vms, 22); + self.render_layer(engine, &mut pass, &self.ui_vms, 23); + self.render_layer(engine, &mut pass, &self.ui_vms, 29); + } + + fn render_world(&self, engine: &Engine, encoder: &mut wgpu::CommandEncoder, surface: &wgpu::Texture) { + engine.queue.write_buffer(&self.uniform_buffer, 0, bytes_of(&self.world_uniform)); + let mut pass = encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("world anms"), + color_attachments: &[Some(RenderPassColorAttachment { + view: &surface.create_view(&Default::default()), + resolve_target: None, + ops: Operations { + // load: LoadOp::Clear(Color { + // // r: 100. / 255., + // // g: 149. / 255., + // // b: 237. / 255., + // r: 1.0, + // g: 1.0, + // b: 1.0, + // a: 1.0, + // }), + load: LoadOp::Load, + ..Default::default() + }, + })], + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &self.depth_texture.create_view(&Default::default()), + depth_ops: Some(Operations { + load: LoadOp::Clear(1.0), + store: StoreOp::Store, + }), + stencil_ops: None, + }), + ..Default::default() + }); + + self.render_layer(engine, &mut pass, &self.world_vms, 0); + self.render_layer(engine, &mut pass, &self.world_vms, 1); + self.render_layer(engine, &mut pass, &self.world_vms, 2); + self.render_layer(engine, &mut pass, &self.world_vms, 3); + self.render_layer(engine, &mut pass, &self.world_vms, 8); + } fn clear(&self, encoder: &mut wgpu::CommandEncoder, surface: &wgpu::Texture) { @@ -434,6 +478,7 @@ impl Manager { pub fn render(&self, engine: &Engine, surface: &wgpu::Texture, encoder: &mut wgpu::CommandEncoder) { self.clear(encoder, surface); + self.render_world(engine, encoder, surface); self.render_ui(engine, encoder, surface); if let Some(backbuffer_anm) = self.world_backbuffer_anm.upgrade() { diff --git a/src/game/anm/vm/execute.rs b/src/game/anm/vm/execute.rs index 091f49c..553cb77 100644 --- a/src/game/anm/vm/execute.rs +++ b/src/game/anm/vm/execute.rs @@ -1,14 +1,17 @@ use glam::{Vec2, Vec3}; +use instructions::{ + anm::{Instruction, Op, PrimOp, PrimSetOp}, + Param, +}; use num_traits::{FloatConst, FromPrimitive}; use rand::Rng; use crate::{ - game::{anm::{manager::ManagerUpdate, VmLocation}, param::Param}, interp::Mode + game::anm::{manager::ManagerUpdate, VmLocation}, + interp::Mode, }; -use super::{ - opcodes::{Instruction, Op, PrimOp, PrimSetOp}, AnmVm -}; +use super::AnmVm; impl AnmVm { fn next_instruction(&mut self) -> Option { @@ -104,7 +107,9 @@ impl AnmVm { } pub fn interrupt(&mut self, interrupt_id: u32) { - self.pending_interrupt = Some(interrupt_id); + if self.script.interrupts.contains_key(&interrupt_id) { + self.pending_interrupt = Some(interrupt_id); + } for ele in self.children.iter() { ele.borrow_mut().interrupt(interrupt_id); @@ -115,7 +120,7 @@ impl AnmVm { if let Some((int_pc, int_time)) = self.pending_interrupt.take().and_then(|int| self.script.interrupts.get(&int).cloned()) { - self.interrupt_return = Some((self.pc, self.time)); + self.interrupt_return.replace((self.pc, self.time)); self.pc = int_pc; self.time = Default::default(); self.time.set_time(int_time) @@ -124,6 +129,7 @@ impl AnmVm { fn return_from_interrupt(&mut self) { if let Some((pc, time)) = self.interrupt_return.take() { + println!("returned"); self.pc = pc; self.time.set_time(time.time()); } @@ -313,26 +319,27 @@ impl AnmVm { self.sprite_mode = mode; } Op::Layer { layer } => self.layer = layer as u32, + Op::ZWriteDisable(value) => self.zwrite_disable = value, Op::Wait { time } => self.time.wait(time as u32), - Op::CaseReturn => { + Op::ReturnFromInterrupt => { self.return_from_interrupt(); } Op::UnknownBitflag { .. } => {} Op::RandMode { mode } => self.random_mode = mode, - Op::ScriptNew { script } => { + Op::NewChildBack { script } => { self.children.push(manager.new_vm(self.file.clone(), script as usize, VmLocation::new_child(self.layer))); } - Op::ScriptNewUI { script } => { + Op::NewChildUIBack { script } => { self.children.push(manager.new_vm(self.file.clone(), script as usize, VmLocation::new_child_ui(self.layer))); } - Op::ScriptNewFront { script } => { + Op::NewChildFront { script } => { self.children.push(manager.new_vm( self.file.clone(), script as usize, VmLocation::new_child_front(self.layer), )); } - Op::ScriptNewUIFront { script } => { + Op::NewChildUIFront { script } => { self.children.push(manager.new_vm( self.file.clone(), script as usize, diff --git a/src/game/anm/vm/mod.rs b/src/game/anm/vm/mod.rs index 79bc84e..501d3e3 100644 --- a/src/game/anm/vm/mod.rs +++ b/src/game/anm/vm/mod.rs @@ -2,17 +2,21 @@ use std::sync::Arc; use atomic_refcell::AtomicRefCell; use glam::{Quat, Vec2, Vec3}; -use opcodes::SpriteType; +use instructions::anm::SpriteType; use wgpu::{BlendComponent, BlendFactor, BlendOperation, BlendState, Buffer, RenderPipeline}; -use crate::{game::timer::Timer, interp::{FloatInterpolator, Vec2Interpolator, Vec3Interpolator}}; +use crate::{ + game::timer::Timer, + interp::{FloatInterpolator, Vec2Interpolator, Vec3Interpolator}, +}; use super::{ - loaded_file::{LoadedEntry, LoadedFile, LoadedScript, LoadedSprite}, manager::ManagerUpdate, Vm + loaded_file::{LoadedEntry, LoadedFile, LoadedScript, LoadedSprite}, + manager::ManagerUpdate, + Vm, }; pub mod execute; -pub(super) mod opcodes; pub(super) struct RenderingState { pub instance_buffer: Buffer, @@ -48,6 +52,7 @@ pub struct AnmVm { pub(super) visible: bool, pub(super) sprite: Option, pub(super) layer: u32, + pub(super) zwrite_disable: bool, random_mode: i32, sprite_mode: SpriteType, pub(super) blend_state: BlendState, @@ -87,6 +92,7 @@ impl AnmVm { visible: true, sprite: None, layer: default_layer, + zwrite_disable: false, sprite_mode: SpriteType::NoRotate, blend_state: BlendState::REPLACE, random_mode: 0, @@ -123,6 +129,14 @@ impl AnmVm { self.debug = true; } + pub fn child(&self, index: usize) -> Option<&Vm> { + self.children.get(index) + } + + pub fn file(&self) -> &Arc { + &self.file + } + pub fn scale(&self) -> Vec2 { self.scale_interpolator.current() } @@ -210,7 +224,7 @@ impl AnmVm { // additive blending color: BlendComponent { src_factor: BlendFactor::SrcAlpha, - dst_factor: BlendFactor::OneMinusSrcAlpha, + dst_factor: BlendFactor::One, operation: BlendOperation::Add, }, alpha: BlendComponent::OVER, diff --git a/src/game/enemy/manager.rs b/src/game/enemy/manager.rs deleted file mode 100644 index d022deb..0000000 --- a/src/game/enemy/manager.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub struct Manager { - - -} diff --git a/src/game/enemy/mod.rs b/src/game/enemy/mod.rs deleted file mode 100644 index 7944012..0000000 --- a/src/game/enemy/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod loaded_file; -pub mod manager; -pub mod vm; diff --git a/src/game/enemy/vm/opcodes.rs b/src/game/enemy/vm/opcodes.rs deleted file mode 100644 index 5a0d1e1..0000000 --- a/src/game/enemy/vm/opcodes.rs +++ /dev/null @@ -1,343 +0,0 @@ -use macros::decode_args; -use num_derive::FromPrimitive; -use num_traits::FromPrimitive; -use truth::llir::RawInstr; -use wgpu::naga::FastHashMap; - -use crate::game::param::Param; - -#[derive(Debug, FromPrimitive, Clone, Copy)] -enum Opcode { - Nop = 0, - - // control flow - Delete = 1, - Ret = 10, - Call = 11, - Jump = 12, - JumpEq = 13, - JumpNeq = 14, - CallAsync = 15, - CallAsyncById = 16, - KillAsync = 17, - KillAllAsync = 21, - - // stack math - StackAlloc = 40, - PushInt = 42, - SetInt = 43, - PushFloat = 44, - SetFloat = 45, - AddInt = 50, - AddFloat = 51, - SubInt = 52, - SubFloat = 53, - MulInt = 54, - MulFloat = 55, - DivInt = 56, - DivFloat = 57, - ModInt = 58, - EqualInt = 59, - EqualFloat = 60, - NotEqualInt = 61, - NotEqualFloat = 62, - LessInt = 63, - LessFloat = 64, - LessEqualInt = 65, - LessEqualFloat = 66, - GreaterInt = 67, - GreaterFloat = 68, - GreaterEqualInt = 69, - GreaterEqualFloat = 70, - NotInt = 71, - NotFloat = 72, - LogicalOr = 73, - LogicalAnd = 74, - BitwiseXor = 75, - BitwiseOr = 76, - BitwiseAnd = 77, - DecrementInt = 78, - Sin = 79, - Cos = 80, - CirclePos = 81, - ValidRad = 82, - Wait = 83, - NegateInt = 84, - NegateFloat = 85, - SquareSum = 86, - GetAngle = 87, - SquareRoot = 88, - Linear = 89, - PointRotate = 90, - FloatTime = 91, - Math92 = 92, - Math93 = 93, - - EnemyCreate = 256, - EnemyCreateAbsolute = 257, - EnemySelectAnm = 258, - EnemySetAnmSprite = 259, - EnemyCreateMirrored = 260, - EnemyCreateMirroredAbsolute = 261, - EnemySetMainAnm = 262, - EnemyPlayAnm = 263, - EnemyPlayAnmAbsolute = 264, - EnemyCreateFiller = 265, - EnemyCreateFillerAbsolute = 266, - EnemyCreateFillerMirrored = 267, - EnemyCreateFillerMirroredAbsolute = 268, - EnemyPlaySelected = 269, - EnemySwitchAnm = 275, - EnemyResetAnm = 276, - EnemyInst277 = 277, - EnemyInst278 = 278, - - MovePos = 280, - MovePosTime = 281, - MovePosRel = 282, - MovePosRelTime = 283, - MoveVel = 284, - MoveVelTime = 285, - MoveVelRel = 286, - MoveVelRelTime = 287, - MoveCircle = 288, - MoveCircleTime = 289, - MoveCircleRel = 290, - MoveCircleRelTime = 291, - MoveRand = 292, - MoveRandRel = 293, - MoveAdd = 294, - MoveAddRel = 295, - MoveEllipse = 300, - MoveEllipseRel = 301, - MoveBezier = 305, - MoveReset = 307, - - SetHurtbox = 320, - SetHitbox = 321, - FlagSet = 322, - FlagClear = 323, - Movelimit = 324, - MoveLimitReset = 325, - ClearExtraDrops = 326, - AddExtraDrops = 327, - SetDropArea = 328, - DropItems = 329, - SetMainDrop = 330, - SetHealth = 331, - SetBoss = 332, - TimerReset = 333, - SetInterrupt = 334, - SetInvulnerable = 335, - PlaySound = 336, - ShakeScreen = 337, - StartDialogue = 338, - WaitForDialogue = 339, - WaitForDeath = 340, - SetTimeout = 341, - SpellById = 342, - EndSpell = 343, - SetChapter = 344, - KillAllEnemies = 345, - ProtectPlayer = 346, - LifeMarker = 347, - SetByDifficultyInt = 355, - SetByDifficultyFloat = 356, - SpellDifficulty = 357, - SpellDifficultyM1 = 358, - SpellDifficultyM2 = 359, - - SpawnerReset = 400, - SpawnerEnable = 401, - SpawnerSetSprite = 402, - SpawnerSetOffset = 403, - SpawnerSetAngle = 404, - SpawnerSetSpeed = 405, - SpawnerSetCount = 406, - SpawnerSetAimMode = 407, - SpawnerSetSounds, - SpawnerSet, - SpawnerSetTransformation, - SpawnerSetTransformation2, - SpawnerAddTransformation, - SpawnerAddTransformation2, - ClearAllBullets, - SpawnerCopy, - ShootLaserAimed, - CancelBullets, -} - -pub enum PrimOp { - Add, - Sub, - Mul, - Div, - Equal, - NotEqual, - LessThan, - LessEqual, - Greater, - GreaterEqual, - Not, - Negate, -} - -pub enum Op { - Nop, - Delete, - Return, - Jump { - index: usize, - time: i32, - }, - JumpEqual { - index: usize, - time: i32, - }, - JumpNotEqual { - index: usize, - time: i32, - }, - CallAsync(String), - CallAsyncId(String, u32), - StackAlloc(usize), - PushInt(Param), - SetInt(i32), - PushFloat(Param), - SetFloat(i32), - IntOp(PrimOp), - ModuloInt, - FloatOp(PrimOp), - Or, - And, - BitwiseXor, - BitwiseOr, - BitwiseAnd, - DecrementInt(i32), - Sin, - Cos, - CirclePos { - x: f32, - y: f32, - angle: Param, - radius: Param, - }, - - SetFlag(i32), - ClearFlag(i32), - - ClearAllBullets, - CancelBullets(i32), -} - -pub struct Instruction { - time: i32, - difficulty: u8, - op: Op, -} - -impl Instruction { - pub fn from_raw(inst: RawInstr, instruction_offsets: &FastHashMap) -> Self { - let param_int = |value: i32, index: u16| { - if inst.param_mask & (1 << index) == 0 { - Param::Value(value) - } else if value < -9932 || value > 10000 { - Param::Value(value) - } else { - Param::Variable(value) - } - }; - let param_float = |value: f32, index: u16| { - if inst.param_mask & (1 << index) == 0 { - Param::Value(value) - } else if value != value.trunc() && value < -9932. || value > 10000. { - Param::Value(value) - } else { - Param::Variable(value as i32) - } - }; - let var_int = |value: i32, index: u16| { - assert!(inst.param_mask & (1 << index) != 0, "param must be a var"); - assert!(value >= -9932 && value <= 10000, "value must be a var"); - value - }; - let var_float = |value: f32, index: u16| { - assert!(inst.param_mask & (1 << index) != 0, "param must be a var"); - assert!( - value.trunc() == value && value >= -9932. && value <= 10000., - "value must be a var" - ); - value as i32 - }; - let offset_to_index = |value: i32| instruction_offsets[&(value as u32)]; - - let args = &inst.args_blob; - let opcode_raw = inst.opcode; - let opcode = Opcode::from_u16(opcode_raw).unwrap_or_else(|| todo!("failed to convert opcode {opcode_raw}")); - let op = match opcode { - Opcode::Nop => Op::Nop, - Opcode::Delete => todo!(), - Opcode::Ret => todo!(), - Opcode::Call => todo!(), - Opcode::Jump => { - let (offset, time) = decode_args!(args, "ot"); - - Op::Jump { - index: offset_to_index(offset), - time, - } - } - Opcode::JumpEq => { - let (offset, time) = decode_args!(args, "ot"); - - Op::JumpEqual { - index: offset_to_index(offset), - time, - } - } - Opcode::JumpNeq => { - let (offset, time) = decode_args!(args, "ot"); - - Op::JumpNotEqual { - index: offset_to_index(offset), - time, - } - } - Opcode::CallAsync => todo!(), - Opcode::CallAsyncById => todo!(), - Opcode::KillAsync => todo!(), - Opcode::KillAllAsync => todo!(), - Opcode::StackAlloc => Op::StackAlloc(decode_args!(args, "S") as usize), - Opcode::PushInt => todo!(), - Opcode::SetInt => todo!(), - Opcode::PushFloat => { - let float = decode_args!(args, "f"); - Op::PushFloat(param_float(float, 0)) - } - Opcode::SetFloat => { - let var = decode_args!(args, "f"); - Op::SetFloat(var_float(var, 0)) - } - Opcode::FlagSet => { - let flag = decode_args!(args, "S"); - Op::SetFlag(flag) - } - Opcode::FlagClear => { - let flag = decode_args!(args, "S"); - Op::ClearFlag(flag) - } - Opcode::CancelBullets => { - todo!() - } - _ => { - unimplemented!("opcode {opcode:?} not implemented"); - } - }; - - Self { - time: inst.time, - difficulty: inst.difficulty, - op, - } - } -} diff --git a/src/game/mod.rs b/src/game/mod.rs index 5bb72e6..759538c 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,4 +1,4 @@ -use std::{path::Path, sync::Arc}; +use std::sync::Arc; use anm::LoadedFile; use states::GameStateMachine; @@ -9,8 +9,7 @@ use crate::{ }; mod anm; -mod enemy; -mod param; +mod stage; mod snd; mod states; mod timer; @@ -30,8 +29,8 @@ struct GameContext<'a> { impl GameContext<'_> { pub fn start_load_anm(&mut self, game: Game, file_name: impl Into) -> Soon> { self.anm_manager.start_load_anm( - self.engine.device.clone(), - self.engine.queue.clone(), + self.engine.device, + self.engine.queue, self.engine.window.inner_size(), game, file_name, @@ -40,9 +39,9 @@ impl GameContext<'_> { pub fn load_anm(&mut self, game: Game, file_name: impl Into) -> Arc { self.anm_manager.load_anm( - &self.engine.device, - &self.engine.queue, - &self.engine.window, + self.engine.device, + self.engine.queue, + self.engine.window, game, file_name, ) diff --git a/src/game/snd/bgm/format.rs b/src/game/snd/bgm/format.rs index 7e71476..e4e652d 100644 --- a/src/game/snd/bgm/format.rs +++ b/src/game/snd/bgm/format.rs @@ -52,7 +52,7 @@ impl BgmFormat { } let track_name = track_name.to_str().unwrap().to_owned(); - println!("track: {:?}", track_name); + println!("track {track_name}"); let track = Track::ref_from_bytes(&bytes[(start + TRACK_NAME_SIZE)..(start + TRACK_SIZE)]).unwrap(); tracks.insert( track_name, @@ -70,7 +70,9 @@ impl BgmFormat { } pub fn get_track_buffers(&mut self, file_name: &str) -> (Range, Range) { - let track = self.tracks.get(file_name).expect("failed to find bgm file"); + let Some(track) = self.tracks.get(file_name) else { + panic!("failed to find bgm file: {file_name:?}") + }; let intro_start = track.track_offset as usize; let track_start = intro_start + track.intro_size as usize; diff --git a/src/game/snd/bgm/mod.rs b/src/game/snd/bgm/mod.rs index 25229d3..b28a4da 100644 --- a/src/game/snd/bgm/mod.rs +++ b/src/game/snd/bgm/mod.rs @@ -1,4 +1,4 @@ -use std::{rc::Rc, sync::Arc}; +use std::sync::Arc; use async_std::fs::read; use format::BgmFormat; diff --git a/src/game/stage/enemy/execute.rs b/src/game/stage/enemy/execute.rs new file mode 100644 index 0000000..0b676b9 --- /dev/null +++ b/src/game/stage/enemy/execute.rs @@ -0,0 +1,6 @@ +use super::Vm; + +impl Vm { + pub fn execute(&mut self) { + } +} diff --git a/src/game/enemy/vm/flags.rs b/src/game/stage/enemy/flags.rs similarity index 70% rename from src/game/enemy/vm/flags.rs rename to src/game/stage/enemy/flags.rs index d540476..70b94db 100644 --- a/src/game/enemy/vm/flags.rs +++ b/src/game/stage/enemy/flags.rs @@ -7,11 +7,12 @@ pub struct Flags { pub disable_offscreen_horizontal: bool, pub disable_offscreen_vertical: bool, pub invincible: bool, - pub intangible: bool, + pub intangible: bool, // override: disable_hurtbox | disable_hitbox | invincible | no_global_delete pub no_global_delete: bool, pub always_global_delete: bool, pub graze: bool, pub only_delete_on_dialog: bool, - #[bits(22)] + pub only_delete_on_clear: bool, + #[bits(21)] _a: u32, } diff --git a/src/game/enemy/loaded_file.rs b/src/game/stage/enemy/loaded_file.rs similarity index 61% rename from src/game/enemy/loaded_file.rs rename to src/game/stage/enemy/loaded_file.rs index ccfa3a5..e524829 100644 --- a/src/game/enemy/loaded_file.rs +++ b/src/game/stage/enemy/loaded_file.rs @@ -1,4 +1,4 @@ -use std::{io::Cursor, path::Path, sync::Arc}; +use std::{io::Cursor, sync::Arc}; use async_std::fs; use futures::future::{join3, join_all}; @@ -8,7 +8,7 @@ use winit::dpi::PhysicalSize; use crate::{game::anm, utils::game::Game}; -use super::vm::opcodes::Instruction; +use instructions::ecl::Instruction; pub struct LoadedFile { anm_files: Vec, @@ -35,30 +35,41 @@ impl LoadedFile { ) .unwrap(); - let anm_files = join_all(file.anim_list.into_iter().map(|sp| sp.value).map(|anm| { - let (device, queue) = (device.clone(), queue.clone()); - async move { anm::LoadedFile::load(&device, &queue, size, game, &anm).await } - })); - let ecl_files = join_all(file.ecli_list.into_iter().map(|sp| sp.value).map(|ecl| { - let (device, queue) = (device.clone(), queue.clone()); - async move { LoadedFile::load(device, queue, size, game, ecl).await } - })); + let anm_files = join_all( + file + .anim_list + .into_iter() + .map(|sp| sp.value) + .map(|anm| async_std::task::spawn(anm::LoadedFile::load(device.clone(), queue.clone(), size, game, anm))), + ); + let ecl_files = join_all( + file + .ecli_list + .into_iter() + .map(|sp| sp.value) + .map(|ecl| LoadedFile::load(device.clone(), queue.clone(), size, game, ecl)), + ); let subs = file.subs.into_iter().map(|(k, v)| (k.value, v)).collect::>(); let subs = async_std::task::spawn_blocking(|| { subs .into_iter() .map(|(sub, instructions)| { - let mut offset_instructions: FastHashMap<_, _> = Default::default(); + let mut offset_instructions: Vec<(_, _)> = Default::default(); let mut current_offset = 0; for (index, inst) in instructions.iter().enumerate() { - offset_instructions.insert(current_offset, index); - current_offset += 8 + inst.args_blob.len() as u32 + offset_instructions.push((current_offset, index)); + current_offset += 16 + inst.args_blob.len() as u32 } + println!("hitting {sub}"); ( sub, - instructions.into_iter().map(|inst| Instruction::from_raw(inst, &offset_instructions)).collect::>(), + instructions + .into_iter() + .zip(offset_instructions.iter()) + .map(|(inst, (offset, _))| Instruction::from_raw(inst, *offset, &offset_instructions)) + .collect::>(), ) }) .collect::>() diff --git a/src/game/enemy/vm/mod.rs b/src/game/stage/enemy/mod.rs similarity index 86% rename from src/game/enemy/vm/mod.rs rename to src/game/stage/enemy/mod.rs index 8ddf7be..c40ce49 100644 --- a/src/game/enemy/vm/mod.rs +++ b/src/game/stage/enemy/mod.rs @@ -1,7 +1,8 @@ use crate::game::timer::Timer; +pub mod loaded_file; mod flags; -pub(super) mod opcodes; +mod execute; struct StackFrame { pc: usize, @@ -17,4 +18,6 @@ enum Value { pub struct Vm { call_stack: heapless::Vec, current_frame: StackFrame, + + } diff --git a/src/game/stage/mod.rs b/src/game/stage/mod.rs new file mode 100644 index 0000000..3125d7e --- /dev/null +++ b/src/game/stage/mod.rs @@ -0,0 +1,71 @@ +use std::{cell::RefCell, rc::Rc}; + +use vm::{LoadedFile, StageVm}; + +use crate::utils::game::Game; + +use super::GameContext; + +mod enemy; +mod vm; + +#[derive(Debug, Clone, Copy)] +pub enum StageNumber { + Stage1 = 1, + Stage2 = 2, + Stage3 = 3, + Stage4 = 4, + Stage5 = 5, + Stage6 = 6, + Extra = 7, +} + +impl StageNumber { + pub fn next(&self) -> Option { + match self { + Self::Stage1 => Some(Self::Stage2), + Self::Stage2 => Some(Self::Stage3), + Self::Stage3 => Some(Self::Stage4), + Self::Stage4 => Some(Self::Stage5), + Self::Stage5 => Some(Self::Stage6), + Self::Stage6 => None, + Self::Extra => None, + } + } + + pub fn stage_music(&self, game: Game, boss: bool) -> &'static str { + match (self, game, boss) { + (Self::Stage1, Game::Mof, false) => "th10_00.wav", + (Self::Stage1, Game::Mof, true) => "th10_01.wav", + _ => todo!() + } + } +} + +pub struct Stage { + vm: StageVm, + enemies: Vec>>, + game: Game, + stage_number: StageNumber, +} + +impl Stage { + pub async fn new(context: &mut GameContext<'_>, game: Game, stage_number: StageNumber) -> Self { + let file = LoadedFile::load(context, game, format!("stage0{}.std", stage_number as u8)).await; + + Self { + vm: StageVm::new(file), + enemies: vec![], + game, + stage_number, + } + } + + pub fn started(&mut self, context: &mut GameContext) { + context.sound_manager.get_bgm_manager(self.game).play_track(self.stage_number.stage_music(self.game, false)); + } + + pub fn update(&mut self, context: &mut GameContext) { + self.vm.update(context); + } +} diff --git a/src/game/stage/vm/loaded_file.rs b/src/game/stage/vm/loaded_file.rs new file mode 100644 index 0000000..173a54c --- /dev/null +++ b/src/game/stage/vm/loaded_file.rs @@ -0,0 +1,58 @@ +use std::io::Cursor; + +use async_std::fs; +use instructions::std::Instruction; +use truth::{context::RootEmitter, io::BinReader, std::StdExtra, Game as TruthGame, StdFile}; +use wgpu::naga::FastHashMap; + +use crate::{ + game::{ + anm::{self, Vm, VmLocation}, + GameContext, + }, + utils::game::Game, +}; + +pub struct LoadedFile { + pub layers: FastHashMap>, + pub instructions: Vec, +} + +impl LoadedFile { + pub async fn load(context: &mut GameContext<'_>, game: Game, file_name: impl Into + Send) -> LoadedFile { + let file_name = file_name.into(); + let file_data = fs::read(game.asset_path(&file_name)).await.expect("failed to load anm file"); + let file = StdFile::read_from_stream( + &mut BinReader::from_reader(&RootEmitter::new_stderr(), &file_name, Cursor::new(file_data)), + match game { + Game::Mof => TruthGame::Th10, + Game::Sa => TruthGame::Th11, + }, + ) + .unwrap(); + let StdExtra::Th10 { anm_path } = file.extra else { + unreachable!() + }; + let anm_file = context.start_load_anm(game, anm_path.value).await; + + let mut layers: FastHashMap> = FastHashMap::default(); + + for instance in file.instances { + let object = file.objects.get(&instance.object).expect("failed to get object"); + for quad in &object.quads { + let vm = context.anm_manager.new_vm(anm_file.clone(), None, quad.anm_script.into(), VmLocation::new()); + layers.entry(object.layer).or_default().push(vm); + } + } + let mut offset_instructions: Vec<(_, _)> = Default::default(); + let mut current_offset = 0; + for (index, inst) in file.script.iter().enumerate() { + offset_instructions.push((current_offset, index)); + current_offset += 8 + inst.args_blob.len() as u32 + } + + let instructions = + file.script.into_iter().map(|inst| Instruction::from_raw(inst, &offset_instructions)).collect::>(); + LoadedFile { layers, instructions } + } +} diff --git a/src/game/stage/vm/mod.rs b/src/game/stage/vm/mod.rs new file mode 100644 index 0000000..3050dd3 --- /dev/null +++ b/src/game/stage/vm/mod.rs @@ -0,0 +1,27 @@ +pub use loaded_file::LoadedFile; + +use crate::game::{timer::Timer, GameContext}; + +mod loaded_file; + +pub struct StageVm { + pub file: LoadedFile, + pc: usize, + timer: Timer, +} + +impl StageVm { + pub fn new(file: LoadedFile) -> Self { + Self { + file, + pc: 0, + timer: Timer::default() + } + } + + pub fn update(&mut self, context: &mut GameContext) { + self.timer.tick(); + + + } +} diff --git a/src/game/states/gameplay/mod.rs b/src/game/states/gameplay/mod.rs index 6b7081c..837d387 100644 --- a/src/game/states/gameplay/mod.rs +++ b/src/game/states/gameplay/mod.rs @@ -1,24 +1,47 @@ -use async_std::task::{block_on, spawn}; +use async_std::task::block_on; use sfsm::State; use crate::{ - game::{enemy::loaded_file::LoadedFile, GameContext}, + game::{ + stage::{Stage, StageNumber}, + GameContext, + }, utils::game::Game, }; -pub struct Gameplay {} +use super::{loading::Loading, title_mof::TitleScreenMof, UPDATE_CONTEXT}; + +pub struct Gameplay { + stage: Stage, +} impl Gameplay { - pub fn new(context: &GameContext) -> Self { - block_on(LoadedFile::load( - context.engine.device.clone(), - context.engine.queue.clone(), - context.engine.window.inner_size(), - Game::Sa, - "default.ecl", - )); - Self {} + pub fn new_blocking(context: &mut GameContext<'_>, game: Game, stage_number: StageNumber) -> Self { + block_on(Self::new(context, game, stage_number)) + } + pub async fn new(context: &mut GameContext<'_>, game: Game, stage_number: StageNumber) -> Self { + let stage = Stage::new(context, game, stage_number).await; + Self { stage } } } -impl State for Gameplay {} +impl State for Gameplay { + fn entry(&mut self) { + UPDATE_CONTEXT.with(|context| self.stage.started(context)); + } + fn execute(&mut self) { + UPDATE_CONTEXT.with(|context| self.stage.update(context)); + } +} + +impl From for Gameplay { + fn from(_: Loading) -> Self { + UPDATE_CONTEXT.with(|context| Gameplay::new_blocking(context, Game::Mof, StageNumber::Stage1)) + } +} + +impl From for Gameplay { + fn from(_: TitleScreenMof) -> Self { + UPDATE_CONTEXT.with(|context| Gameplay::new_blocking(context, Game::Mof, StageNumber::Stage1)) + } +} diff --git a/src/game/states/loading.rs b/src/game/states/loading.rs index 3e99e34..b72628a 100644 --- a/src/game/states/loading.rs +++ b/src/game/states/loading.rs @@ -11,11 +11,12 @@ use crate::{ utils::{game::Game, soon::Soon}, }; -use super::{title::TitleScreen, UPDATE_CONTEXT}; +use super::{gameplay::Gameplay, title_mof::TitleScreenMof, title_sa::TitleScreenSa, UPDATE_CONTEXT}; pub struct Loading { _sig: Vm, _ascii_loading: Vm, + to_gameplay: bool, title_anm: RefCell>>, } @@ -31,27 +32,35 @@ impl Loading { Loading { _sig: sig, _ascii_loading: ascii_loading, + to_gameplay: true, - title_anm: context.start_load_anm(Game::Sa, "title.anm").into(), + title_anm: context.start_load_anm(Game::Mof, "title.anm").into(), } } } -impl State for Loading { - fn execute(&mut self) { - // if UPDATE_CONTEXT.with(|context| context.sound_manager.get_bgm_manager().is_some()) { - // log::info!("a"); - // } - // if self.title_anm.borrow_mut().is_done() { - // log::info!("b") - // } - } -} +sfsm::derive_state!(Loading); -impl Transition for Loading { +impl Transition for Loading { fn guard(&self) -> TransitGuard { UPDATE_CONTEXT .with(|context| context.sound_manager.is_bgm_manager_loaded() && self.title_anm.borrow_mut().is_done()) .into() } } + +impl Transition for Loading { + fn guard(&self) -> TransitGuard { + UPDATE_CONTEXT + .with(|context|!self.to_gameplay && context.sound_manager.is_bgm_manager_loaded() && self.title_anm.borrow_mut().is_done()) + .into() + } +} + +impl Transition for Loading { + fn guard(&self) -> TransitGuard { + UPDATE_CONTEXT + .with(|context| self.to_gameplay && context.sound_manager.is_bgm_manager_loaded() && self.title_anm.borrow_mut().is_done()) + .into() + } +} diff --git a/src/game/states/mod.rs b/src/game/states/mod.rs index 143f30d..1499f97 100644 --- a/src/game/states/mod.rs +++ b/src/game/states/mod.rs @@ -1,21 +1,24 @@ mod gameplay; mod loading; -mod title; - -use gameplay::Gameplay; -use sfsm::*; +mod title_mof; +mod title_sa; use crate::utils::context::ContextMut; +use gameplay::Gameplay; use loading::Loading; -use title::TitleScreen; +use sfsm::*; +use title_mof::TitleScreenMof; +use title_sa::TitleScreenSa; use super::GameContext; pub(super) static UPDATE_CONTEXT: ContextMut = ContextMut::new(); -add_state_machine!(Machine, Loading, {Loading, TitleScreen, Gameplay}, { - Loading => TitleScreen, - TitleScreen => TitleScreen +// add_state_machine!(Machine, Gameplay, {Loading, TitleScreen, Gameplay}, { +add_state_machine!(Machine, Loading, {Loading, TitleScreenSa, TitleScreenMof, Gameplay}, { + Loading => TitleScreenMof, + Loading => Gameplay, + TitleScreenMof => Gameplay, }); pub(super) struct GameStateMachine(Machine); diff --git a/src/game/states/title_mof/main_menu.rs b/src/game/states/title_mof/main_menu.rs new file mode 100644 index 0000000..5605d1c --- /dev/null +++ b/src/game/states/title_mof/main_menu.rs @@ -0,0 +1,218 @@ +use sfsm::*; +use winit::keyboard::KeyCode; + +use crate::{ + game::{ + anm::{Vm, VmLocation}, + states::UPDATE_CONTEXT, + }, + utils::game::Game, +}; + +pub struct MainMenu(MainMenuMachine); +impl MainMenu { + pub fn new() -> MainMenu { + UPDATE_CONTEXT.with(|context| { + let title = context.load_anm(Game::Mof, "title.anm"); + let title_ver = context.load_anm(Game::Mof, "title_v.anm"); + + let mut main_menu = MainMenu(MainMenuMachine::new()); + main_menu + .0 + .start(Boot { + logo: context.anm_manager.new_vm(title, None, 90, VmLocation::new_ui()), + title_ver: context.anm_manager.new_vm(title_ver, None, 0, VmLocation::new_ui()), + }) + .unwrap(); + + main_menu + }) + } + + pub fn switch_to_gameplay(&self) -> bool { + sfsm::IsState::::is_state(&self.0) + } +} +impl State for MainMenu { + fn execute(&mut self) { + self.0.step().unwrap(); + } +} + +add_state_machine!(MainMenuMachine, Boot, {Boot, Starting, Selecting, StartingGameplay, Exiting}, { + Boot => Starting, + Starting => Selecting, + Selecting => StartingGameplay, + Selecting => Exiting, +}); +struct Boot { + logo: Vm, + title_ver: Vm, +} +impl State for Boot { + fn execute(&mut self) { + self.exit(); + UPDATE_CONTEXT.with(|context| { + if context.engine.keys.is_any_key_down() { + self.logo.borrow_mut().interrupt(6); + self.title_ver.borrow_mut().interrupt(1); + } + }); + } +} + +impl Transition for Boot { + fn guard(&self) -> TransitGuard { + self.title_ver.borrow_mut().deleted().into() + } +} +impl From for Starting { + fn from(value: Boot) -> Self { + Starting { + logo: value.logo, + time_in_state: 0, + } + } +} + +struct Starting { + logo: Vm, + time_in_state: u32, +} +impl State for Starting { + fn execute(&mut self) { + self.time_in_state += 1; + } +} + +impl Transition for Starting { + fn guard(&self) -> TransitGuard { + (self.time_in_state >= 30).into() + } +} +impl From for Selecting { + fn from(val: Starting) -> Self { + UPDATE_CONTEXT.with(|context| { + let file = val.logo.borrow().file().clone(); + Selecting { + logo: val.logo, + main_ui: context.anm_manager.new_vm(file, None, 0, VmLocation::new_child_ui(0)), + selected: 0, + selection_changed: false, + input_locked: false, + } + }) + } +} + +struct Selecting { + logo: Vm, + main_ui: Vm, + selected: u32, + selection_changed: bool, + input_locked: bool, +} +impl Selecting { + fn selected(&self, desired: u32) -> TransitGuard { + (self.selected == desired && self.input_locked).into() + } +} +impl State for Selecting { + fn execute(&mut self) { + UPDATE_CONTEXT.with(|context| { + if self.input_locked { + return; + } + if context.engine.keys.was_key_pressed(KeyCode::ArrowUp) { + if let Some(prev) = self.selected.checked_sub(1) { + self.selected = prev; + } else { + self.selected = 7; + } + if self.selected == 1 { + self.selected = 0; + } + + self.selection_changed = false; + } + + if context.engine.keys.was_key_pressed(KeyCode::ArrowDown) { + if self.selected >= 7 { + self.selected = 0; + } else { + self.selected += 1; + } + if self.selected == 1 { + self.selected = 2; + } + + self.selection_changed = false; + } + + if context.engine.keys.was_key_pressed(KeyCode::KeyZ) { + self.main_ui.borrow_mut().interrupt(3); + self.input_locked = true; + } + + if !self.selection_changed { + self.selection_changed = true; + let main_ui = self.main_ui.borrow_mut(); + for i in 0..8u32 { + let child = main_ui.child(i as usize).unwrap(); + let child_bg = main_ui.child(8 + i as usize).unwrap(); + if i == self.selected { + child.borrow_mut().interrupt(i + 7); + child_bg.borrow_mut().interrupt(i + 7); + } else { + context.anm_manager.interrupt_immediately(&child, 3); + context.anm_manager.interrupt_immediately(&child_bg, 3); + } + } + } + }); + } +} + +#[derive(Default)] +struct StartingGameplay; +derive_state!(StartingGameplay); +derive_transition_into_default!(Selecting, StartingGameplay); +impl Transition for Selecting { + fn guard(&self) -> TransitGuard { + self.selected(0) + } + + fn action(&mut self) { + println!("agmlng") + } +} + +impl Transition for Selecting { + fn guard(&self) -> TransitGuard { + self.selected(7) + } +} +impl From for Exiting { + fn from(value: Selecting) -> Self { + Exiting { + _logo: value.logo, + _main_ui: value.main_ui, + timer: 0, + } + } +} + +struct Exiting { + _logo: Vm, + _main_ui: Vm, + timer: u32, +} + +impl State for Exiting { + fn execute(&mut self) { + self.timer += 1; + if self.timer >= 20 { + UPDATE_CONTEXT.with(|context| context.engine.should_exit.set(true)); + } + } +} diff --git a/src/game/states/title_mof/mod.rs b/src/game/states/title_mof/mod.rs new file mode 100644 index 0000000..95b831d --- /dev/null +++ b/src/game/states/title_mof/mod.rs @@ -0,0 +1,72 @@ +mod main_menu; + +use std::sync::Arc; + +use main_menu::MainMenu; +use sfsm::*; +use winit::keyboard::KeyCode; + +use crate::{ + game::{ + anm::{LoadedFile, Vm, VmLocation}, + GameContext, + }, + utils::game::Game, +}; + +use super::{gameplay::Gameplay, loading::Loading, UPDATE_CONTEXT}; + +pub struct TitleScreenMof { + _splash: Vm, + _title_anm: Arc, + machine: TitleScreenMachine, +} + +add_state_machine!(TitleScreenMachine, MainMenu, { MainMenu }, {}); + +impl TitleScreenMof { + fn new(context: &mut GameContext, _: bool) -> Self { + let title = context.load_anm(Game::Mof, "title.anm"); + + // 79, 83 + let splash = context.anm_manager.new_vm(title.clone(), None, 88, VmLocation::new_ui()); + + Self { + _splash: splash, + _title_anm: title, + machine: TitleScreenMachine::new(), + } + } +} + +impl State for TitleScreenMof { + fn entry(&mut self) { + self.machine.start(MainMenu::new()).unwrap(); + } + fn execute(&mut self) { + self.machine.step().unwrap(); + } +} + +impl From for TitleScreenMof { + fn from(_: Loading) -> Self { + UPDATE_CONTEXT.with(|context| { + context.sound_manager.get_bgm_manager(Game::Mof).play_track("th10_02.wav"); + // context.sound_manager.get_bgm_manager(Game::Mof).play_track("th11_00.wav"); + TitleScreenMof::new(context, true) + }) + } +} + +impl Transition for TitleScreenMof { + fn guard(&self) -> TransitGuard { + match &self.machine.states { + TitleScreenMachineStates::MainMenuState(Some(main_menu)) => main_menu.switch_to_gameplay().into(), + _ => TransitGuard::Remain + } + } + + fn action(&mut self) { + println!("hsttkasdgn") + } +} diff --git a/src/game/states/title.rs b/src/game/states/title_sa.rs similarity index 57% rename from src/game/states/title.rs rename to src/game/states/title_sa.rs index f2b8d17..d13749d 100644 --- a/src/game/states/title.rs +++ b/src/game/states/title_sa.rs @@ -11,20 +11,18 @@ use crate::{ use super::{loading::Loading, UPDATE_CONTEXT}; -pub struct TitleScreen { +pub struct TitleScreenSa { _logo: Vm, _splash: Vm, } -impl TitleScreen { +impl TitleScreenSa { fn new(context: &mut GameContext, _: bool) -> Self { - let title = context.load_anm(Game::Mof, "title.anm"); + let title = context.load_anm(Game::Sa, "title.anm"); // 79, 83 - // let splash = context.anm_manager.new_vm(title.clone(), Some(8), 79, VmLocation::new_ui()); - // let logo = context.anm_manager.new_vm(title.clone(), Some(8), 83, VmLocation::new_ui()); - let splash = context.anm_manager.new_vm(title.clone(), Some(8), 88, VmLocation::new_ui()); - let logo = context.anm_manager.new_vm(title.clone(), Some(8), 90, VmLocation::new_ui()); + let splash = context.anm_manager.new_vm(title.clone(), Some(8), 79, VmLocation::new_ui()); + let logo = context.anm_manager.new_vm(title.clone(), Some(8), 83, VmLocation::new_ui()); Self { _logo: logo, @@ -33,7 +31,7 @@ impl TitleScreen { } } -impl State for TitleScreen { +impl State for TitleScreenSa { fn execute(&mut self) { UPDATE_CONTEXT.with(|context| { if context.engine.keys.was_key_pressed(KeyCode::Space) { @@ -43,19 +41,19 @@ impl State for TitleScreen { } } -impl From for TitleScreen { +impl From for TitleScreenSa { fn from(_: Loading) -> Self { UPDATE_CONTEXT.with(|context| { - context.sound_manager.get_bgm_manager(Game::Mof).play_track("th10_02.wav"); - // context.sound_manager.get_bgm_manager_sa().play_track("th11_00.wav"); - TitleScreen::new(context, true) + // context.sound_manager.get_bgm_manager(Game::Mof).play_track("th10_02.wav"); + context.sound_manager.get_bgm_manager(Game::Sa).play_track("th11_00.wav"); + TitleScreenSa::new(context, true) }) } } -impl Transition for TitleScreen { +impl Transition for TitleScreenSa { fn action(&mut self) { - *self = UPDATE_CONTEXT.with(|context| TitleScreen::new(context, false)) + *self = UPDATE_CONTEXT.with(|context| TitleScreenSa::new(context, false)) } fn guard(&self) -> TransitGuard { diff --git a/src/main.rs b/src/main.rs index 9abe486..a400b64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,13 +4,19 @@ pub mod interp; pub mod utils; use std::{ - sync::{Arc, Mutex}, time::Duration + ops::ControlFlow, + sync::Arc, }; use engine::Engine; use wgpu::SurfaceError; use winit::{ - application::ApplicationHandler, dpi::PhysicalSize, event::{ElementState, KeyEvent, StartCause, WindowEvent}, event_loop::EventLoop, keyboard::{KeyCode, PhysicalKey}, platform::pump_events::EventLoopExtPumpEvents, window::Window + application::ApplicationHandler, + dpi::PhysicalSize, + event::{ElementState, KeyEvent, StartCause, WindowEvent}, + event_loop::EventLoop, + keyboard::{KeyCode, PhysicalKey}, + window::Window, }; #[derive(Default)] @@ -73,8 +79,10 @@ impl<'a> ApplicationHandler for App<'a> { self.engine.as_mut().unwrap().resize(new_size); } WindowEvent::RedrawRequested => { - let mut engine = self.engine.as_mut().unwrap(); - engine.update(); + let engine = self.engine.as_mut().unwrap(); + if let ControlFlow::Break(()) = engine.update() { + return event_loop.exit(); + } match engine.render() { Ok(_) => {} Err(SurfaceError::Lost) => { diff --git a/src/utils/context.rs b/src/utils/context.rs index 8e55e11..289419f 100644 --- a/src/utils/context.rs +++ b/src/utils/context.rs @@ -1,4 +1,4 @@ -use std::marker::PhantomData; +use std::{cell::RefCell, ffi::c_void, marker::PhantomData, thread::LocalKey}; #[doc(hidden)] #[derive(Default)] @@ -12,9 +12,9 @@ impl ContextMut { ContextMut(PhantomData) } - fn thread_local() -> &'static std::thread::LocalKey>> { + fn thread_local() -> &'static LocalKey>> { std::thread_local! { - static CTX: std::cell::RefCell> = Default::default(); + static CTX: RefCell> = Default::default(); } &CTX @@ -54,9 +54,9 @@ impl Context { Context(PhantomData) } - fn thread_local() -> &'static std::thread::LocalKey>> { + fn thread_local() -> &'static LocalKey>> { std::thread_local! { - static CTX: std::cell::RefCell> = Default::default(); + static CTX: RefCell> = Default::default(); } &CTX diff --git a/utils/th10.eclm b/utils/th10.eclm index 2a86cf6..6adabcb 100644 --- a/utils/th10.eclm +++ b/utils/th10.eclm @@ -649,7 +649,7 @@ !ins_intrinsics #10 RetStack() ## Internally calls FrameLeave() #11 CallStack() -12 Jmp() +#12 Jmp() #13 StackJmp(op="==") #14 StackJmp(op="!=") #15 CallStackAsync()