lua 5.3, a bunch of new functionality for scripting api
This commit is contained in:
parent
fcf7b292b1
commit
eeb290a6f3
|
@ -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"
|
||||
|
|
|
@ -48,6 +48,16 @@ pub struct PlayerConsts {
|
|||
pub frames_bubble: [Rect<u16>; 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,
|
||||
|
|
106
src/npc/mod.rs
106
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<u32> {
|
||||
|
@ -681,7 +725,7 @@ impl NPCTable {
|
|||
23 => "Npc/NpcRegu",
|
||||
26 => "TextBox",
|
||||
27 => "Face",
|
||||
_ => "Npc/Npc0"
|
||||
_ => "Npc/Npc0",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,150 @@
|
|||
declare type EventHandler<T> = (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 <ANP) of NPC (16-bit short integer).
|
||||
*/
|
||||
actionNum: number;
|
||||
|
||||
/**
|
||||
* First internal counter of the NPC (unsigned 16-bit short integer).
|
||||
*/
|
||||
actionCounter: number;
|
||||
|
||||
/**
|
||||
* Second internal counter of the NPC (unsigned 16-bit short integer).
|
||||
*/
|
||||
actionCounter2: number;
|
||||
|
||||
/**
|
||||
* Third internal counter of the NPC (unsigned 16-bit short integer).
|
||||
*/
|
||||
actionCounter3: number;
|
||||
|
||||
/**
|
||||
* Health of the NPC.
|
||||
*/
|
||||
life: number;
|
||||
|
||||
/**
|
||||
* Flag attached to this NPC.
|
||||
*/
|
||||
flagNum: number;
|
||||
|
||||
/**
|
||||
* Event attached to this NPC.
|
||||
*/
|
||||
eventNum: number;
|
||||
|
||||
/**
|
||||
* ID of the parent NPC.
|
||||
*/
|
||||
parentId: number;
|
||||
|
||||
/**
|
||||
* Direction of the NPC. Constrained to 0-4, use rawDirection to store values outside that range there.
|
||||
*/
|
||||
direction: number;
|
||||
|
||||
/**
|
||||
* Raw direction of the NPC, non-constrained and may have different value than direction field.
|
||||
* Used by certain vanilla NPCs that use the direction field for different purposes.
|
||||
*/
|
||||
rawDirection: number;
|
||||
|
||||
hitCeiling(): boolean;
|
||||
hitFloor(): boolean;
|
||||
hitLeftWall(): boolean;
|
||||
hitRightWall(): boolean;
|
||||
|
||||
/**
|
||||
* Pick a random integer from given range using RNG bound to this NPC.
|
||||
* @param min minimum value.
|
||||
* @param max maximum value.
|
||||
*/
|
||||
random(min: number, max: number): number;
|
||||
|
||||
/**
|
||||
* Returns a reference to parent NPC, if present.
|
||||
*/
|
||||
parentNPC(): NPC | null;
|
||||
|
||||
/**
|
||||
* Returns a reference to closest player, non-nullable.
|
||||
*/
|
||||
closestPlayer(): DoukutsuPlayer;
|
||||
|
||||
/**
|
||||
* Internal counter used as index of animation frame. (unsigned 16-bit short integer)
|
||||
*/
|
||||
animNum: number;
|
||||
|
||||
/**
|
||||
* Internal counter used by animation functions to count ticks between frames (unsigned 16-bit short integer).
|
||||
*/
|
||||
animCounter: number;
|
||||
|
||||
/**
|
||||
* Returns sprite bounds of the NPC.
|
||||
*/
|
||||
getAnimRect(): [number, number, number, number];
|
||||
|
||||
/**
|
||||
* Sets the sprite bounds to specified rectangle.
|
||||
* @param rect [left, top, right, bottom] rectangle.
|
||||
*/
|
||||
setAnimRect(rect: [number, number, number, number]): void;
|
||||
|
||||
/**
|
||||
* Sets the sprite bounds to specified rectangle.
|
||||
* @param left left bound
|
||||
* @param top top bound
|
||||
* @param right right bound
|
||||
* @param bottom bottom bound
|
||||
*/
|
||||
setAnimRect(left: number, top: number, right: number, bottom: number): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an in-game player.
|
||||
*/
|
||||
|
@ -7,74 +152,52 @@ declare interface DoukutsuPlayer {
|
|||
/**
|
||||
* The ID of player.
|
||||
*/
|
||||
id(): number;
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* Current position of player in X axis (as floating point, not internal fixed point representation).
|
||||
*/
|
||||
x(): number;
|
||||
x: number;
|
||||
|
||||
/**
|
||||
* Current position of player in Y axis (as floating point, not internal fixed point representation).
|
||||
*/
|
||||
y(): number;
|
||||
y: number;
|
||||
|
||||
/**
|
||||
* Current velocity of player in X axis (as floating point, not internal fixed point representation).
|
||||
*/
|
||||
velX(): number;
|
||||
velX: number;
|
||||
|
||||
/**
|
||||
* Current velocity of player in Y axis (as floating point, not internal fixed point representation).
|
||||
*/
|
||||
velY(): number;
|
||||
velY: number;
|
||||
|
||||
/**
|
||||
* Sets the position of player in X axis (as floating point, not internal fixed point representation).
|
||||
* Damages the player. Has no effect when invincibility is enabled.
|
||||
* @param value number of health points to subtract.
|
||||
*/
|
||||
setX(value: number): void;
|
||||
|
||||
/**
|
||||
* Sets the position of player in Y axis (as floating point, not internal fixed point representation).
|
||||
*/
|
||||
setY(value: number): void;
|
||||
|
||||
/**
|
||||
* Sets the velocity of player in X axis (as floating point, not internal fixed point representation).
|
||||
*/
|
||||
setVelX(value: number): void;
|
||||
|
||||
/**
|
||||
* Sets the velocity of player in Y axis (as floating point, not internal fixed point representation).
|
||||
*/
|
||||
setVelY(value: number): void;
|
||||
|
||||
|
||||
}
|
||||
|
||||
declare interface DoukutsuStage {
|
||||
/**
|
||||
* Returns the tick of current stage.
|
||||
*/
|
||||
tick(): number;
|
||||
|
||||
/**
|
||||
* Returns a list of players on current map.
|
||||
*/
|
||||
players(): DoukutsuPlayer[];
|
||||
|
||||
/**
|
||||
* Returns player with specified id.
|
||||
*/
|
||||
player(id: number): DoukutsuPlayer | null;
|
||||
damage(value: number): void;
|
||||
}
|
||||
|
||||
declare namespace doukutsu {
|
||||
/**
|
||||
* Plays a PixTone sound effect with specified ID.
|
||||
* A reference to main locally controlled player.
|
||||
* In multiplayer context it refers to player who hosts the game.
|
||||
*/
|
||||
const player: DoukutsuPlayer;
|
||||
|
||||
/**
|
||||
* Plays a sound effect with specified ID.
|
||||
*/
|
||||
function playSfx(id: number): void;
|
||||
|
||||
/**
|
||||
* Plays a looping sound effect with specified ID.
|
||||
*/
|
||||
function playSfxLoop(id: number): void;
|
||||
|
||||
/**
|
||||
* Changes current music to one with specified ID.
|
||||
* If ID equals 0, the music is stopped.
|
||||
|
@ -88,14 +211,35 @@ declare namespace doukutsu {
|
|||
function getFlag(id: number): boolean;
|
||||
|
||||
/**
|
||||
* Returns a list of players connected to current game.
|
||||
* Sets the value of a certain TSC flag.
|
||||
* @param id the flag number
|
||||
* * @param value the flag value
|
||||
*/
|
||||
function onlinePlayers(): DoukutsuPlayer[];
|
||||
function setFlag(id: number, value: boolean): void;
|
||||
|
||||
/**
|
||||
* Returns the id of local player.
|
||||
* Returns the value of a certain skip flag.
|
||||
* @param id the flag number
|
||||
*/
|
||||
function localPlayerId(): number;
|
||||
function getSkipFlag(id: number): boolean;
|
||||
|
||||
/**
|
||||
* Sets the value of a certain skip flag.
|
||||
* @param id the flag number
|
||||
* @param value the flag value
|
||||
*/
|
||||
function setSkipFlag(id: number, value: boolean): void;
|
||||
|
||||
/**
|
||||
* Returns a list of players currently in game.
|
||||
*/
|
||||
function players(): DoukutsuPlayer[];
|
||||
|
||||
/**
|
||||
* Returns a reference to NPC by it's ID (index in table).s
|
||||
* @param id
|
||||
*/
|
||||
function getNPC(id: number): NPC;
|
||||
|
||||
/**
|
||||
* Sets an implementation-defined game setting.
|
||||
|
@ -104,6 +248,13 @@ declare namespace doukutsu {
|
|||
*/
|
||||
function setSetting(name: string, value: any): void;
|
||||
|
||||
/**
|
||||
* Sets the handler override for specified NPC type. Passing a null removes the handler.
|
||||
* @param npcType
|
||||
* @param handler
|
||||
*/
|
||||
function setNPCHandler(npcType: number, handler: (this: void, npc: NPC) => void | null): void;
|
||||
|
||||
/**
|
||||
* Registers an event handler called after all scripts are loaded.
|
||||
* @param event event name
|
||||
|
|
|
@ -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<luaL_Reg> {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<u16> {
|
||||
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<i16> {
|
||||
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<i32> {
|
||||
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<f32> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<luaL_Reg> {
|
||||
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),
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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<luaL_Reg> {
|
||||
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");
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue