mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2024-09-27 20:48:54 +00:00
535 lines
17 KiB
Lua
535 lines
17 KiB
Lua
-- 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 = {}
|
|
|
|
ModCS = {
|
|
Flag = {},
|
|
Game = {
|
|
Act = nil,
|
|
},
|
|
Mod = {},
|
|
NPC = {},
|
|
Organya = {},
|
|
Player = {},
|
|
Rect = {},
|
|
SkipFlag = {},
|
|
Sound = {},
|
|
}
|
|
|
|
__doukutsu_rs_runtime_dont_touch._known_settings = {
|
|
["doukutsu-rs.intro.event_id"] = 0x1000,
|
|
["doukutsu-rs.intro.stage_id"] = 0x1001,
|
|
["doukutsu-rs.intro.pos"] = 0x1002,
|
|
["doukutsu-rs.new_game.event_id"] = 0x1003,
|
|
["doukutsu-rs.new_game.stage_id"] = 0x1004,
|
|
["doukutsu-rs.new_game.pos"] = 0x1005,
|
|
["doukutsu-rs.font_scale"] = 0x2000,
|
|
}
|
|
|
|
__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_rs_runtime_dont_touch._handlers = setmetatable({
|
|
tick = function(scene)
|
|
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,
|
|
}, {
|
|
__index = function(self, event)
|
|
error("Unknown event: " .. event)
|
|
end,
|
|
})
|
|
|
|
__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)
|
|
f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
|
|
local name
|
|
local up = 0
|
|
repeat
|
|
up = up + 1
|
|
name = debug.getupvalue(f, up)
|
|
until name == '_ENV' or name == nil
|
|
if name then
|
|
|
|
debug.upvaluejoin(f, up, function()
|
|
return name
|
|
end, 1)
|
|
debug.setupvalue(f, up, t)
|
|
end
|
|
end
|
|
|
|
global_copy = {}
|
|
for k, v in pairs(_G) do
|
|
global_copy[k] = v
|
|
end
|
|
|
|
_setfenv(script, global_copy)
|
|
script()
|
|
end
|
|
|
|
__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_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_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) -- <- not a typo
|
|
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.rs = {}
|
|
setmetatable(doukutsu.rs, {
|
|
__index = function(self, property)
|
|
if property == "lightingMode" then
|
|
return __doukutsu_rs:stageCommand(0x01)
|
|
elseif property == "lightingEnabled" then
|
|
return __doukutsu_rs:stageCommand(0x02)
|
|
else
|
|
return nil
|
|
end
|
|
end,
|
|
__newindex = function(self, property, val)
|
|
if property == "lightingMode" then
|
|
__doukutsu_rs:stageCommand(0x101, val)
|
|
end
|
|
|
|
return nil
|
|
end,
|
|
})
|
|
|
|
setmetatable(doukutsu, {
|
|
__index = function(self, property)
|
|
if property == "currentStage" then
|
|
local v = __doukutsu_rs:stageCommand(0x03)
|
|
if v == nil then
|
|
v = -1
|
|
end
|
|
|
|
return v
|
|
end
|
|
end,
|
|
})
|
|
|
|
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.setSetting(key, value)
|
|
assert(type(key) == "string", "key must be a string.")
|
|
|
|
local id = __doukutsu_rs_runtime_dont_touch._known_settings[key]
|
|
if id ~= nil then
|
|
__doukutsu_rs:setEngineConstant(id, value)
|
|
end
|
|
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_rs_runtime_dont_touch._registered[event] == nil then
|
|
error("Unknown event: " .. event)
|
|
end
|
|
|
|
table.insert(__doukutsu_rs_runtime_dont_touch._registered[event], handler)
|
|
|
|
return handler
|
|
end
|
|
|
|
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_rs_runtime_dont_touch._registered[event] == nil then
|
|
error("Unknown event: " .. event)
|
|
end
|
|
|
|
local index = -1
|
|
for i, h in pairs(__doukutsu_rs_runtime_dont_touch._registered[event]) do
|
|
if handler == h then
|
|
index = i
|
|
break
|
|
end
|
|
end
|
|
|
|
if index ~= -1 then
|
|
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
|