From eeb290a6f3ecadfdd17f8d0f0b7a64f18554bc06 Mon Sep 17 00:00:00 2001 From: Alula <6276139+alula@users.noreply.github.com> Date: Sun, 27 Jun 2021 23:14:36 +0200 Subject: [PATCH] lua 5.3, a bunch of new functionality for scripting api --- Cargo.toml | 3 +- src/engine_constants/mod.rs | 20 ++ src/npc/mod.rs | 106 ++++++--- src/player/mod.rs | 2 +- src/scene/game_scene.rs | 11 +- src/scripting/boot.lua | 422 ++++++++++++++++++++++++++++++++-- src/scripting/doukutsu.d.ts | 243 ++++++++++++++++---- src/scripting/doukutsu.rs | 442 +++++++++++++++++++++++++++++++++++- src/scripting/mod.rs | 106 ++++++++- src/scripting/player.rs | 135 ----------- src/scripting/scene.rs | 42 +--- src/shared_game_state.rs | 32 +-- src/sound/pixtone.rs | 61 ++--- 13 files changed, 1293 insertions(+), 332 deletions(-) delete mode 100644 src/scripting/player.rs diff --git a/Cargo.toml b/Cargo.toml index fa4d795..0435ed1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ android = [] [dependencies] #cpal = { path = "./3rdparty/cpal" } #glutin = { path = "./3rdparty/glutin/glutin", optional = true } +#lua-ffi = { path = "./3rdparty/luajit-rs", optional = true } #winit = { path = "./3rdparty/winit", optional = true, default_features = false, features = ["x11"] } bitvec = "0.20" byteorder = "1.4" @@ -54,7 +55,7 @@ lazy_static = "1.4.0" lewton = { version = "0.10.2", optional = true } libc = { version = "0.2", optional = true } log = "0.4" -lua-ffi = { git = "https://github.com/doukutsu-rs/lua-ffi.git", rev = "1ef3caf772d72068297ddf75df06fd2ef8c1daab", optional = true } +lua-ffi = { git = "https://github.com/doukutsu-rs/lua-ffi.git", rev = "e0b2ff5960f7ef9974aa9675cebe4907bee0134f", optional = true } num-derive = "0.3.2" num-traits = "0.2.12" paste = "1.0.0" diff --git a/src/engine_constants/mod.rs b/src/engine_constants/mod.rs index 8c307ff..83d4dea 100644 --- a/src/engine_constants/mod.rs +++ b/src/engine_constants/mod.rs @@ -48,6 +48,16 @@ pub struct PlayerConsts { pub frames_bubble: [Rect; 2], } +#[derive(Debug, Copy, Clone)] +pub struct GameConsts { + pub intro_stage: u16, + pub intro_event: u16, + pub intro_player_pos: (i16, i16), + pub new_game_stage: u16, + pub new_game_event: u16, + pub new_game_player_pos: (i16, i16), +} + #[derive(Debug)] pub struct CaretConsts { pub offsets: [(i32, i32); 18], @@ -235,6 +245,7 @@ pub struct EngineConstants { pub is_cs_plus: bool, pub is_switch: bool, pub supports_og_textures: bool, + pub game: GameConsts, pub player: PlayerConsts, pub booster: BoosterConsts, pub caret: CaretConsts, @@ -259,6 +270,7 @@ impl Clone for EngineConstants { is_cs_plus: self.is_cs_plus, is_switch: self.is_switch, supports_og_textures: self.supports_og_textures, + game: self.game, player: self.player, booster: self.booster, caret: self.caret.clone(), @@ -285,6 +297,14 @@ impl EngineConstants { is_cs_plus: false, is_switch: false, supports_og_textures: false, + game: GameConsts { + intro_stage: 72, + intro_event: 100, + intro_player_pos: (3, 3), + new_game_stage: 13, + new_game_event: 200, + new_game_player_pos: (10, 8) + }, player: PlayerConsts { life: 3, max_life: 3, diff --git a/src/npc/mod.rs b/src/npc/mod.rs index 366a8b8..15f9d32 100644 --- a/src/npc/mod.rs +++ b/src/npc/mod.rs @@ -1,18 +1,19 @@ use std::io; use std::io::Cursor; -use byteorder::{LE, ReadBytesExt}; -use crate::framework::context::Context; -use crate::framework::error::GameResult; +use byteorder::{ReadBytesExt, LE}; use num_traits::abs; use crate::bitfield; -use crate::weapon::bullet::BulletManager; -use crate::common::{Condition, interpolate_fix9_scale, Rect}; use crate::common::Direction; use crate::common::Flag; +use crate::common::{interpolate_fix9_scale, Condition, Rect}; +use crate::components::flash::Flash; +use crate::components::number_popup::NumberPopup; use crate::entity::GameEntity; use crate::frame::Frame; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::npc::list::NPCList; use crate::physics::PhysicalEntity; use crate::player::Player; @@ -20,8 +21,7 @@ use crate::rng::Xoroshiro32PlusPlus; use crate::shared_game_state::SharedGameState; use crate::stage::Stage; use crate::str; -use crate::components::flash::Flash; -use crate::components::number_popup::NumberPopup; +use crate::weapon::bullet::BulletManager; pub mod ai; pub mod boss; @@ -155,7 +155,13 @@ impl NPC { } } - pub fn draw_if_layer(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame, layer: NPCLayer) -> GameResult { + pub fn draw_if_layer( + &self, + state: &mut SharedGameState, + ctx: &mut Context, + frame: &Frame, + layer: NPCLayer, + ) -> GameResult { if self.layer == layer { self.draw(state, ctx, frame)? } @@ -165,8 +171,23 @@ impl NPC { } impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Flash)> for NPC { - fn tick(&mut self, state: &mut SharedGameState, (players, npc_list, stage, bullet_manager, flash): ([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Flash)) -> GameResult { + fn tick( + &mut self, + state: &mut SharedGameState, + (players, npc_list, stage, bullet_manager, flash): ( + [&mut Player; 2], + &NPCList, + &mut Stage, + &BulletManager, + &mut Flash, + ), + ) -> GameResult { + let mut npc_hook_ran = false; + #[cfg(feature = "scripting")] + { npc_hook_ran = state.lua.try_run_npc_hook(self.id, self.npc_type); } + match self.npc_type { + _ if npc_hook_ran => Ok(()), 0 => self.tick_n000_null(), 1 => self.tick_n001_experience(state, stage), 2 => self.tick_n002_behemoth(state), @@ -386,11 +407,11 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Fl 359 => self.tick_n359_water_droplet_generator(state, players, npc_list), _ => { #[cfg(feature = "hooks")] - { - crate::hooks::run_npc_hook(self, state, players, npc_list, stage, bullet_manager); - } + { + crate::hooks::run_npc_hook(self, state, players, npc_list, stage, bullet_manager); + } Ok(()) - }, + } }?; self.popup.x = self.x; @@ -420,20 +441,19 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Fl let texture = state.npc_table.get_texture_name(self.spritesheet_id); let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, texture)?; - let off_x = if self.direction == Direction::Left { self.display_bounds.left } else { self.display_bounds.right } as i32; - let shock = if self.shock > 0 { - (2 * ((self.shock as i32 / 2) % 2) - 1) as f32 - } else { 0.0 }; + let off_x = + if self.direction == Direction::Left { self.display_bounds.left } else { self.display_bounds.right } as i32; + let shock = if self.shock > 0 { (2 * ((self.shock as i32 / 2) % 2) - 1) as f32 } else { 0.0 }; let (frame_x, frame_y) = frame.xy_interpolated(state.frame_time); batch.add_rect( - interpolate_fix9_scale(self.prev_x - off_x, - self.x - off_x, - state.frame_time) + shock - frame_x, - interpolate_fix9_scale(self.prev_y - self.display_bounds.top as i32, - self.y - self.display_bounds.top as i32, - state.frame_time) - frame_y, + interpolate_fix9_scale(self.prev_x - off_x, self.x - off_x, state.frame_time) + shock - frame_x, + interpolate_fix9_scale( + self.prev_y - self.display_bounds.top as i32, + self.y - self.display_bounds.top as i32, + state.frame_time, + ) - frame_y, &self.anim_rect, ); batch.draw(ctx)?; @@ -446,31 +466,55 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Fl impl PhysicalEntity for NPC { #[inline(always)] - fn x(&self) -> i32 { self.x } + fn x(&self) -> i32 { + self.x + } #[inline(always)] - fn y(&self) -> i32 { self.y } + fn y(&self) -> i32 { + self.y + } #[inline(always)] - fn vel_x(&self) -> i32 { self.vel_x } + fn vel_x(&self) -> i32 { + self.vel_x + } #[inline(always)] - fn vel_y(&self) -> i32 { self.vel_y } + fn vel_y(&self) -> i32 { + self.vel_y + } #[inline(always)] fn hit_rect_size(&self) -> usize { if self.size >= 3 { - if self.cond.drs_boss() { 4 } else { 3 } + if self.cond.drs_boss() { + 4 + } else { + 3 + } } else { 2 } } #[inline(always)] - fn offset_x(&self) -> i32 { if self.size >= 3 && !self.cond.drs_boss() { -0x1000 } else { 0 } } + fn offset_x(&self) -> i32 { + if self.size >= 3 && !self.cond.drs_boss() { + -0x1000 + } else { + 0 + } + } #[inline(always)] - fn offset_y(&self) -> i32 { if self.size >= 3 && !self.cond.drs_boss() { -0x1000 } else { 0 } } + fn offset_y(&self) -> i32 { + if self.size >= 3 && !self.cond.drs_boss() { + -0x1000 + } else { + 0 + } + } #[inline(always)] fn hit_bounds(&self) -> &Rect { @@ -681,7 +725,7 @@ impl NPCTable { 23 => "Npc/NpcRegu", 26 => "TextBox", 27 => "Face", - _ => "Npc/Npc0" + _ => "Npc/Npc0", } } } diff --git a/src/player/mod.rs b/src/player/mod.rs index c96094d..1ac3bce 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -670,7 +670,7 @@ impl Player { } pub fn damage(&mut self, hp: i32, state: &mut SharedGameState, npc_list: &NPCList) { - if state.settings.god_mode || self.shock_counter > 0 { + if self.life == 0 || hp <= 0 || state.settings.god_mode || self.shock_counter > 0 { return; } diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index e943bbb..1886663 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -1643,6 +1643,8 @@ impl Scene for GameScene { state.textscript_vm.set_scene_script(self.stage.load_text_script(&state.base_path, &state.constants, ctx)?); state.textscript_vm.suspend = false; state.tile_size = self.stage.map.tile_size; + #[cfg(feature = "scripting")] + state.lua.set_game_scene(self as *mut _); self.player1.controller = state.settings.create_player1_controller(); self.player2.controller = state.settings.create_player2_controller(); @@ -1653,11 +1655,11 @@ impl Scene for GameScene { let mut npc = NPC::create_from_data(npc_data, &state.npc_table, state.tile_size); if npc.npc_flags.appear_when_flag_set() { - if state.get_flag(npc_data.flag_num as usize) { + if state.get_flag(npc_data.flag_num as _) { npc.cond.set_alive(true); } } else if npc.npc_flags.hide_unless_flag_set() { - if !state.get_flag(npc_data.flag_num as usize) { + if !state.get_flag(npc_data.flag_num as _) { npc.cond.set_alive(true); } } else { @@ -1776,7 +1778,7 @@ impl Scene for GameScene { TextScriptVM::run(state, self, ctx)?; #[cfg(feature = "scripting")] - state.lua.scene_tick(self); + state.lua.scene_tick(); if state.control_flags.control_enabled() { self.tick = self.tick.wrapping_add(1); @@ -1852,9 +1854,6 @@ impl Scene for GameScene { self.draw_bullets(state, ctx)?; self.player2.draw(state, ctx, &self.frame)?; self.player1.draw(state, ctx, &self.frame)?; - /*if state.settings.shader_effects && self.water_visible { - self.draw_water(state, ctx)?; - }*/ self.water_renderer.draw(state, ctx, &self.frame)?; self.draw_tiles(state, ctx, TileLayer::Foreground)?; diff --git a/src/scripting/boot.lua b/src/scripting/boot.lua index 556d943..045ea6f 100644 --- a/src/scripting/boot.lua +++ b/src/scripting/boot.lua @@ -1,12 +1,77 @@ +-- sandboxing (still tbd) +--_ENV = { +-- ipairs = ipairs, +-- next = next, +-- pairs = pairs, +-- pcall = pcall, +-- tonumber = tonumber, +-- tostring = tostring, +-- type = type, +-- unpack = unpack, +-- coroutine = { create = coroutine.create, resume = coroutine.resume, +-- running = coroutine.running, status = coroutine.status, +-- wrap = coroutine.wrap }, +-- string = { byte = string.byte, char = string.char, find = string.find, +-- format = string.format, gmatch = string.gmatch, gsub = string.gsub, +-- len = string.len, lower = string.lower, match = string.match, +-- rep = string.rep, reverse = string.reverse, sub = string.sub, +-- upper = string.upper }, +-- table = { insert = table.insert, maxn = table.maxn, remove = table.remove, +-- sort = table.sort }, +-- math = { abs = math.abs, acos = math.acos, asin = math.asin, +-- atan = math.atan, atan2 = math.atan2, ceil = math.ceil, cos = math.cos, +-- cosh = math.cosh, deg = math.deg, exp = math.exp, floor = math.floor, +-- fmod = math.fmod, frexp = math.frexp, huge = math.huge, +-- ldexp = math.ldexp, log = math.log, log10 = math.log10, max = math.max, +-- min = math.min, modf = math.modf, pi = math.pi, pow = math.pow, +-- rad = math.rad, random = math.random, sin = math.sin, sinh = math.sinh, +-- sqrt = math.sqrt, tan = math.tan, tanh = math.tanh }, +-- os = { clock = os.clock, difftime = os.difftime, time = os.time }, +--} + +-- __doukutsu_rs is an internal API used meant to be used solely by doukutsu-rs to implement higher-level, +-- documented APIs and is a subject to change. Do NOT use it or your scripts will break. + +__doukutsu_rs_runtime_dont_touch = {} doukutsu = {} -doukutsu._registered = { +ModCS = { + Flag = {}, + Game = { + Act = nil, + }, + Mod = {}, + NPC = {}, + Organya = {}, + Player = {}, + Rect = {}, + SkipFlag = {}, + Sound = {}, +} + +__doukutsu_rs_runtime_dont_touch._requires = {} + +require = function(modname) + if __doukutsu_rs_runtime_dont_touch._requires[modname] == nil then + local mod = __doukutsu_rs:loadScript(modname) + + __doukutsu_rs_runtime_dont_touch._requires[modname] = { mod = mod, loaded = True } + else + return __doukutsu_rs_runtime_dont_touch._requires[modname].mod + end +end + +__doukutsu_rs_runtime_dont_touch._registered = { tick = {}, } -doukutsu._handlers = setmetatable({ +__doukutsu_rs_runtime_dont_touch._handlers = setmetatable({ tick = function(scene) - for _, h in pairs(doukutsu._registered.tick) do + if type(ModCS.Game.Act) == 'function' then + pcall(ModCS.Game.Act) + end + + for _, h in pairs(__doukutsu_rs_runtime_dont_touch._registered.tick) do pcall(h, scene) end end, @@ -16,7 +81,27 @@ doukutsu._handlers = setmetatable({ end, }) -doukutsu._initializeScript = function(script) +__doukutsu_rs_runtime_dont_touch._registeredNPCHooks = {} + +__doukutsu_rs_runtime_dont_touch._tryNPCHook = function(npc_id, npc_type) + local hook = __doukutsu_rs_runtime_dont_touch._registeredNPCHooks[npc_type] + if hook ~= nil then + local npc = __doukutsu_rs_runtime_dont_touch._getNPCRef(npc_id) + if npc ~= nil then + local status, err = pcall(hook, npc) + + if not status then + print("error in npc handler:" .. err) + end + end + + return true + end + + return false +end + +__doukutsu_rs_runtime_dont_touch._initializeScript = function(script) -- for compatibility with Lua 5.2+, copy-pasted from Lua mailing list -- http://lua-users.org/lists/lua-l/2010-06/msg00313.html local _setfenv = setfenv or function(f, t) @@ -45,37 +130,255 @@ doukutsu._initializeScript = function(script) script() end -doukutsu.playSfx = function(id) - __doukutsu:playSfx(id) +__doukutsu_rs_runtime_dont_touch._createPlayerRef = function(player_id) + local player_ref = { id = player_id } + + function player_ref.damage(self, value) + __doukutsu_rs:playerCommand(rawget(self, "id"), 0x200, value) + end + + setmetatable(player_ref, { + __index = function(self, property) + if property == "x" then + return __doukutsu_rs:playerCommand(rawget(self, "id"), 0x10) + elseif property == "y" then + return __doukutsu_rs:playerCommand(rawget(self, "id"), 0x11) + elseif property == "velX" then + return __doukutsu_rs:playerCommand(rawget(self, "id"), 0x12) + elseif property == "velY" then + return __doukutsu_rs:playerCommand(rawget(self, "id"), 0x13) + else + return nil + end + end, + __newindex = function(self, property, val) + if property == "x" then + __doukutsu_rs:playerCommand(rawget(self, "id"), 0x110, val) + elseif property == "y" then + __doukutsu_rs:playerCommand(rawget(self, "id"), 0x111, val) + elseif property == "velX" then + __doukutsu_rs:playerCommand(rawget(self, "id"), 0x112, val) + elseif property == "velY" then + __doukutsu_rs:playerCommand(rawget(self, "id"), 0x113, val) + end + + return nil + end, + }) + + return player_ref end -doukutsu.playSong = function(id) - __doukutsu:playSong(id) +__doukutsu_rs_runtime_dont_touch._npcRefs = {} + +__doukutsu_rs_runtime_dont_touch._getNPCRef = function(npc_id) + if __doukutsu_rs_runtime_dont_touch._npcRefs[npc_id] == nil then + local npc_ref = __doukutsu_rs_runtime_dont_touch._createNPCRef(npc_id) + if npc_ref == nil then + return nil + end + + __doukutsu_rs_runtime_dont_touch._npcRefs[npc_id] = npc_ref + + return npc_ref + end + + return __doukutsu_rs_runtime_dont_touch._npcRefs[npc_id] end -doukutsu.on = function(event, handler) +__doukutsu_rs_runtime_dont_touch._createNPCRef = function(npc_id) + local npc_ref = { id = npc_id } + + function npc_ref.closestPlayer(self) + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x200) + end + + function npc_ref.random(self, min, max) + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x201, min, max) + end + + function npc_ref.hitLeftWall(self) + local flags = __doukutsu_rs:npcCommand(rawget(self, "id"), 0x0f) + return (flags & 1) ~= 0 + end + + function npc_ref.hitCeiling(self) + local flags = __doukutsu_rs:npcCommand(rawget(self, "id"), 0x0f) + return (flags & 2) ~= 0 + end + + function npc_ref.hitRightWall(self) + local flags = __doukutsu_rs:npcCommand(rawget(self, "id"), 0x0f) + return (flags & 4) ~= 0 + end + + function npc_ref.hitFloor(self) + local flags = __doukutsu_rs:npcCommand(rawget(self, "id"), 0x0f) + return (flags & 8) ~= 0 + end + + function npc_ref.parentNPC(self) + local id = __doukutsu_rs:npcCommand(rawget(self, "id"), 0x1c) + return __doukutsu_rs_runtime_dont_touch._getNPCRef(id) + end + + function npc_ref.getAnimRect(self) + local l, t, r, b = __doukutsu_rs:npcCommand(rawget(self, "id"), 0x202) + return { l, t, r, b } + end + + function npc_ref.setAnimRect(self, l, t, r, b) + if type(l) == "number" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x203, l, t, r, b) + elseif type(l) == "table" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x203, l[1], l[2], l[3], l[4]) + else + error("Invalid parameters supplied.") + end + end + + setmetatable(npc_ref, { + __index = function(self, property) + if property == "x" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x10) + elseif property == "y" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x11) + elseif property == "velX" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x12) + elseif property == "velY" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x13) + elseif property == "velX2" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x14) + elseif property == "velY2" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x15) + elseif property == "actionNum" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x16) + elseif property == "animNum" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x17) + elseif property == "actionCounter" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x18) + elseif property == "actionCounter2" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x19) + elseif property == "actionCounter3" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x1a) + elseif property == "animCounter" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x1b) + elseif property == "parentId" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x1c) + elseif property == "npcType" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x1d) + elseif property == "life" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x1e) + elseif property == "flagNum" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x1f) + elseif property == "eventNum" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x20) + elseif property == "direction" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x21) + elseif property == "rawDirection" then + return __doukutsu_rs:npcCommand(rawget(self, "id"), 0x22) + else + return nil + end + end, + __newindex = function(self, property, val) + if property == "x" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x110, val) + elseif property == "y" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x111, val) + elseif property == "velX" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x112, val) + elseif property == "velY" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x113, val) + elseif property == "velX2" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x114, val) + elseif property == "velY2" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x115, val) + elseif property == "actionNum" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x116, val) + elseif property == "animNum" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x117, val) + elseif property == "actionCounter" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x118, val) + elseif property == "actionCounter2" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x119, val) + elseif property == "actionCounter3" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x11a, val) + elseif property == "animCounter" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x11b, val) + elseif property == "parentId" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x11c, val) + elseif property == "npcType" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x11d, val) + elseif property == "life" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x11e, val) + elseif property == "flagNum" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x11f, val) + elseif property == "eventNum" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x120, val) + elseif property == "direction" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x121, val) + elseif property == "rawDirection" then + __doukutsu_rs:npcCommand(rawget(self, "id"), 0x121, val) + end + + return nil + end, + }) + + return npc_ref +end + +__doukutsu_rs_runtime_dont_touch._playerRef0 = __doukutsu_rs_runtime_dont_touch._createPlayerRef(0) +__doukutsu_rs_runtime_dont_touch._playerRef1 = __doukutsu_rs_runtime_dont_touch._createPlayerRef(1) + +doukutsu.player = __doukutsu_rs_runtime_dont_touch._playerRef0 + +function doukutsu.playSfx(id) + __doukutsu_rs:playSfx(id) +end + +function doukutsu.playSfxLoop(id) + __doukutsu_rs:playSfxLoop(id) +end + +function doukutsu.playSong(id) + __doukutsu_rs:playSong(id) +end + +function doukutsu.players() + return { __doukutsu_rs_runtime_dont_touch._playerRef0, __doukutsu_rs_runtime_dont_touch._playerRef1 } +end + +function doukutsu.setNPCHandler(npc_type, handler) + assert(type(npc_type) == "number", "npc type must be an integer.") + + __doukutsu_rs_runtime_dont_touch._registeredNPCHooks[npc_type] = handler +end + +function doukutsu.on(event, handler) assert(type(event) == "string", "event type must be a string.") assert(type(handler) == "function", "event handler must be a function.") - if doukutsu._registered[event] == nil then + if __doukutsu_rs_runtime_dont_touch._registered[event] == nil then error("Unknown event: " .. event) end - table.insert(doukutsu._registered[event], handler) + table.insert(__doukutsu_rs_runtime_dont_touch._registered[event], handler) return handler end -doukutsu.removeHandler = function(event, handler) +function doukutsu.removeHandler(event, handler) assert(type(event) == "string", "event type must be a string.") assert(type(handler) == "function", "event handler must be a function.") - if doukutsu._registered[event] == nil then + if __doukutsu_rs_runtime_dont_touch._registered[event] == nil then error("Unknown event: " .. event) end local index = -1 - for i, h in pairs(doukutsu._registered[event]) do + for i, h in pairs(__doukutsu_rs_runtime_dont_touch._registered[event]) do if handler == h then index = i break @@ -83,8 +386,97 @@ doukutsu.removeHandler = function(event, handler) end if index ~= -1 then - table.remove(doukutsu._registered[event], index) + table.remove(__doukutsu_rs_runtime_dont_touch._registered[event], index) end return handler end + +ModCS.Color = { r = 0, g = 0, b = 0 } +function ModCS.Color:_new(o) + o = o or {} + setmetatable(o, self) + self.__index = self + self.r = 0 + self.g = 0 + self.b = 0 + return o +end + +ModCS.Color.Create = function(color) + return ModCS.Color:_new(nil) +end + +function ModCS.Color:Set(r, g, b) + self.r = tonumber(r) or 0 + self.g = tonumber(g) or 0 + self.b = tonumber(b) or 0 +end + +function ModCS.Color:Box() + -- stub +end + +function ModCS.Mod.SetName(name) + -- stub +end + +function ModCS.Mod.SetAuthor(name) + -- stub +end + +function ModCS.Mod.SetVersion(v1, v2, v3, v4) + -- stub +end + +function ModCS.Mod.SetOpening(stage_id, event_id, ticks) + __doukutsu_rs:setEngineConstant(0x1000, event_id) + __doukutsu_rs:setEngineConstant(0x1001, stage_id) + -- todo ticks +end + +function ModCS.Mod.SetStart(stage_id, pos_x, pos_y, event_id) + __doukutsu_rs:setEngineConstant(0x1003, event_id) + __doukutsu_rs:setEngineConstant(0x1004, stage_id) + __doukutsu_rs:setEngineConstant(0x1005, pos_x, pos_y) +end + +function ModCS.Flag.Set(id) + __doukutsu_rs:setFlag(id, True) +end + +function ModCS.Flag.Unset(id) + __doukutsu_rs:setFlag(id, False) +end + +function ModCS.Flag.Get(id) + return __doukutsu_rs:getFlag(id) or False +end + +function ModCS.SkipFlag.Set(id) + __doukutsu_rs:setSkipFlag(id, True) +end + +function ModCS.SkipFlag.Unset(id) + __doukutsu_rs:setSkipFlag(id, False) +end + +function ModCS.SkipFlag.Get(id) + return __doukutsu_rs:getSkipFlag(id) or False +end + +function ModCS.Organya.Play(id) + __doukutsu_rs:playSong(id) +end + +function ModCS.Sound.Play(id, loop) + if loop then + __doukutsu_rs:playSfxLoop(id) + else + __doukutsu_rs:playSfx(id) + end +end + +function ModCS.Player.AddMaxLife(life) + -- stub +end diff --git a/src/scripting/doukutsu.d.ts b/src/scripting/doukutsu.d.ts index ccd0575..c6f9805 100644 --- a/src/scripting/doukutsu.d.ts +++ b/src/scripting/doukutsu.d.ts @@ -1,5 +1,150 @@ declare type EventHandler = (this: void, param: T) => void; +declare interface NPC { + /** + * The ID of NPC, equivalent to offset in NPC list of current scene. + */ + id: number; + + /** + * The type ID of NPC. + */ + npcType: number; + + /** + * Position of NPC in X axis (as floating point, not internal fixed point representation). + */ + x: number; + + /** + * Position of NPC in Y axis (as floating point, not internal fixed point representation). + */ + y: number; + + /** + * Velocity of NPC in X axis (as floating point, not internal fixed point representation). + */ + velX: number; + + /** + * Velocity of NPC in Y axis (as floating point, not internal fixed point representation). + */ + velY: number; + + /** + * Alternate velocity of NPC in X axis (as floating point, not internal fixed point representation). + */ + velX2: number; + + /** + * Alternate velocity of NPC in Y axis (as floating point, not internal fixed point representation). + */ + velY2: number; + + /** + * Current action id (the one that can be set with void | null): void; + /** * Registers an event handler called after all scripts are loaded. * @param event event name diff --git a/src/scripting/doukutsu.rs b/src/scripting/doukutsu.rs index 6bc2f41..d2a7385 100644 --- a/src/scripting/doukutsu.rs +++ b/src/scripting/doukutsu.rs @@ -1,7 +1,15 @@ +use std::io::Read; + use lua_ffi::ffi::luaL_Reg; use lua_ffi::{c_int, LuaObject, State}; -use crate::scripting::LuaScriptingState; +use crate::common::{Direction, Rect}; +use crate::framework::filesystem; +use crate::npc::NPC; +use crate::player::Player; +use crate::rng::RNG; +use crate::scene::game_scene::GameScene; +use crate::scripting::{check_status, LuaScriptingState, DRS_RUNTIME_GLOBAL}; pub struct Doukutsu { pub ptr: *mut LuaScriptingState, @@ -23,6 +31,16 @@ impl Doukutsu { 0 } + unsafe fn lua_play_sfx_loop(&self, state: &mut State) -> c_int { + if let Some(index) = state.to_int(2) { + let game_state = &mut (*(*self.ptr).state_ptr); + + game_state.sound_manager.loop_sfx(index as u8); + } + + 0 + } + unsafe fn lua_play_song(&self, state: &mut State) -> c_int { if let Some(index) = state.to_int(2) { let game_state = &mut (*(*self.ptr).state_ptr); @@ -58,11 +76,398 @@ impl Doukutsu { 1 } + + unsafe fn lua_set_flag(&self, state: &mut State) -> c_int { + let flag_id = state.to_int(2); + let flag_val = state.to_bool(3); + + if let (Some(flag_id), Some(flag_val)) = (flag_id, flag_val) { + let game_state = &mut (*(*self.ptr).state_ptr); + + game_state.set_flag(flag_id.max(0) as usize, flag_val); + } + + 0 + } + + unsafe fn lua_get_skip_flag(&self, state: &mut State) -> c_int { + if let Some(index) = state.to_int(2) { + let game_state = &mut (*(*self.ptr).state_ptr); + + state.push(game_state.get_skip_flag(index.max(0) as usize)); + } else { + state.push_nil(); + } + + 1 + } + + unsafe fn lua_set_skip_flag(&self, state: &mut State) -> c_int { + let flag_id = state.to_int(2); + let flag_val = state.to_bool(3); + + if let (Some(flag_id), Some(flag_val)) = (flag_id, flag_val) { + let game_state = &mut (*(*self.ptr).state_ptr); + + game_state.set_skip_flag(flag_id.max(0) as usize, flag_val); + } + + 0 + } + + unsafe fn lua_set_engine_constant(&self, state: &mut State) -> c_int { + if let Some(constant_id) = state.to_int(2) { + let game_state = &mut (*(*self.ptr).state_ptr); + + match constant_id { + 0x1000 => { + // intro event + if let Some(intro_event) = state.to_int(3) { + game_state.constants.game.intro_event = intro_event as u16; + } + } + 0x1001 => { + // intro stage + if let Some(intro_stage) = state.to_int(3) { + game_state.constants.game.intro_stage = intro_stage as u16; + } + } + 0x1002 => { + // intro pos + if let (Some(intro_x), Some(intro_y)) = (state.to_int(3), state.to_int(4)) { + game_state.constants.game.intro_player_pos = (intro_x as i16, intro_y as i16); + } + } + 0x1003 => { + // ng event + if let Some(ng_event) = state.to_int(3) { + game_state.constants.game.new_game_event = ng_event as u16; + } + } + 0x1004 => { + // ng stage + if let Some(ng_stage) = state.to_int(3) { + game_state.constants.game.new_game_stage = ng_stage as u16; + } + } + 0x1005 => { + // ng pos + if let (Some(ng_x), Some(ng_y)) = (state.to_int(3), state.to_int(4)) { + game_state.constants.game.new_game_player_pos = (ng_x as i16, ng_y as i16); + } + } + _ => {} + } + } + + 0 + } + + unsafe fn lua_npc_command(&self, state: &mut State) -> c_int { + if (*self.ptr).game_scene.is_null() { + state.push_nil(); + return 1; + } + + if let (Some(npc_id), Some(param_type)) = (state.to_int(2), state.to_int(3)) { + let game_scene = &mut *(*self.ptr).game_scene; + + let npc = match game_scene.npc_list.get_npc(npc_id as usize) { + Some(npc) => npc, + None => { + state.push_nil(); + return 1; + } + }; + + match param_type { + 0x0e => state.push(npc.cond.0 as i32), + 0x0f => state.push(npc.flags.0), + 0x10 => state.push(npc.x as f32 / 512.0), + 0x11 => state.push(npc.y as f32 / 512.0), + 0x12 => state.push(npc.vel_x as f32 / 512.0), + 0x13 => state.push(npc.vel_y as f32 / 512.0), + 0x14 => state.push(npc.vel_x2 as f32 / 512.0), + 0x15 => state.push(npc.vel_y2 as f32 / 512.0), + 0x16 => state.push(npc.action_num as i32), + 0x17 => state.push(npc.anim_num as i32), + 0x18 => state.push(npc.action_counter as i32), + 0x19 => state.push(npc.action_counter2 as i32), + 0x1a => state.push(npc.action_counter3 as i32), + 0x1b => state.push(npc.anim_counter as i32), + 0x1c => state.push(npc.parent_id as i32), + 0x1d => state.push(npc.npc_type as i32), + 0x1e => state.push(npc.life as i32), + 0x1f => state.push(npc.flag_num as i32), + 0x20 => state.push(npc.event_num as i32), + 0x21 => state.push(npc.direction as i32), + 0x22 => state.push(npc.tsc_direction as i32), + 0x10e => { + if let Some(v) = state.to_uint(4) { + npc.cond.0 = v as u16; + } + } + 0x10f => { + if let Some(v) = state.to_uint(4) { + npc.flags.0 = v; + } + } + 0x110 => { + if let Some(v) = state.to_float(4) { + // set x + npc.x = (v * 512.0) as i32; + } + } + 0x111 => { + if let Some(v) = state.to_float(4) { + // set y + npc.y = (v * 512.0) as i32; + } + } + 0x112 => { + if let Some(v) = state.to_float(4) { + // set vel x + npc.vel_x = (v * 512.0) as i32; + } + } + 0x113 => { + if let Some(v) = state.to_float(4) { + // set vel y + npc.vel_y = (v * 512.0) as i32; + } + } + 0x114 => { + if let Some(v) = state.to_float(4) { + // set vel x 2 + npc.vel_x2 = (v * 512.0) as i32; + } + } + 0x115 => { + if let Some(v) = state.to_float(4) { + // set vel y 2 + npc.vel_y2 = (v * 512.0) as i32; + } + } + 0x116 => { + if let Some(v) = state.to_int(4) { + npc.action_num = v as u16; + } + } + 0x117 => { + if let Some(v) = state.to_int(4) { + npc.anim_num = v as u16; + } + } + 0x118 => { + if let Some(v) = state.to_int(4) { + npc.action_counter = v as u16; + } + } + 0x119 => { + if let Some(v) = state.to_int(4) { + npc.action_counter2 = v as u16; + } + } + 0x11a => { + if let Some(v) = state.to_int(4) { + npc.action_counter3 = v as u16; + } + } + 0x11b => { + if let Some(v) = state.to_int(4) { + npc.anim_counter = v as u16; + } + } + 0x11c => { + if let Some(v) = state.to_int(4) { + npc.parent_id = v as u16; + } + } + 0x11d => { + if let Some(v) = state.to_int(4) { + npc.npc_type = v as u16; + } + } + 0x11e => { + if let Some(v) = state.to_int(4) { + npc.life = v as u16; + } + } + 0x11f => { + if let Some(v) = state.to_int(4) { + npc.flag_num = v as u16; + } + } + 0x120 => { + if let Some(v) = state.to_int(4) { + npc.event_num = v as u16; + } + } + 0x121 | 0x122 => { + if let Some(v) = state.to_int(4) { + npc.direction = Direction::from_int_facing(v as _).unwrap_or(Direction::Left); + npc.tsc_direction = v as _; + } + } + 0x200 => { + // get player idx + let index = npc.get_closest_player_idx_mut(&[&mut game_scene.player1, &mut game_scene.player2]); + state.push(index as i32); + } + 0x201 => { + // random + if let (Some(min), Some(max)) = (state.to_int(4), state.to_int(5)) { + if max < min { + state.error("max < min"); + } else { + state.push(npc.rng.range(min..max)); + } + } else { + state.error("Invalid parameters supplied."); + } + } + 0x202 => { + // get anim rect + state.push(npc.anim_rect.left as i32); + state.push(npc.anim_rect.top as i32); + state.push(npc.anim_rect.right as i32); + state.push(npc.anim_rect.bottom as i32); + } + 0x203 => { + // set anim rect + if let (Some(l), Some(t), Some(r), Some(b)) = + (state.to_int(4), state.to_int(5), state.to_int(6), state.to_int(7)) + { + npc.anim_rect = Rect { left: l as u16, top: t as u16, right: r as u16, bottom: b as u16 }; + } else { + state.error("Invalid parameters supplied."); + } + } + _ => state.push_nil(), + } + } else { + state.push_nil() + } + + 1 + } + + unsafe fn lua_player_command(&self, state: &mut State) -> c_int { + if (*self.ptr).game_scene.is_null() { + state.push_nil(); + return 1; + } + + if let (Some(player_id), Some(param_type)) = (state.to_int(2), state.to_int(3)) { + let game_scene = &mut *(*self.ptr).game_scene; + + let player = match player_id { + 0 => &mut game_scene.player1, + 1 => &mut game_scene.player2, + _ => { + state.push_nil(); + return 1; + } + }; + + match param_type { + 0x0e => state.push(player.cond.0 as u32), + 0x0f => state.push(player.flags.0), + 0x10 => state.push(player.x as f32 / 512.0), + 0x11 => state.push(player.y as f32 / 512.0), + 0x12 => state.push(player.vel_x as f32 / 512.0), + 0x13 => state.push(player.vel_y as f32 / 512.0), + 0x110 => { + if let Some(v) = state.to_float(4) { + // set x + player.x = (v * 512.0) as i32; + } + } + 0x111 => { + if let Some(v) = state.to_float(4) { + // set y + player.y = (v * 512.0) as i32; + } + } + 0x112 => { + if let Some(v) = state.to_float(4) { + // set vel x + player.vel_x = (v * 512.0) as i32; + } + } + 0x113 => { + if let Some(v) = state.to_float(4) { + // set vel y + player.vel_y = (v * 512.0) as i32; + } + } + 0x200 => { + if let Some(points) = state.to_int(4) { + player.damage(points, &mut (*(*self.ptr).state_ptr), &game_scene.npc_list); + } + + state.push_nil(); + } + _ => state.push_nil(), + } + } else { + state.push_nil() + } + + 1 + } + + unsafe fn lua_load_script(&mut self, state: &mut State) -> c_int { + let lua_state = &mut (*self.ptr); + + if let Some(name) = state.to_str(2) { + let name = name.to_string(); + + let ctx = &mut (*(*self.ptr).ctx_ptr); + + let path = format!("/Scripts/{}.lua", name); + let lua_vfs_path = format!("@/Scripts/{}.lua", name); + + fn raise_error(name: &str, state: &mut State, err: &str) { + let error_msg = format!("module '{}' not found: {}", name, err.to_string()); + state.error(&error_msg); + } + + match filesystem::open(ctx, &path) { + Ok(mut file) => { + let mut buf = Vec::new(); + if let Err(res) = file.read_to_end(&mut buf) { + raise_error(&name, state, &res.to_string()); + return 0; + } + + let res = state.load_buffer(&buf, &lua_vfs_path); + if let Err(err) = check_status(res, state) { + raise_error(&name, state, &err.to_string()); + return 0; + } + + return match state.pcall(0, 1, 0) { + Ok(_) => 1, + Err((_, err)) => { + raise_error(&name, state, &err); + 0 + } + }; + } + Err(err) => { + raise_error(&name, state, &err.to_string()); + } + } + } + + 0 + } } impl LuaObject for Doukutsu { fn name() -> *const i8 { - c_str!("Doukutsu") + c_str!("doukutsu-rs-internal") } fn lua_fns() -> Vec { @@ -70,6 +475,39 @@ impl LuaObject for Doukutsu { lua_method!("playSfx", Doukutsu, Doukutsu::lua_play_sfx), lua_method!("playSong", Doukutsu, Doukutsu::lua_play_song), lua_method!("getFlag", Doukutsu, Doukutsu::lua_get_flag), + lua_method!("setFlag", Doukutsu, Doukutsu::lua_set_flag), + lua_method!("getSkipFlag", Doukutsu, Doukutsu::lua_get_skip_flag), + lua_method!("setSkipFlag", Doukutsu, Doukutsu::lua_set_skip_flag), + lua_method!("setEngineConstant", Doukutsu, Doukutsu::lua_set_engine_constant), + lua_method!("playerCommand", Doukutsu, Doukutsu::lua_player_command), + lua_method!("npcCommand", Doukutsu, Doukutsu::lua_npc_command), + lua_method!("loadScript", Doukutsu, Doukutsu::lua_load_script), ] } } + +impl LuaScriptingState { + pub fn try_run_npc_hook(&mut self, npc_id: u16, npc_type: u16) -> bool { + let mut result = false; + + if let Some(state) = self.state.as_mut() { + state.get_global(DRS_RUNTIME_GLOBAL); + state.get_field(-1, "_tryNPCHook"); + + state.push(npc_id as i32); + state.push(npc_type as i32); + + if let Err((_, err)) = state.pcall(2, 1, 0) { + log::error!("npc_hook error: {}", err); + } + + if let Some(val) = state.to_bool(-1) { + result = val; + } + + state.pop(2); + } + + result + } +} diff --git a/src/scripting/mod.rs b/src/scripting/mod.rs index 277ae88..afd9c4a 100644 --- a/src/scripting/mod.rs +++ b/src/scripting/mod.rs @@ -13,9 +13,11 @@ use crate::scripting::doukutsu::Doukutsu; use crate::shared_game_state::SharedGameState; use crate::framework::filesystem::File; use crate::framework::filesystem; +use lua_ffi::types::LuaValue; +use crate::common::Rect; +use lua_ffi::ffi::lua_State; mod doukutsu; -mod player; mod scene; pub struct LuaScriptingState { @@ -25,11 +27,13 @@ pub struct LuaScriptingState { game_scene: *mut GameScene, } -pub static REF_ERROR: &str = "Reference went out of scope. DO NOT store/use references to game objects outside the event."; +pub(in crate::scripting) static REF_ERROR: &str = "Reference went out of scope. DO NOT store/use references to game objects outside the event."; +pub(in crate::scripting) static DRS_API_GLOBAL: &str = "__doukutsu_rs"; +pub(in crate::scripting) static DRS_RUNTIME_GLOBAL: &str = "__doukutsu_rs_runtime_dont_touch"; static BOOT_SCRIPT: &str = include_str!("boot.lua"); -fn check_status(status: ThreadStatus, state: &mut State) -> GameResult { +pub(in crate::scripting) fn check_status(status: ThreadStatus, state: &mut State) -> GameResult { match status { ThreadStatus::Ok | ThreadStatus::Yield => { return Ok(()); } _ => {} @@ -42,6 +46,7 @@ fn check_status(status: ThreadStatus, state: &mut State) -> GameResult { ThreadStatus::MemoryError => Err(GameError::EventLoopError(format!("Lua Memory Error: {}", error))), ThreadStatus::MsgHandlerError => Err(GameError::EventLoopError(format!("Lua Message Handler Error: {}", error))), ThreadStatus::FileError => Err(GameError::EventLoopError(format!("Lua File Error: {}", error))), + ThreadStatus::Unknown => Err(GameError::EventLoopError(format!("Unknown Error: {}", error))), _ => Ok(()) } } @@ -69,8 +74,12 @@ impl LuaScriptingState { self.ctx_ptr = ctx; } + pub fn set_game_scene(&mut self, game_scene: *mut GameScene) { + self.game_scene = game_scene; + } + fn load_script(mut state: &mut State, path: &str, mut script: File) -> bool { - let mut buf = Vec::with_capacity(1024); + let mut buf = Vec::new(); let res = script.read_to_end(&mut buf); if let Err(err) = res { @@ -86,7 +95,7 @@ impl LuaScriptingState { return false; } - state.get_global("doukutsu"); + state.get_global(DRS_RUNTIME_GLOBAL); state.get_field(-1, "_initializeScript"); state.push_value(-3); @@ -109,14 +118,15 @@ impl LuaScriptingState { state.set_global("print"); state.push(Doukutsu { ptr: self as *mut LuaScriptingState }); - state.set_global("__doukutsu"); + state.set_global(DRS_API_GLOBAL); + log::info!("Initializing Lua scripting engine..."); let res = state.do_string(BOOT_SCRIPT); check_status(res, &mut state)?; - if filesystem::exists(ctx, "/scripts/") { + if filesystem::exists(ctx, "/drs-scripts/") { let mut script_count = 0; - let files = filesystem::read_dir(ctx, "/scripts/")? + let files = filesystem::read_dir(ctx, "/drs-scripts/")? .filter(|f| f.to_string_lossy().to_lowercase().ends_with(".lua")); for file in files { @@ -139,8 +149,88 @@ impl LuaScriptingState { } } + let modcs_path = "/Scripts/main.lua"; + if filesystem::exists(ctx, modcs_path) { + log::info!("Loading ModCS main script..."); + + match filesystem::open(ctx, modcs_path) { + Ok(script) => { + if !LuaScriptingState::load_script(&mut state, modcs_path, script) { + log::warn!("Error loading ModCS main script."); + } + } + Err(err) => { + log::warn!("Error opening script {:?}: {}", modcs_path, err); + } + } + } + self.state = Some(state); Ok(()) } } + +impl LuaValue for Rect { + fn push_val(self, l: *mut lua_State) { + unsafe { + lua_ffi::ffi::lua_newtable(l); + lua_ffi::ffi::lua_pushinteger(l, self.left as isize); + lua_ffi::ffi::lua_rawseti(l, -2, 1); + lua_ffi::ffi::lua_pushinteger(l, self.top as isize); + lua_ffi::ffi::lua_rawseti(l, -2, 2); + lua_ffi::ffi::lua_pushinteger(l, self.right as isize); + lua_ffi::ffi::lua_rawseti(l, -2, 3); + lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize); + lua_ffi::ffi::lua_rawseti(l, -2, 4); + } + } +} + +impl LuaValue for Rect { + fn push_val(self, l: *mut lua_State) { + unsafe { + lua_ffi::ffi::lua_newtable(l); + lua_ffi::ffi::lua_pushinteger(l, self.left as isize); + lua_ffi::ffi::lua_rawseti(l, -2, 1); + lua_ffi::ffi::lua_pushinteger(l, self.top as isize); + lua_ffi::ffi::lua_rawseti(l, -2, 2); + lua_ffi::ffi::lua_pushinteger(l, self.right as isize); + lua_ffi::ffi::lua_rawseti(l, -2, 3); + lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize); + lua_ffi::ffi::lua_rawseti(l, -2, 4); + } + } +} + +impl LuaValue for Rect { + fn push_val(self, l: *mut lua_State) { + unsafe { + lua_ffi::ffi::lua_newtable(l); + lua_ffi::ffi::lua_pushinteger(l, self.left as isize); + lua_ffi::ffi::lua_rawseti(l, -2, 1); + lua_ffi::ffi::lua_pushinteger(l, self.top as isize); + lua_ffi::ffi::lua_rawseti(l, -2, 2); + lua_ffi::ffi::lua_pushinteger(l, self.right as isize); + lua_ffi::ffi::lua_rawseti(l, -2, 3); + lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize); + lua_ffi::ffi::lua_rawseti(l, -2, 4); + } + } +} + +impl LuaValue for Rect { + fn push_val(self, l: *mut lua_State) { + unsafe { + lua_ffi::ffi::lua_newtable(l); + lua_ffi::ffi::lua_pushnumber(l, self.left as f64); + lua_ffi::ffi::lua_rawseti(l, -2, 1); + lua_ffi::ffi::lua_pushnumber(l, self.top as f64); + lua_ffi::ffi::lua_rawseti(l, -2, 2); + lua_ffi::ffi::lua_pushnumber(l, self.right as f64); + lua_ffi::ffi::lua_rawseti(l, -2, 3); + lua_ffi::ffi::lua_pushnumber(l, self.bottom as f64); + lua_ffi::ffi::lua_rawseti(l, -2, 4); + } + } +} diff --git a/src/scripting/player.rs b/src/scripting/player.rs deleted file mode 100644 index 7aab899..0000000 --- a/src/scripting/player.rs +++ /dev/null @@ -1,135 +0,0 @@ -use lua_ffi::{c_int, LuaObject, State}; -use lua_ffi::ffi::luaL_Reg; - -use crate::inventory::Inventory; -use crate::player::Player; -use crate::scripting::REF_ERROR; -use crate::weapon::WeaponType; - -pub struct LuaPlayer { - valid_reference: bool, - plr_ptr: *mut Player, - inv_ptr: *mut Inventory, -} - -#[allow(unused)] -impl LuaPlayer { - fn check_ref(&self, state: &mut State) -> bool { - if !self.valid_reference { - state.error(REF_ERROR); - return true; - } - - false - } - - fn lua_get_x(&self, state: &mut State) -> c_int { - if self.check_ref(state) { return 0; } - - unsafe { - state.push((*self.plr_ptr).x); - } - - 1 - } - - fn lua_get_y(&self, state: &mut State) -> c_int { - if self.check_ref(state) { return 0; } - - unsafe { - state.push((*self.plr_ptr).y); - } - - 1 - } - - fn lua_get_vel_x(&self, state: &mut State) -> c_int { - if self.check_ref(state) { return 0; } - - unsafe { - state.push((*self.plr_ptr).vel_x); - } - - 1 - } - - fn lua_get_vel_y(&self, state: &mut State) -> c_int { - if self.check_ref(state) { return 0; } - - unsafe { - state.push((*self.plr_ptr).vel_y); - } - - 1 - } - - fn lua_set_vel_x(&self, state: &mut State) -> c_int { - if self.check_ref(state) { return 0; } - - unsafe { - if let Some(vel_x) = state.to_int(2) { - (*self.plr_ptr).vel_x = vel_x; - } - } - - 0 - } - - fn lua_set_vel_y(&self, state: &mut State) -> c_int { - if self.check_ref(state) { return 0; } - - unsafe { - if let Some(vel_y) = state.to_int(2) { - (*self.plr_ptr).vel_y = vel_y; - } - } - - 0 - } - - fn lua_get_weapon_ammo(&self, state: &mut State) -> c_int { - if self.check_ref(state) { return 0; } - - if let Some(index) = state.to_int(2) {} else { - state.error("Weapon type must be a number"); - return 0; - } - - unsafe { - if let Some(weap) = (*self.inv_ptr).get_weapon_by_type_mut(WeaponType::PolarStar) {} - } - - 1 - } - - pub(crate) fn new(plr_ptr: *mut Player, inv_ptr: *mut Inventory) -> LuaPlayer { - LuaPlayer { - valid_reference: true, - plr_ptr, - inv_ptr, - } - } -} - -impl Drop for LuaPlayer { - fn drop(&mut self) { - self.valid_reference = false; - } -} - -impl LuaObject for LuaPlayer { - fn name() -> *const i8 { - c_str!("Player") - } - - fn lua_fns() -> Vec { - vec![ - lua_method!("x", LuaPlayer, LuaPlayer::lua_get_x), - lua_method!("y", LuaPlayer, LuaPlayer::lua_get_y), - lua_method!("velX", LuaPlayer, LuaPlayer::lua_get_vel_x), - lua_method!("velX", LuaPlayer, LuaPlayer::lua_get_vel_y), - lua_method!("setVelX", LuaPlayer, LuaPlayer::lua_set_vel_x), - lua_method!("setVelY", LuaPlayer, LuaPlayer::lua_set_vel_y), - ] - } -} diff --git a/src/scripting/scene.rs b/src/scripting/scene.rs index 703d42c..ccf79a0 100644 --- a/src/scripting/scene.rs +++ b/src/scripting/scene.rs @@ -1,11 +1,8 @@ -use lua_ffi::{c_int, LuaObject, State}; use lua_ffi::ffi::luaL_Reg; +use lua_ffi::{c_int, LuaObject, State}; -use crate::inventory::Inventory; -use crate::player::Player; use crate::scene::game_scene::GameScene; -use crate::scripting::LuaScriptingState; -use crate::scripting::player::LuaPlayer; +use crate::scripting::{LuaScriptingState, DRS_RUNTIME_GLOBAL}; pub struct LuaGameScene { valid_reference: bool, @@ -19,30 +16,8 @@ impl LuaGameScene { 1 } - unsafe fn lua_get_player(&self, state: &mut State) -> c_int { - if let Some(index) = state.to_int(2) { - let (player_ref, inv_ref) = match index { - 0 => (&mut (*self.ptr).player1, &mut (*self.ptr).inventory_player1), - 1 => (&mut (*self.ptr).player2, &mut (*self.ptr).inventory_player2), - _ => { - state.error("Player index out of range!"); - return 0; - } - }; - - state.push(LuaPlayer::new(player_ref as *mut Player, inv_ref as *mut Inventory)); - 1 - } else { - state.error("Player index must be a number."); - 0 - } - } - pub(crate) fn new(ptr: *mut GameScene) -> LuaGameScene { - LuaGameScene { - valid_reference: true, - ptr, - } + LuaGameScene { valid_reference: true, ptr } } } @@ -58,21 +33,16 @@ impl LuaObject for LuaGameScene { } fn lua_fns() -> Vec { - vec![ - lua_method!("tick", LuaGameScene, LuaGameScene::lua_get_tick), - lua_method!("player", LuaGameScene, LuaGameScene::lua_get_player), - ] + vec![lua_method!("tick", LuaGameScene, LuaGameScene::lua_get_tick)] } } impl LuaScriptingState { - pub fn scene_tick(&mut self, game_scene: &mut GameScene) { - self.game_scene = game_scene as *mut GameScene; - + pub fn scene_tick(&mut self) { if let Some(state) = self.state.as_mut() { let val = LuaGameScene::new(self.game_scene); - state.get_global("doukutsu"); + state.get_global(DRS_RUNTIME_GLOBAL); state.get_field(-1, "_handlers"); state.get_field(-1, "tick"); diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index 23d299e..9129f05 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -291,17 +291,18 @@ impl SharedGameState { } pub fn start_new_game(&mut self, ctx: &mut Context) -> GameResult { - let mut next_scene = GameScene::new(self, ctx, 13)?; + #[cfg(feature = "scripting")] + self.lua.reload_scripts(ctx)?; + + let mut next_scene = GameScene::new(self, ctx, self.constants.game.new_game_stage as usize)?; next_scene.player1.cond.set_alive(true); - next_scene.player1.x = 10 * 0x2000; - next_scene.player1.y = 8 * 0x2000; + let (pos_x, pos_y)= self.constants.game.new_game_player_pos; + next_scene.player1.x = pos_x as i32 * next_scene.stage.map.tile_size.as_int() * 0x200; + next_scene.player1.y = pos_y as i32 * next_scene.stage.map.tile_size.as_int() * 0x200; self.reset_map_flags(); self.fade_state = FadeState::Hidden; - self.textscript_vm.state = TextScriptExecutionState::Running(200, 0); - - #[cfg(feature = "scripting")] - self.lua.reload_scripts(ctx)?; + self.textscript_vm.state = TextScriptExecutionState::Running(self.constants.game.new_game_event, 0); self.next_scene = Some(Box::new(next_scene)); @@ -309,7 +310,10 @@ impl SharedGameState { } pub fn start_intro(&mut self, ctx: &mut Context) -> GameResult { - let start_stage_id = 72; + #[cfg(feature = "scripting")] + self.lua.reload_scripts(ctx)?; + + let start_stage_id = self.constants.game.intro_stage as usize; if self.stages.len() < start_stage_id { log::warn!("Intro scene out of bounds in stage table, skipping to title..."); @@ -317,18 +321,16 @@ impl SharedGameState { return Ok(()); } - let mut next_scene = GameScene::new(self, ctx, 72)?; + let mut next_scene = GameScene::new(self, ctx, start_stage_id)?; next_scene.player1.cond.set_hidden(true); - next_scene.player1.x = 3 * 0x2000; - next_scene.player1.y = 3 * 0x2000; + let (pos_x, pos_y)= self.constants.game.intro_player_pos; + next_scene.player1.x = pos_x as i32 * next_scene.stage.map.tile_size.as_int() * 0x200; + next_scene.player1.y = pos_y as i32 * next_scene.stage.map.tile_size.as_int() * 0x200; next_scene.intro_mode = true; self.reset_map_flags(); self.fade_state = FadeState::Hidden; - self.textscript_vm.state = TextScriptExecutionState::Running(100, 0); - - #[cfg(feature = "scripting")] - self.lua.reload_scripts(ctx)?; + self.textscript_vm.state = TextScriptExecutionState::Running(self.constants.game.intro_event, 0); self.next_scene = Some(Box::new(next_scene)); diff --git a/src/sound/pixtone.rs b/src/sound/pixtone.rs index 0bee3e8..d70f988 100644 --- a/src/sound/pixtone.rs +++ b/src/sound/pixtone.rs @@ -178,7 +178,7 @@ impl PixToneParameters { #[derive(Copy, Clone, PartialEq)] pub struct PlaybackState { - id: u8 + id: u8, pos: f32, tag: u32, looping: bool, @@ -225,12 +225,7 @@ impl PixTonePlayback { } } - self.playback_state.push(PlaybackState { - id, - pos: 0.0, - tag: 0, - looping: false - }); + self.playback_state.push(PlaybackState { id, pos: 0.0, tag: 0, looping: false }); } pub fn loop_sfx(&mut self, id: u8) { @@ -241,12 +236,7 @@ impl PixTonePlayback { } } - self.playback_state.push(PlaybackState { - id, - pos: 0.0, - tag: 0, - looping: true - }); + self.playback_state.push(PlaybackState { id, pos: 0.0, tag: 0, looping: true }); } pub fn stop_sfx(&mut self, id: u8) { @@ -256,12 +246,7 @@ impl PixTonePlayback { } pub fn play_concurrent(&mut self, id: u8, tag: u32) { - self.playback_state.push(PlaybackState { - id, - pos: 0.0, - tag, - looping: false - }); + self.playback_state.push(PlaybackState { id, pos: 0.0, tag, looping: false }); } pub fn mix(&mut self, dst: &mut [u16], sample_rate: f32) { @@ -272,30 +257,34 @@ impl PixTonePlayback { let mut state = *item; let mut remove = false; - if let Some(sample) = self.samples.get(&state.0) { + if let Some(sample) = self.samples.get(&state.id) { if sample.is_empty() { item.remove(); continue; }; for result in dst.iter_mut() { - if state.1 >= sample.len() as f32 { - remove = true; - break; - } else { - let pos = state.1 as usize; - let s1 = (sample[pos] as f32) / 32768.0; - let s2 = (sample[(pos + 1).clamp(0, sample.len() - 1)] as f32) / 32768.0; - let s3 = (sample[(pos + 2).clamp(0, sample.len() - 1)] as f32) / 32768.0; - let s4 = (sample[pos.saturating_sub(1)] as f32) / 32768.0; - - let s = cubic_interp(s1, s2, s4, s3, state.1.fract()) * 32768.0; - // let s = sample[pos] as f32; - let sam = (*result ^ 0x8000) as i16; - *result = sam.saturating_add(s as i16) as u16 ^ 0x8000; - - state.1 += delta; + if state.pos >= sample.len() as f32 { + if state.looping { + state.pos = 0.0; + } else { + remove = true; + break; + } } + + let pos = state.pos as usize; + let s1 = (sample[pos] as f32) / 32768.0; + let s2 = (sample[(pos + 1).clamp(0, sample.len() - 1)] as f32) / 32768.0; + let s3 = (sample[(pos + 2).clamp(0, sample.len() - 1)] as f32) / 32768.0; + let s4 = (sample[pos.saturating_sub(1)] as f32) / 32768.0; + + let s = cubic_interp(s1, s2, s4, s3, state.pos.fract()) * 32768.0; + // let s = sample[pos] as f32; + let sam = (*result ^ 0x8000) as i16; + *result = sam.saturating_add(s as i16) as u16 ^ 0x8000; + + state.pos += delta; } if remove {