hush
This commit is contained in:
231
src/lua.cpp
231
src/lua.cpp
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <psyqo-lua/lua.hh>
|
||||
|
||||
#include <psyqo/soft-math.hh>
|
||||
#include <psyqo/trigonometry.hh>
|
||||
#include <psyqo/xprintf.h>
|
||||
|
||||
#include "gameobject.hh"
|
||||
@@ -10,15 +12,27 @@ constexpr const char GAMEOBJECT_SCRIPT[] = R"(
|
||||
return function(metatable)
|
||||
local get_position = metatable.get_position
|
||||
local set_position = metatable.set_position
|
||||
local get_active = metatable.get_active
|
||||
local set_active = metatable.set_active
|
||||
local get_rotationY = metatable.get_rotationY
|
||||
local set_rotationY = metatable.set_rotationY
|
||||
|
||||
metatable.get_position = nil
|
||||
metatable.set_position = nil
|
||||
metatable.get_active = nil
|
||||
metatable.set_active = nil
|
||||
metatable.get_rotationY = nil
|
||||
metatable.set_rotationY = nil
|
||||
|
||||
function metatable.__index(self, key)
|
||||
local raw = rawget(self, key)
|
||||
if raw ~= nil then return raw end
|
||||
if key == "position" then
|
||||
return get_position(self.__cpp_ptr)
|
||||
elseif key == "active" then
|
||||
return get_active(self.__cpp_ptr)
|
||||
elseif key == "rotationY" then
|
||||
return get_rotationY(self.__cpp_ptr)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
@@ -29,6 +43,10 @@ return function(metatable)
|
||||
return
|
||||
elseif key == "active" then
|
||||
set_active(self.__cpp_ptr, value)
|
||||
return
|
||||
elseif key == "rotationY" then
|
||||
set_rotationY(self.__cpp_ptr, value)
|
||||
return
|
||||
end
|
||||
rawset(self, key, value)
|
||||
end
|
||||
@@ -37,17 +55,18 @@ end
|
||||
|
||||
// Lua helpers
|
||||
|
||||
static constexpr lua_Number kFixedScale = 4096;
|
||||
|
||||
static int gameobjectGetPosition(psyqo::Lua L) {
|
||||
|
||||
auto go = L.toUserdata<psxsplash::GameObject>(1);
|
||||
|
||||
L.newTable();
|
||||
L.pushNumber(go->position.x.raw());
|
||||
L.pushNumber(static_cast<lua_Number>(go->position.x.raw()) / kFixedScale);
|
||||
L.setField(2, "x");
|
||||
L.pushNumber(go->position.y.raw());
|
||||
L.pushNumber(static_cast<lua_Number>(go->position.y.raw()) / kFixedScale);
|
||||
L.setField(2, "y");
|
||||
L.pushNumber(go->position.z.raw());
|
||||
L.pushNumber(static_cast<lua_Number>(go->position.z.raw()) / kFixedScale);
|
||||
L.setField(2, "z");
|
||||
|
||||
return 1;
|
||||
@@ -59,18 +78,15 @@ static int gameobjectSetPosition(psyqo::Lua L) {
|
||||
auto go = L.toUserdata<psxsplash::GameObject>(1);
|
||||
|
||||
L.getField(2, "x");
|
||||
psyqo::FixedPoint<> x(L.toNumber(3), psyqo::FixedPoint<>::RAW);
|
||||
go->position.x = x;
|
||||
go->position.x = psyqo::FixedPoint<>(static_cast<int32_t>(L.toNumber(3) * kFixedScale), psyqo::FixedPoint<>::RAW);
|
||||
L.pop();
|
||||
|
||||
L.getField(2, "y");
|
||||
psyqo::FixedPoint<> y(L.toNumber(3), psyqo::FixedPoint<>::RAW);
|
||||
go->position.y = y;
|
||||
go->position.y = psyqo::FixedPoint<>(static_cast<int32_t>(L.toNumber(3) * kFixedScale), psyqo::FixedPoint<>::RAW);
|
||||
L.pop();
|
||||
L.getField(2, "z");
|
||||
|
||||
psyqo::FixedPoint<> z(L.toNumber(3), psyqo::FixedPoint<>::RAW);
|
||||
go->position.z = z;
|
||||
go->position.z = psyqo::FixedPoint<>(static_cast<int32_t>(L.toNumber(3) * kFixedScale), psyqo::FixedPoint<>::RAW);
|
||||
L.pop();
|
||||
return 0;
|
||||
|
||||
@@ -89,6 +105,59 @@ static int gamobjectSetActive(psyqo::Lua L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Angle constants: psyqo::Angle is FixedPoint<10>, so 1.0_pi = raw 1024
|
||||
static constexpr lua_Number kAngleScale = 1024;
|
||||
static psyqo::Trig<> s_trig;
|
||||
|
||||
// Fast integer atan2 approximation → psyqo::Angle (pi-fraction units)
|
||||
// Uses linear approximation in first octant then folds to full circle.
|
||||
// Max error ~4° (acceptable for PS1 game objects).
|
||||
static psyqo::Angle fastAtan2(int32_t sinVal, int32_t cosVal) {
|
||||
psyqo::Angle result;
|
||||
if (cosVal == 0 && sinVal == 0) { result.value = 0; return result; }
|
||||
|
||||
int32_t abs_s = sinVal < 0 ? -sinVal : sinVal;
|
||||
int32_t abs_c = cosVal < 0 ? -cosVal : cosVal;
|
||||
|
||||
int32_t minV = abs_s < abs_c ? abs_s : abs_c;
|
||||
int32_t maxV = abs_s > abs_c ? abs_s : abs_c;
|
||||
|
||||
// Compute angle in first octant [0, π/4 = 256 Angle units]
|
||||
// angle = (minV/maxV) * 256, using only 32-bit math.
|
||||
// Max minV for normalized sin/cos ≈ 4096, so minV * 256 ≈ 1M — fits int32.
|
||||
int32_t angle = (minV * 256) / maxV;
|
||||
|
||||
// Past 45°: use complement
|
||||
if (abs_s > abs_c) angle = 512 - angle; // π/2 - angle
|
||||
// Quadrant 2/3: cos < 0
|
||||
if (cosVal < 0) angle = 1024 - angle; // π - angle
|
||||
// Quadrant 3/4: sin < 0
|
||||
if (sinVal < 0) angle = -angle;
|
||||
|
||||
result.value = angle;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int gameobjectGetRotationY(psyqo::Lua L) {
|
||||
auto go = L.toUserdata<psxsplash::GameObject>(1);
|
||||
// Y rotation matrix: vs[0].x = cos(θ), vs[0].z = sin(θ)
|
||||
int32_t sinRaw = go->rotation.vs[0].z.raw();
|
||||
int32_t cosRaw = go->rotation.vs[0].x.raw();
|
||||
psyqo::Angle angle = fastAtan2(sinRaw, cosRaw);
|
||||
// Return in pi-units: 0.5 = π/2 = 90°
|
||||
L.pushNumber(static_cast<lua_Number>(angle.value) / kAngleScale);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int gameobjectSetRotationY(psyqo::Lua L) {
|
||||
auto go = L.toUserdata<psxsplash::GameObject>(1);
|
||||
lua_Number piUnits = L.toNumber(2);
|
||||
psyqo::Angle angle;
|
||||
angle.value = static_cast<int32_t>(piUnits * kAngleScale);
|
||||
go->rotation = psyqo::SoftMath::generateRotationMatrix33(angle, psyqo::SoftMath::Axis::Y, s_trig);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void psxsplash::Lua::Init() {
|
||||
auto L = m_state;
|
||||
// Load and run the game objects script
|
||||
@@ -109,11 +178,17 @@ void psxsplash::Lua::Init() {
|
||||
L.push(gamobjectSetActive);
|
||||
L.setField(-2, "set_active");
|
||||
|
||||
L.push(gameobjectGetRotationY);
|
||||
L.setField(-2, "get_rotationY");
|
||||
|
||||
L.push(gameobjectSetRotationY);
|
||||
L.setField(-2, "set_rotationY");
|
||||
|
||||
L.copy(-1);
|
||||
m_metatableReference = L.ref();
|
||||
|
||||
if (L.pcall(1, 0) == 0) {
|
||||
printf("Lua script 'gameObjects' executed successfully");
|
||||
// success
|
||||
} else {
|
||||
printf("Error registering Lua script: %s\n", L.optString(-1, "Unknown error"));
|
||||
L.clearStack();
|
||||
@@ -136,6 +211,24 @@ void psxsplash::Lua::Init() {
|
||||
m_luascriptsReference = L.ref();
|
||||
}
|
||||
|
||||
void psxsplash::Lua::Shutdown() {
|
||||
// Close the Lua VM if it's still open.
|
||||
// Safe to call multiple times or on an already-closed VM.
|
||||
if (m_state.getState()) {
|
||||
m_state.close();
|
||||
}
|
||||
m_metatableReference = LUA_NOREF;
|
||||
m_luascriptsReference = LUA_NOREF;
|
||||
m_luaSceneScriptsReference = LUA_NOREF;
|
||||
}
|
||||
|
||||
void psxsplash::Lua::Reset() {
|
||||
// Nuclear reset: destroy the entire Lua VM and create a fresh one.
|
||||
Shutdown();
|
||||
m_state = psyqo::Lua(); // fresh state (luaL_newstate + openlibs)
|
||||
Init();
|
||||
}
|
||||
|
||||
void psxsplash::Lua::LoadLuaFile(const char* code, size_t len, int index) {
|
||||
auto L = m_state;
|
||||
char filename[32];
|
||||
@@ -143,18 +236,31 @@ void psxsplash::Lua::LoadLuaFile(const char* code, size_t len, int index) {
|
||||
if (L.loadBuffer(code, len, filename) != LUA_OK) {
|
||||
printf("Lua error: %s\n", L.toString(-1));
|
||||
L.pop();
|
||||
return;
|
||||
}
|
||||
// (1) script func
|
||||
L.rawGetI(LUA_REGISTRYINDEX, m_luascriptsReference);
|
||||
// (1) script func (2) scripts table
|
||||
L.newTable();
|
||||
// (1) script func (2) scripts table (3) {}
|
||||
// (1) script func (2) scripts table (3) env {}
|
||||
|
||||
// Give the environment a metatable that falls back to _G
|
||||
// so scripts can see Entity, Debug, Input, etc.
|
||||
L.newTable();
|
||||
// (1) script func (2) scripts table (3) env {} (4) mt {}
|
||||
L.pushGlobalTable();
|
||||
// (1) script func (2) scripts table (3) env {} (4) mt {} (5) _G
|
||||
L.setField(-2, "__index");
|
||||
// (1) script func (2) scripts table (3) env {} (4) mt { __index = _G }
|
||||
L.setMetatable(-2);
|
||||
// (1) script func (2) scripts table (3) env { mt }
|
||||
|
||||
L.pushNumber(index);
|
||||
// (1) script func (2) scripts table (3) {} (4) index
|
||||
// (1) script func (2) scripts table (3) env (4) index
|
||||
L.copy(-2);
|
||||
// (1) script func (2) scripts table (3) {} (4) index (5) {}
|
||||
// (1) script func (2) scripts table (3) env (4) index (5) env
|
||||
L.setTable(-4);
|
||||
// (1) script func (2) scripts table (3) {}
|
||||
// (1) script func (2) scripts table (3) env
|
||||
lua_setupvalue(L.getState(), -3, 1);
|
||||
// (1) script func (2) scripts table
|
||||
L.pop();
|
||||
@@ -177,9 +283,15 @@ void psxsplash::Lua::RegisterSceneScripts(int index) {
|
||||
L.rawGetI(LUA_REGISTRYINDEX, m_luascriptsReference);
|
||||
// (1) {} (2) scripts table
|
||||
L.pushNumber(index);
|
||||
// (1) {} (2) script environments table (2) index
|
||||
// (1) {} (2) script environments table (3) index
|
||||
L.getTable(-2);
|
||||
// (1) {} (2) script environments table (3) script environment table for the scene
|
||||
if (!L.isTable(-1)) {
|
||||
// Scene Lua file index is invalid or script not loaded
|
||||
printf("Warning: scene Lua file index %d not found\n", index);
|
||||
L.pop(3);
|
||||
return;
|
||||
}
|
||||
onSceneCreationStartFunctionWrapper.resolveGlobal(L);
|
||||
onSceneCreationEndFunctionWrapper.resolveGlobal(L);
|
||||
L.pop(3);
|
||||
@@ -218,29 +330,112 @@ void psxsplash::Lua::RegisterGameObject(GameObject* go) {
|
||||
// (1) {} (2) go + 1 (3) {}
|
||||
L.rawSet(LUA_REGISTRYINDEX);
|
||||
// (1) {}
|
||||
|
||||
// Initialize event mask for this object
|
||||
uint32_t eventMask = EVENT_NONE;
|
||||
|
||||
if (go->luaFileIndex != -1) {
|
||||
L.rawGetI(LUA_REGISTRYINDEX, m_luascriptsReference);
|
||||
// (1) {} (2) script environments table
|
||||
L.rawGetI(-1, go->luaFileIndex);
|
||||
// (1) {} (2) script environments table (3) script environment table for this object
|
||||
onCollisionMethodWrapper.resolveGlobal(L);
|
||||
onInteractMethodWrapper.resolveGlobal(L);
|
||||
|
||||
// Guard: if the script file failed to load (e.g. compilation error),
|
||||
// the environment will be nil — skip event resolution.
|
||||
if (!L.isTable(-1)) {
|
||||
L.pop(2);
|
||||
} else {
|
||||
|
||||
// Resolve each event and build the bitmask
|
||||
// Only events that exist in the script get their bit set
|
||||
if (onCreateMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_CREATE;
|
||||
if (onCollisionMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_COLLISION;
|
||||
if (onInteractMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_INTERACT;
|
||||
if (onTriggerEnterMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_ENTER;
|
||||
if (onTriggerStayMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_STAY;
|
||||
if (onTriggerExitMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_EXIT;
|
||||
if (onUpdateMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_UPDATE;
|
||||
if (onDestroyMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_DESTROY;
|
||||
if (onEnableMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_ENABLE;
|
||||
if (onDisableMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_DISABLE;
|
||||
if (onButtonPressMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_BUTTON_PRESS;
|
||||
if (onButtonReleaseMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_BUTTON_RELEASE;
|
||||
|
||||
L.pop(2);
|
||||
// (1) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the event mask directly in the GameObject
|
||||
go->eventMask = eventMask;
|
||||
|
||||
L.pop();
|
||||
// empty stack
|
||||
printf("GameObject registered in Lua registry: %p\n", ptr);
|
||||
|
||||
|
||||
// Fire onCreate event if this object handles it
|
||||
if (eventMask & EVENT_ON_CREATE) {
|
||||
onCreateMethodWrapper.callMethod(*this, go);
|
||||
}
|
||||
}
|
||||
|
||||
void psxsplash::Lua::OnCollision(GameObject* self, GameObject* other) {
|
||||
if (!hasEvent(self, EVENT_ON_COLLISION)) return;
|
||||
onCollisionMethodWrapper.callMethod(*this, self, other);
|
||||
}
|
||||
|
||||
void psxsplash::Lua::OnInteract(GameObject* self) {
|
||||
if (!hasEvent(self, EVENT_ON_INTERACT)) return;
|
||||
onInteractMethodWrapper.callMethod(*this, self);
|
||||
}
|
||||
|
||||
void psxsplash::Lua::OnTriggerEnter(GameObject* trigger, GameObject* other) {
|
||||
if (!hasEvent(trigger, EVENT_ON_TRIGGER_ENTER)) return;
|
||||
onTriggerEnterMethodWrapper.callMethod(*this, trigger, other);
|
||||
}
|
||||
|
||||
void psxsplash::Lua::OnTriggerStay(GameObject* trigger, GameObject* other) {
|
||||
if (!hasEvent(trigger, EVENT_ON_TRIGGER_STAY)) return;
|
||||
onTriggerStayMethodWrapper.callMethod(*this, trigger, other);
|
||||
}
|
||||
|
||||
void psxsplash::Lua::OnTriggerExit(GameObject* trigger, GameObject* other) {
|
||||
if (!hasEvent(trigger, EVENT_ON_TRIGGER_EXIT)) return;
|
||||
onTriggerExitMethodWrapper.callMethod(*this, trigger, other);
|
||||
}
|
||||
|
||||
void psxsplash::Lua::OnDestroy(GameObject* go) {
|
||||
if (!hasEvent(go, EVENT_ON_DESTROY)) return;
|
||||
onDestroyMethodWrapper.callMethod(*this, go);
|
||||
// Clear the event mask when object is destroyed
|
||||
go->eventMask = EVENT_NONE;
|
||||
}
|
||||
|
||||
void psxsplash::Lua::OnEnable(GameObject* go) {
|
||||
if (!hasEvent(go, EVENT_ON_ENABLE)) return;
|
||||
onEnableMethodWrapper.callMethod(*this, go);
|
||||
}
|
||||
|
||||
void psxsplash::Lua::OnDisable(GameObject* go) {
|
||||
if (!hasEvent(go, EVENT_ON_DISABLE)) return;
|
||||
onDisableMethodWrapper.callMethod(*this, go);
|
||||
}
|
||||
|
||||
void psxsplash::Lua::OnButtonPress(GameObject* go, int button) {
|
||||
if (!hasEvent(go, EVENT_ON_BUTTON_PRESS)) return;
|
||||
onButtonPressMethodWrapper.callMethod(*this, go, button);
|
||||
}
|
||||
|
||||
void psxsplash::Lua::OnButtonRelease(GameObject* go, int button) {
|
||||
if (!hasEvent(go, EVENT_ON_BUTTON_RELEASE)) return;
|
||||
onButtonReleaseMethodWrapper.callMethod(*this, go, button);
|
||||
}
|
||||
|
||||
void psxsplash::Lua::OnUpdate(GameObject* go, int deltaFrames) {
|
||||
if (!hasEvent(go, EVENT_ON_UPDATE)) return;
|
||||
onUpdateMethodWrapper.callMethod(*this, go, deltaFrames);
|
||||
}
|
||||
|
||||
void psxsplash::Lua::PushGameObject(GameObject* go) {
|
||||
auto L = m_state;
|
||||
L.push(go);
|
||||
|
||||
Reference in New Issue
Block a user