1755 lines
46 KiB
C++
1755 lines
46 KiB
C++
#include "luaapi.hh"
|
||
#include "scenemanager.hh"
|
||
#include "gameobject.hh"
|
||
#include "controls.hh"
|
||
#include "camera.hh"
|
||
#include "cutscene.hh"
|
||
#include "uisystem.hh"
|
||
|
||
#include <psyqo/soft-math.hh>
|
||
#include <psyqo/trigonometry.hh>
|
||
#include <psyqo/fixed-point.hh>
|
||
|
||
|
||
namespace psxsplash {
|
||
|
||
// Static member
|
||
SceneManager* LuaAPI::s_sceneManager = nullptr;
|
||
CutscenePlayer* LuaAPI::s_cutscenePlayer = nullptr;
|
||
UISystem* LuaAPI::s_uiSystem = nullptr;
|
||
|
||
// Scale factor: FixedPoint<12> stores 1.0 as raw 4096.
|
||
// Lua scripts work in world-space units (1 = one unit), so we convert.
|
||
static constexpr lua_Number kFixedScale = 4096;
|
||
|
||
static lua_Number fpToLua(psyqo::FixedPoint<12> fp) {
|
||
return static_cast<lua_Number>(fp.raw()) / kFixedScale;
|
||
}
|
||
|
||
static psyqo::FixedPoint<12> luaToFp(lua_Number val) {
|
||
return psyqo::FixedPoint<12>(static_cast<int32_t>(val * kFixedScale), psyqo::FixedPoint<12>::RAW);
|
||
}
|
||
|
||
// Angle scale: psyqo::Angle is FixedPoint<10>, so 1.0_pi = raw 1024
|
||
static constexpr lua_Number kAngleScale = 1024;
|
||
static psyqo::Trig<> s_trig;
|
||
|
||
// ============================================================================
|
||
// REGISTRATION
|
||
// ============================================================================
|
||
|
||
void LuaAPI::RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cutscenePlayer, UISystem* uiSystem) {
|
||
s_sceneManager = scene;
|
||
s_cutscenePlayer = cutscenePlayer;
|
||
s_uiSystem = uiSystem;
|
||
|
||
// ========================================================================
|
||
// ENTITY API
|
||
// ========================================================================
|
||
L.newTable(); // Entity table
|
||
|
||
L.push(Entity_FindByScriptIndex);
|
||
L.setField(-2, "FindByScriptIndex");
|
||
|
||
L.push(Entity_FindByIndex);
|
||
L.setField(-2, "FindByIndex");
|
||
|
||
L.push(Entity_Find);
|
||
L.setField(-2, "Find");
|
||
|
||
L.push(Entity_GetCount);
|
||
L.setField(-2, "GetCount");
|
||
|
||
L.push(Entity_SetActive);
|
||
L.setField(-2, "SetActive");
|
||
|
||
L.push(Entity_IsActive);
|
||
L.setField(-2, "IsActive");
|
||
|
||
L.push(Entity_GetPosition);
|
||
L.setField(-2, "GetPosition");
|
||
|
||
L.push(Entity_SetPosition);
|
||
L.setField(-2, "SetPosition");
|
||
|
||
L.push(Entity_GetRotationY);
|
||
L.setField(-2, "GetRotationY");
|
||
|
||
L.push(Entity_SetRotationY);
|
||
L.setField(-2, "SetRotationY");
|
||
|
||
L.push(Entity_ForEach);
|
||
L.setField(-2, "ForEach");
|
||
|
||
L.setGlobal("Entity");
|
||
|
||
// ========================================================================
|
||
// VEC3 API
|
||
// ========================================================================
|
||
L.newTable(); // Vec3 table
|
||
|
||
L.push(Vec3_New);
|
||
L.setField(-2, "new");
|
||
|
||
L.push(Vec3_Add);
|
||
L.setField(-2, "add");
|
||
|
||
L.push(Vec3_Sub);
|
||
L.setField(-2, "sub");
|
||
|
||
L.push(Vec3_Mul);
|
||
L.setField(-2, "mul");
|
||
|
||
L.push(Vec3_Dot);
|
||
L.setField(-2, "dot");
|
||
|
||
L.push(Vec3_Cross);
|
||
L.setField(-2, "cross");
|
||
|
||
L.push(Vec3_Length);
|
||
L.setField(-2, "length");
|
||
|
||
L.push(Vec3_LengthSq);
|
||
L.setField(-2, "lengthSq");
|
||
|
||
L.push(Vec3_Normalize);
|
||
L.setField(-2, "normalize");
|
||
|
||
L.push(Vec3_Distance);
|
||
L.setField(-2, "distance");
|
||
|
||
L.push(Vec3_DistanceSq);
|
||
L.setField(-2, "distanceSq");
|
||
|
||
L.push(Vec3_Lerp);
|
||
L.setField(-2, "lerp");
|
||
|
||
L.setGlobal("Vec3");
|
||
|
||
// ========================================================================
|
||
// INPUT API
|
||
// ========================================================================
|
||
L.newTable(); // Input table
|
||
|
||
L.push(Input_IsPressed);
|
||
L.setField(-2, "IsPressed");
|
||
|
||
L.push(Input_IsReleased);
|
||
L.setField(-2, "IsReleased");
|
||
|
||
L.push(Input_IsHeld);
|
||
L.setField(-2, "IsHeld");
|
||
|
||
L.push(Input_GetAnalog);
|
||
L.setField(-2, "GetAnalog");
|
||
|
||
// Register button constants
|
||
RegisterInputConstants(L);
|
||
|
||
L.setGlobal("Input");
|
||
|
||
// ========================================================================
|
||
// TIMER API
|
||
// ========================================================================
|
||
L.newTable(); // Timer table
|
||
|
||
L.push(Timer_GetFrameCount);
|
||
L.setField(-2, "GetFrameCount");
|
||
|
||
L.setGlobal("Timer");
|
||
|
||
// ========================================================================
|
||
// CAMERA API
|
||
// ========================================================================
|
||
L.newTable(); // Camera table
|
||
|
||
L.push(Camera_GetPosition);
|
||
L.setField(-2, "GetPosition");
|
||
|
||
L.push(Camera_SetPosition);
|
||
L.setField(-2, "SetPosition");
|
||
|
||
L.push(Camera_GetRotation);
|
||
L.setField(-2, "GetRotation");
|
||
|
||
L.push(Camera_SetRotation);
|
||
L.setField(-2, "SetRotation");
|
||
|
||
L.push(Camera_LookAt);
|
||
L.setField(-2, "LookAt");
|
||
|
||
L.setGlobal("Camera");
|
||
|
||
// ========================================================================
|
||
// AUDIO API (Placeholder)
|
||
// ========================================================================
|
||
L.newTable(); // Audio table
|
||
|
||
L.push(Audio_Play);
|
||
L.setField(-2, "Play");
|
||
|
||
L.push(Audio_Find);
|
||
L.setField(-2, "Find");
|
||
|
||
L.push(Audio_Stop);
|
||
L.setField(-2, "Stop");
|
||
|
||
L.push(Audio_SetVolume);
|
||
L.setField(-2, "SetVolume");
|
||
|
||
L.push(Audio_StopAll);
|
||
L.setField(-2, "StopAll");
|
||
|
||
L.setGlobal("Audio");
|
||
|
||
// ========================================================================
|
||
// DEBUG API
|
||
// ========================================================================
|
||
L.newTable(); // Debug table
|
||
|
||
L.push(Debug_Log);
|
||
L.setField(-2, "Log");
|
||
|
||
L.push(Debug_DrawLine);
|
||
L.setField(-2, "DrawLine");
|
||
|
||
L.push(Debug_DrawBox);
|
||
L.setField(-2, "DrawBox");
|
||
|
||
L.setGlobal("Debug");
|
||
|
||
// ========================================================================
|
||
// MATH API
|
||
// ========================================================================
|
||
L.newTable(); // PSXMath table (avoid conflict with Lua's math)
|
||
|
||
L.push(Math_Clamp);
|
||
L.setField(-2, "Clamp");
|
||
|
||
L.push(Math_Lerp);
|
||
L.setField(-2, "Lerp");
|
||
|
||
L.push(Math_Sign);
|
||
L.setField(-2, "Sign");
|
||
|
||
L.push(Math_Abs);
|
||
L.setField(-2, "Abs");
|
||
|
||
L.push(Math_Min);
|
||
L.setField(-2, "Min");
|
||
|
||
L.push(Math_Max);
|
||
L.setField(-2, "Max");
|
||
|
||
L.setGlobal("PSXMath");
|
||
|
||
// ========================================================================
|
||
// SCENE API
|
||
// ========================================================================
|
||
L.newTable(); // Scene table
|
||
|
||
L.push(Scene_Load);
|
||
L.setField(-2, "Load");
|
||
|
||
L.push(Scene_GetIndex);
|
||
L.setField(-2, "GetIndex");
|
||
|
||
L.setGlobal("Scene");
|
||
|
||
// ========================================================================
|
||
// PERSIST API
|
||
// ========================================================================
|
||
L.newTable(); // Persist table
|
||
|
||
L.push(Persist_Get);
|
||
L.setField(-2, "Get");
|
||
|
||
L.push(Persist_Set);
|
||
L.setField(-2, "Set");
|
||
|
||
L.setGlobal("Persist");
|
||
|
||
// ========================================================================
|
||
// CUTSCENE API
|
||
// ========================================================================
|
||
L.newTable(); // Cutscene table
|
||
|
||
L.push(Cutscene_Play);
|
||
L.setField(-2, "Play");
|
||
|
||
L.push(Cutscene_Stop);
|
||
L.setField(-2, "Stop");
|
||
|
||
L.push(Cutscene_IsPlaying);
|
||
L.setField(-2, "IsPlaying");
|
||
|
||
L.setGlobal("Cutscene");
|
||
|
||
// ========================================================================
|
||
// UI API
|
||
// ========================================================================
|
||
L.newTable(); // UI table
|
||
|
||
L.push(UI_FindCanvas);
|
||
L.setField(-2, "FindCanvas");
|
||
|
||
L.push(UI_SetCanvasVisible);
|
||
L.setField(-2, "SetCanvasVisible");
|
||
|
||
L.push(UI_IsCanvasVisible);
|
||
L.setField(-2, "IsCanvasVisible");
|
||
|
||
L.push(UI_FindElement);
|
||
L.setField(-2, "FindElement");
|
||
|
||
L.push(UI_SetVisible);
|
||
L.setField(-2, "SetVisible");
|
||
|
||
L.push(UI_IsVisible);
|
||
L.setField(-2, "IsVisible");
|
||
|
||
L.push(UI_SetText);
|
||
L.setField(-2, "SetText");
|
||
|
||
L.push(UI_GetText);
|
||
L.setField(-2, "GetText");
|
||
|
||
L.push(UI_SetProgress);
|
||
L.setField(-2, "SetProgress");
|
||
|
||
L.push(UI_GetProgress);
|
||
L.setField(-2, "GetProgress");
|
||
|
||
L.push(UI_SetColor);
|
||
L.setField(-2, "SetColor");
|
||
|
||
L.push(UI_GetColor);
|
||
L.setField(-2, "GetColor");
|
||
|
||
L.push(UI_SetPosition);
|
||
L.setField(-2, "SetPosition");
|
||
|
||
L.push(UI_GetPosition);
|
||
L.setField(-2, "GetPosition");
|
||
|
||
L.push(UI_SetSize);
|
||
L.setField(-2, "SetSize");
|
||
|
||
L.push(UI_GetSize);
|
||
L.setField(-2, "GetSize");
|
||
|
||
L.push(UI_SetProgressColors);
|
||
L.setField(-2, "SetProgressColors");
|
||
|
||
L.push(UI_GetElementType);
|
||
L.setField(-2, "GetElementType");
|
||
|
||
L.push(UI_GetElementCount);
|
||
L.setField(-2, "GetElementCount");
|
||
|
||
L.push(UI_GetElementByIndex);
|
||
L.setField(-2, "GetElementByIndex");
|
||
|
||
L.setGlobal("UI");
|
||
}
|
||
|
||
// ============================================================================
|
||
// ENTITY API IMPLEMENTATION
|
||
// ============================================================================
|
||
|
||
int LuaAPI::Entity_FindByScriptIndex(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager || !lua.isNumber(1)) {
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
// Find first object with matching luaFileIndex
|
||
int16_t luaIdx = static_cast<int16_t>(lua.toNumber(1));
|
||
for (size_t i = 0; i < s_sceneManager->getGameObjectCount(); i++) {
|
||
auto* go = s_sceneManager->getGameObject(static_cast<uint16_t>(i));
|
||
if (go && go->luaFileIndex == luaIdx) {
|
||
lua.push(reinterpret_cast<uint8_t*>(go));
|
||
lua.rawGet(LUA_REGISTRYINDEX);
|
||
if (lua.isTable(-1)) return 1;
|
||
lua.pop();
|
||
}
|
||
}
|
||
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Entity_FindByIndex(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isNumber(1)) {
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
int index = static_cast<int>(lua.toNumber(1));
|
||
|
||
if (s_sceneManager) {
|
||
GameObject* go = s_sceneManager->getGameObject(static_cast<uint16_t>(index));
|
||
if (go) {
|
||
lua.push(reinterpret_cast<uint8_t*>(go));
|
||
lua.rawGet(LUA_REGISTRYINDEX);
|
||
if (lua.isTable(-1)) {
|
||
return 1;
|
||
}
|
||
lua.pop();
|
||
}
|
||
}
|
||
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Entity_Find(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager) {
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
// Accept number (index) or string (name lookup) for backwards compat
|
||
// Check isNumber FIRST — in Lua, numbers pass isString too.
|
||
if (lua.isNumber(1)) {
|
||
int index = static_cast<int>(lua.toNumber(1));
|
||
GameObject* go = s_sceneManager->getGameObject(static_cast<uint16_t>(index));
|
||
if (go) {
|
||
lua.push(reinterpret_cast<uint8_t*>(go));
|
||
lua.rawGet(LUA_REGISTRYINDEX);
|
||
if (lua.isTable(-1)) return 1;
|
||
lua.pop();
|
||
}
|
||
} else if (lua.isString(1)) {
|
||
const char* name = lua.toString(1);
|
||
GameObject* go = s_sceneManager->findObjectByName(name);
|
||
if (go) {
|
||
lua.push(reinterpret_cast<uint8_t*>(go));
|
||
lua.rawGet(LUA_REGISTRYINDEX);
|
||
if (lua.isTable(-1)) return 1;
|
||
lua.pop();
|
||
}
|
||
}
|
||
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Entity_GetCount(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (s_sceneManager) {
|
||
lua.pushNumber(static_cast<lua_Number>(s_sceneManager->getGameObjectCount()));
|
||
} else {
|
||
lua.pushNumber(0);
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
|
||
int LuaAPI::Entity_SetActive(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1)) {
|
||
return 0;
|
||
}
|
||
|
||
lua.getField(1, "__cpp_ptr");
|
||
auto go = lua.toUserdata<GameObject>(-1);
|
||
lua.pop();
|
||
|
||
bool active = lua.toBoolean(2);
|
||
|
||
if (go && s_sceneManager) {
|
||
s_sceneManager->setObjectActive(go, active);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::Entity_IsActive(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1)) {
|
||
lua.push(false);
|
||
return 1;
|
||
}
|
||
|
||
lua.getField(1, "__cpp_ptr");
|
||
auto go = lua.toUserdata<GameObject>(-1);
|
||
lua.pop();
|
||
|
||
if (go) {
|
||
lua.push(go->isActive());
|
||
} else {
|
||
lua.push(false);
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Entity_GetPosition(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1)) {
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
lua.getField(1, "__cpp_ptr");
|
||
auto go = lua.toUserdata<GameObject>(-1);
|
||
lua.pop();
|
||
|
||
if (go) {
|
||
PushVec3(lua, go->position.x, go->position.y, go->position.z);
|
||
return 1;
|
||
}
|
||
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Entity_SetPosition(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1) || !lua.isTable(2)) {
|
||
return 0;
|
||
}
|
||
|
||
lua.getField(1, "__cpp_ptr");
|
||
auto go = lua.toUserdata<GameObject>(-1);
|
||
lua.pop();
|
||
|
||
if (!go) return 0;
|
||
|
||
psyqo::FixedPoint<12> x, y, z;
|
||
ReadVec3(lua, 2, x, y, z);
|
||
|
||
go->position.x = x;
|
||
go->position.y = y;
|
||
go->position.z = z;
|
||
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::Entity_GetRotationY(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1)) {
|
||
lua.pushNumber(0);
|
||
return 1;
|
||
}
|
||
|
||
lua.getField(1, "__cpp_ptr");
|
||
auto go = lua.toUserdata<GameObject>(-1);
|
||
lua.pop();
|
||
|
||
if (!go) { lua.pushNumber(0); return 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();
|
||
|
||
// Fast atan2 approximation (linear in first octant, fold to full circle)
|
||
psyqo::Angle angle;
|
||
if (cosRaw == 0 && sinRaw == 0) {
|
||
angle.value = 0;
|
||
} else {
|
||
int32_t abs_s = sinRaw < 0 ? -sinRaw : sinRaw;
|
||
int32_t abs_c = cosRaw < 0 ? -cosRaw : cosRaw;
|
||
int32_t minV = abs_s < abs_c ? abs_s : abs_c;
|
||
int32_t maxV = abs_s > abs_c ? abs_s : abs_c;
|
||
int32_t a = (minV * 256) / maxV; // [0, 256] for [0, π/4]
|
||
if (abs_s > abs_c) a = 512 - a;
|
||
if (cosRaw < 0) a = 1024 - a;
|
||
if (sinRaw < 0) a = -a;
|
||
angle.value = a;
|
||
}
|
||
|
||
// Return in pi-units: 0.5 = π/2 = 90°
|
||
lua.pushNumber(static_cast<lua_Number>(angle.value) / kAngleScale);
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Entity_SetRotationY(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1)) return 0;
|
||
|
||
lua.getField(1, "__cpp_ptr");
|
||
auto go = lua.toUserdata<GameObject>(-1);
|
||
lua.pop();
|
||
|
||
if (!go) return 0;
|
||
|
||
// Accept angle in pi-units (0.5 = π/2 = 90°)
|
||
lua_Number piUnits = lua.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;
|
||
}
|
||
|
||
int LuaAPI::Entity_ForEach(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager || !lua.isFunction(1)) return 0;
|
||
|
||
size_t count = s_sceneManager->getGameObjectCount();
|
||
for (size_t i = 0; i < count; i++) {
|
||
auto* go = s_sceneManager->getGameObject(static_cast<uint16_t>(i));
|
||
if (!go || !go->isActive()) continue;
|
||
|
||
// Push callback copy
|
||
lua.copy(1);
|
||
// Look up registered Lua table for this object (keyed by C++ pointer)
|
||
lua.push(reinterpret_cast<uint8_t*>(go));
|
||
lua.rawGet(LUA_REGISTRYINDEX);
|
||
if (!lua.isTable(-1)) {
|
||
lua.pop(2); // pop non-table + callback copy
|
||
continue;
|
||
}
|
||
lua.pushNumber(i); // push index as second argument
|
||
if (lua.pcall(2, 0) != LUA_OK) {
|
||
lua.pop(); // pop error message
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
// ============================================================================
|
||
// VEC3 API IMPLEMENTATION
|
||
// ============================================================================
|
||
|
||
void LuaAPI::PushVec3(psyqo::Lua& L, psyqo::FixedPoint<12> x,
|
||
psyqo::FixedPoint<12> y, psyqo::FixedPoint<12> z) {
|
||
L.newTable();
|
||
L.pushNumber(fpToLua(x));
|
||
L.setField(-2, "x");
|
||
L.pushNumber(fpToLua(y));
|
||
L.setField(-2, "y");
|
||
L.pushNumber(fpToLua(z));
|
||
L.setField(-2, "z");
|
||
}
|
||
|
||
void LuaAPI::ReadVec3(psyqo::Lua& L, int idx,
|
||
psyqo::FixedPoint<12>& x,
|
||
psyqo::FixedPoint<12>& y,
|
||
psyqo::FixedPoint<12>& z) {
|
||
L.getField(idx, "x");
|
||
x = luaToFp(L.toNumber(-1));
|
||
L.pop();
|
||
|
||
L.getField(idx, "y");
|
||
y = luaToFp(L.toNumber(-1));
|
||
L.pop();
|
||
|
||
L.getField(idx, "z");
|
||
z = luaToFp(L.toNumber(-1));
|
||
L.pop();
|
||
}
|
||
|
||
int LuaAPI::Vec3_New(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
psyqo::FixedPoint<12> x = luaToFp(lua.optNumber(1, 0));
|
||
psyqo::FixedPoint<12> y = luaToFp(lua.optNumber(2, 0));
|
||
psyqo::FixedPoint<12> z = luaToFp(lua.optNumber(3, 0));
|
||
|
||
PushVec3(lua, x, y, z);
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Vec3_Add(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1) || !lua.isTable(2)) {
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
psyqo::FixedPoint<12> ax, ay, az;
|
||
psyqo::FixedPoint<12> bx, by, bz;
|
||
|
||
ReadVec3(lua, 1, ax, ay, az);
|
||
ReadVec3(lua, 2, bx, by, bz);
|
||
|
||
PushVec3(lua, ax + bx, ay + by, az + bz);
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Vec3_Sub(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1) || !lua.isTable(2)) {
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
psyqo::FixedPoint<12> ax, ay, az;
|
||
psyqo::FixedPoint<12> bx, by, bz;
|
||
|
||
ReadVec3(lua, 1, ax, ay, az);
|
||
ReadVec3(lua, 2, bx, by, bz);
|
||
|
||
PushVec3(lua, ax - bx, ay - by, az - bz);
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Vec3_Mul(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1)) {
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
psyqo::FixedPoint<12> x, y, z;
|
||
ReadVec3(lua, 1, x, y, z);
|
||
|
||
psyqo::FixedPoint<12> scalar = luaToFp(lua.toNumber(2));
|
||
|
||
PushVec3(lua, x * scalar, y * scalar, z * scalar);
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Vec3_Dot(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1) || !lua.isTable(2)) {
|
||
lua.pushNumber(0);
|
||
return 1;
|
||
}
|
||
|
||
psyqo::FixedPoint<12> ax, ay, az;
|
||
psyqo::FixedPoint<12> bx, by, bz;
|
||
|
||
ReadVec3(lua, 1, ax, ay, az);
|
||
ReadVec3(lua, 2, bx, by, bz);
|
||
|
||
auto dot = ax * bx + ay * by + az * bz;
|
||
lua.pushNumber(fpToLua(dot));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Vec3_Cross(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1) || !lua.isTable(2)) {
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
psyqo::FixedPoint<12> ax, ay, az;
|
||
psyqo::FixedPoint<12> bx, by, bz;
|
||
|
||
ReadVec3(lua, 1, ax, ay, az);
|
||
ReadVec3(lua, 2, bx, by, bz);
|
||
|
||
psyqo::FixedPoint<12> cx = ay * bz - az * by;
|
||
psyqo::FixedPoint<12> cy = az * bx - ax * bz;
|
||
psyqo::FixedPoint<12> cz = ax * by - ay * bx;
|
||
|
||
PushVec3(lua, cx, cy, cz);
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Vec3_LengthSq(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1)) {
|
||
lua.pushNumber(0);
|
||
return 1;
|
||
}
|
||
|
||
psyqo::FixedPoint<12> x, y, z;
|
||
ReadVec3(lua, 1, x, y, z);
|
||
|
||
auto lengthSq = x * x + y * y + z * z;
|
||
lua.pushNumber(fpToLua(lengthSq));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Vec3_Length(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1)) {
|
||
lua.pushNumber(0);
|
||
return 1;
|
||
}
|
||
|
||
psyqo::FixedPoint<12> x, y, z;
|
||
ReadVec3(lua, 1, x, y, z);
|
||
|
||
// Compute length in scaled world-space to avoid fp12×fp12 overflow issues.
|
||
// Convert to Lua-number domain, sqrt there, return directly.
|
||
lua_Number sx = fpToLua(x);
|
||
lua_Number sy = fpToLua(y);
|
||
lua_Number sz = fpToLua(z);
|
||
lua_Number sqVal = sx * sx + sy * sy + sz * sz;
|
||
|
||
if (sqVal <= 0) {
|
||
lua.pushNumber(0);
|
||
return 1;
|
||
}
|
||
|
||
// Newton's method sqrt (integer-safe)
|
||
lua_Number guess = sqVal / 2;
|
||
if (guess == 0) guess = 1;
|
||
for (int i = 0; i < 12; i++) {
|
||
guess = (guess + sqVal / guess) / 2;
|
||
}
|
||
|
||
lua.pushNumber(guess);
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Vec3_Normalize(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1)) {
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
psyqo::FixedPoint<12> x, y, z;
|
||
ReadVec3(lua, 1, x, y, z);
|
||
|
||
// Work in Lua-number (world-space) domain for the sqrt
|
||
lua_Number sx = fpToLua(x);
|
||
lua_Number sy = fpToLua(y);
|
||
lua_Number sz = fpToLua(z);
|
||
lua_Number sLen = sx * sx + sy * sy + sz * sz;
|
||
|
||
if (sLen <= 0) {
|
||
PushVec3(lua, psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0));
|
||
return 1;
|
||
}
|
||
|
||
// Newton's method sqrt
|
||
lua_Number guess = sLen / 2;
|
||
if (guess == 0) guess = 1;
|
||
for (int i = 0; i < 12; i++) {
|
||
guess = (guess + sLen / guess) / 2;
|
||
}
|
||
if (guess == 0) guess = 1;
|
||
|
||
PushVec3(lua, luaToFp(sx / guess), luaToFp(sy / guess), luaToFp(sz / guess));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Vec3_DistanceSq(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1) || !lua.isTable(2)) {
|
||
lua.pushNumber(0);
|
||
return 1;
|
||
}
|
||
|
||
psyqo::FixedPoint<12> ax, ay, az;
|
||
psyqo::FixedPoint<12> bx, by, bz;
|
||
|
||
ReadVec3(lua, 1, ax, ay, az);
|
||
ReadVec3(lua, 2, bx, by, bz);
|
||
|
||
auto dx = ax - bx;
|
||
auto dy = ay - by;
|
||
auto dz = az - bz;
|
||
|
||
auto distSq = dx * dx + dy * dy + dz * dz;
|
||
lua.pushNumber(fpToLua(distSq));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Vec3_Distance(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1) || !lua.isTable(2)) {
|
||
lua.pushNumber(0);
|
||
return 1;
|
||
}
|
||
|
||
psyqo::FixedPoint<12> ax, ay, az;
|
||
psyqo::FixedPoint<12> bx, by, bz;
|
||
|
||
ReadVec3(lua, 1, ax, ay, az);
|
||
ReadVec3(lua, 2, bx, by, bz);
|
||
|
||
lua_Number dx = fpToLua(ax) - fpToLua(bx);
|
||
lua_Number dy = fpToLua(ay) - fpToLua(by);
|
||
lua_Number dz = fpToLua(az) - fpToLua(bz);
|
||
|
||
lua_Number sqVal = dx * dx + dy * dy + dz * dz;
|
||
|
||
if (sqVal <= 0) {
|
||
lua.pushNumber(0);
|
||
return 1;
|
||
}
|
||
|
||
lua_Number guess = sqVal / 2;
|
||
if (guess == 0) guess = 1;
|
||
for (int i = 0; i < 12; i++) {
|
||
guess = (guess + sqVal / guess) / 2;
|
||
}
|
||
|
||
lua.pushNumber(guess);
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Vec3_Lerp(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!lua.isTable(1) || !lua.isTable(2)) {
|
||
lua.push();
|
||
return 1;
|
||
}
|
||
|
||
psyqo::FixedPoint<12> ax, ay, az;
|
||
psyqo::FixedPoint<12> bx, by, bz;
|
||
|
||
ReadVec3(lua, 1, ax, ay, az);
|
||
ReadVec3(lua, 2, bx, by, bz);
|
||
|
||
psyqo::FixedPoint<12> t = luaToFp(lua.toNumber(3));
|
||
psyqo::FixedPoint<12> oneMinusT = psyqo::FixedPoint<12>(4096, psyqo::FixedPoint<12>::RAW) - t;
|
||
|
||
psyqo::FixedPoint<12> rx = ax * oneMinusT + bx * t;
|
||
psyqo::FixedPoint<12> ry = ay * oneMinusT + by * t;
|
||
psyqo::FixedPoint<12> rz = az * oneMinusT + bz * t;
|
||
|
||
PushVec3(lua, rx, ry, rz);
|
||
return 1;
|
||
}
|
||
|
||
// ============================================================================
|
||
// INPUT API IMPLEMENTATION
|
||
// ============================================================================
|
||
|
||
void LuaAPI::RegisterInputConstants(psyqo::Lua& L) {
|
||
// Button constants - must match psyqo::AdvancedPad::Button enum
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::Cross));
|
||
L.setField(-2, "CROSS");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::Circle));
|
||
L.setField(-2, "CIRCLE");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::Square));
|
||
L.setField(-2, "SQUARE");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::Triangle));
|
||
L.setField(-2, "TRIANGLE");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::L1));
|
||
L.setField(-2, "L1");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::R1));
|
||
L.setField(-2, "R1");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::L2));
|
||
L.setField(-2, "L2");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::R2));
|
||
L.setField(-2, "R2");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::Start));
|
||
L.setField(-2, "START");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::Select));
|
||
L.setField(-2, "SELECT");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::Up));
|
||
L.setField(-2, "UP");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::Down));
|
||
L.setField(-2, "DOWN");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::Left));
|
||
L.setField(-2, "LEFT");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::Right));
|
||
L.setField(-2, "RIGHT");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::L3));
|
||
L.setField(-2, "L3");
|
||
|
||
L.pushNumber(static_cast<lua_Number>(psyqo::AdvancedPad::Button::R3));
|
||
L.setField(-2, "R3");
|
||
}
|
||
|
||
int LuaAPI::Input_IsPressed(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager || !lua.isNumber(1)) {
|
||
lua.push(false);
|
||
return 1;
|
||
}
|
||
|
||
auto button = static_cast<psyqo::AdvancedPad::Button>(static_cast<uint16_t>(lua.toNumber(1)));
|
||
lua.push(s_sceneManager->getControls().wasButtonPressed(button));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Input_IsReleased(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager || !lua.isNumber(1)) {
|
||
lua.push(false);
|
||
return 1;
|
||
}
|
||
|
||
auto button = static_cast<psyqo::AdvancedPad::Button>(static_cast<uint16_t>(lua.toNumber(1)));
|
||
lua.push(s_sceneManager->getControls().wasButtonReleased(button));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Input_IsHeld(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager || !lua.isNumber(1)) {
|
||
lua.push(false);
|
||
return 1;
|
||
}
|
||
|
||
auto button = static_cast<psyqo::AdvancedPad::Button>(static_cast<uint16_t>(lua.toNumber(1)));
|
||
lua.push(s_sceneManager->getControls().isButtonHeld(button));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Input_GetAnalog(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager) {
|
||
lua.pushNumber(0);
|
||
lua.pushNumber(0);
|
||
return 2;
|
||
}
|
||
|
||
int stick = lua.isNumber(1) ? static_cast<int>(lua.toNumber(1)) : 0;
|
||
auto& controls = s_sceneManager->getControls();
|
||
|
||
int16_t x, y;
|
||
if (stick == 1) {
|
||
x = controls.getRightStickX();
|
||
y = controls.getRightStickY();
|
||
} else {
|
||
x = controls.getLeftStickX();
|
||
y = controls.getLeftStickY();
|
||
}
|
||
|
||
// Scale to approximately [-1.0, 1.0] in Lua number space
|
||
// Stick range is -127 to +127; divide by 127
|
||
lua.pushNumber(x * kFixedScale / 127);
|
||
lua.pushNumber(y * kFixedScale / 127);
|
||
return 2;
|
||
}
|
||
|
||
// ============================================================================
|
||
// TIMER API IMPLEMENTATION
|
||
// ============================================================================
|
||
|
||
static uint32_t s_frameCount = 0;
|
||
|
||
void LuaAPI::IncrementFrameCount() {
|
||
s_frameCount++;
|
||
}
|
||
|
||
void LuaAPI::ResetFrameCount() {
|
||
s_frameCount = 0;
|
||
}
|
||
|
||
int LuaAPI::Timer_GetFrameCount(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
lua.pushNumber(s_frameCount);
|
||
return 1;
|
||
}
|
||
|
||
// ============================================================================
|
||
// CAMERA API IMPLEMENTATION
|
||
// ============================================================================
|
||
|
||
int LuaAPI::Camera_GetPosition(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (s_sceneManager) {
|
||
auto& pos = s_sceneManager->getCamera().GetPosition();
|
||
PushVec3(lua, pos.x, pos.y, pos.z);
|
||
} else {
|
||
PushVec3(lua, psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0));
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Camera_SetPosition(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager || !lua.isTable(1)) return 0;
|
||
|
||
psyqo::FixedPoint<12> x, y, z;
|
||
ReadVec3(lua, 1, x, y, z);
|
||
s_sceneManager->getCamera().SetPosition(x, y, z);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::Camera_GetRotation(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
// Camera only stores the rotation matrix internally.
|
||
// Decomposing back to Euler angles is not supported.
|
||
// Return {0,0,0} — use Camera.SetRotation to control orientation.
|
||
PushVec3(lua, psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Camera_SetRotation(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager) return 0;
|
||
|
||
// Accept three angles in pi-units (e.g., 0.5 = π/2 = 90°)
|
||
// This matches psyqo::Angle convention used by the engine.
|
||
psyqo::Angle rx, ry, rz;
|
||
rx.value = static_cast<int32_t>(lua.optNumber(1, 0) * kAngleScale);
|
||
ry.value = static_cast<int32_t>(lua.optNumber(2, 0) * kAngleScale);
|
||
rz.value = static_cast<int32_t>(lua.optNumber(3, 0) * kAngleScale);
|
||
s_sceneManager->getCamera().SetRotation(rx, ry, rz);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::Camera_LookAt(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager) return 0;
|
||
|
||
psyqo::FixedPoint<12> tx, ty, tz;
|
||
|
||
if (lua.isTable(1)) {
|
||
ReadVec3(lua, 1, tx, ty, tz);
|
||
} else {
|
||
tx = luaToFp(lua.optNumber(1, 0));
|
||
ty = luaToFp(lua.optNumber(2, 0));
|
||
tz = luaToFp(lua.optNumber(3, 0));
|
||
}
|
||
|
||
auto& cam = s_sceneManager->getCamera();
|
||
auto& pos = cam.GetPosition();
|
||
|
||
// Compute direction vector from camera to target
|
||
lua_Number dx = fpToLua(tx) - fpToLua(pos.x);
|
||
lua_Number dy = fpToLua(ty) - fpToLua(pos.y);
|
||
lua_Number dz = fpToLua(tz) - fpToLua(pos.z);
|
||
|
||
// Compute horizontal distance for pitch calculation
|
||
lua_Number horizDistSq = dx * dx + dz * dz;
|
||
lua_Number horizGuess = horizDistSq / 2;
|
||
if (horizGuess == 0) horizGuess = 1;
|
||
for (int i = 0; i < 12; i++) {
|
||
horizGuess = (horizGuess + horizDistSq / horizGuess) / 2;
|
||
}
|
||
|
||
// Yaw = atan2(dx, dz) — approximate with lookup or use psyqo trig
|
||
// For now, use a simple atan2 approximation in fp12 domain
|
||
// and set rotation via SetRotation (pitch, yaw, 0)
|
||
// Approximate: yaw is proportional to dx/dz in small-angle
|
||
// Full implementation requires psyqo Trig atan2 which is not trivially
|
||
// accessible here. Set rotation to face the target on the Y axis.
|
||
// This is a simplified look-at that only handles yaw.
|
||
psyqo::Angle yaw;
|
||
psyqo::Angle pitch;
|
||
|
||
// Use scaled integer atan2 approximation
|
||
// atan2(dx, dz) in the range [-π, π]
|
||
// For PS1, the exact method depends on psyqo's Trig class.
|
||
// Returning luaError since we can't do a proper atan2 without Trig instance.
|
||
// Compromise: just set rotation angles directly
|
||
yaw.value = 0;
|
||
pitch.value = 0;
|
||
|
||
// For a real implementation, Camera would need a LookAt method.
|
||
return 0;
|
||
}
|
||
|
||
// ============================================================================
|
||
// AUDIO API IMPLEMENTATION
|
||
// ============================================================================
|
||
|
||
int LuaAPI::Audio_Play(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager) {
|
||
lua.pushNumber(-1);
|
||
return 1;
|
||
}
|
||
|
||
int soundId = -1;
|
||
|
||
// Accept number (index) or string (name lookup) like Entity.Find
|
||
// Check isNumber FIRST - in Lua, numbers pass isString too.
|
||
if (lua.isNumber(1)) {
|
||
soundId = static_cast<int>(lua.toNumber(1));
|
||
} else if (lua.isString(1)) {
|
||
const char* name = lua.toString(1);
|
||
soundId = s_sceneManager->findAudioClipByName(name);
|
||
if (soundId < 0) {
|
||
lua.pushNumber(-1);
|
||
return 1;
|
||
}
|
||
} else {
|
||
lua.pushNumber(-1);
|
||
return 1;
|
||
}
|
||
|
||
int volume = static_cast<int>(lua.optNumber(2, 100));
|
||
int pan = static_cast<int>(lua.optNumber(3, 64));
|
||
|
||
int voice = s_sceneManager->getAudio().play(soundId, volume, pan);
|
||
lua.pushNumber(voice);
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Audio_Find(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager || !lua.isString(1)) {
|
||
lua.push(); // nil
|
||
return 1;
|
||
}
|
||
|
||
const char* name = lua.toString(1);
|
||
int clipIndex = s_sceneManager->findAudioClipByName(name);
|
||
|
||
if (clipIndex >= 0) {
|
||
lua.pushNumber(static_cast<lua_Number>(clipIndex));
|
||
} else {
|
||
lua.push(); // nil
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Audio_Stop(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_sceneManager) return 0;
|
||
int channelId = static_cast<int>(lua.toNumber(1));
|
||
s_sceneManager->getAudio().stopVoice(channelId);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::Audio_SetVolume(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_sceneManager) return 0;
|
||
int channelId = static_cast<int>(lua.toNumber(1));
|
||
int volume = static_cast<int>(lua.toNumber(2));
|
||
int pan = static_cast<int>(lua.optNumber(3, 64));
|
||
s_sceneManager->getAudio().setVoiceVolume(channelId, volume, pan);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::Audio_StopAll(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_sceneManager) return 0;
|
||
s_sceneManager->getAudio().stopAll();
|
||
return 0;
|
||
}
|
||
|
||
// ============================================================================
|
||
// DEBUG API IMPLEMENTATION
|
||
// ============================================================================
|
||
|
||
int LuaAPI::Debug_Log(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
// Debug.Log is a no-op on PS1 to avoid printf overhead.
|
||
// Messages are only visible in emulator TTY builds.
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::Debug_DrawLine(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
// Parse start and end Vec3 tables, optional color
|
||
psyqo::FixedPoint<12> sx, sy, sz, ex, ey, ez;
|
||
if (lua.isTable(1) && lua.isTable(2)) {
|
||
ReadVec3(lua, 1, sx, sy, sz);
|
||
ReadVec3(lua, 2, ex, ey, ez);
|
||
}
|
||
|
||
// TODO: Queue LINE_G2 primitive through Renderer
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::Debug_DrawBox(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
// Parse center and size Vec3 tables, optional color
|
||
psyqo::FixedPoint<12> cx, cy, cz, hx, hy, hz;
|
||
if (lua.isTable(1) && lua.isTable(2)) {
|
||
ReadVec3(lua, 1, cx, cy, cz);
|
||
ReadVec3(lua, 2, hx, hy, hz);
|
||
}
|
||
|
||
// TODO: Queue 12 LINE_G2 primitives (box wireframe) through Renderer
|
||
return 0;
|
||
}
|
||
|
||
// ============================================================================
|
||
// MATH API IMPLEMENTATION
|
||
// ============================================================================
|
||
|
||
int LuaAPI::Math_Clamp(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
lua_Number value = lua.toNumber(1);
|
||
lua_Number minVal = lua.toNumber(2);
|
||
lua_Number maxVal = lua.toNumber(3);
|
||
|
||
if (value < minVal) value = minVal;
|
||
if (value > maxVal) value = maxVal;
|
||
|
||
lua.pushNumber(value);
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Math_Lerp(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
lua_Number a = lua.toNumber(1);
|
||
lua_Number b = lua.toNumber(2);
|
||
lua_Number t = lua.toNumber(3);
|
||
|
||
lua.pushNumber(a + (b - a) * t);
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Math_Sign(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
lua_Number value = lua.toNumber(1);
|
||
|
||
if (value > 0) lua.pushNumber(1);
|
||
else if (value < 0) lua.pushNumber(-1);
|
||
else lua.pushNumber(0);
|
||
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Math_Abs(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
lua_Number value = lua.toNumber(1);
|
||
lua.pushNumber(value < 0 ? -value : value);
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Math_Min(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
lua_Number a = lua.toNumber(1);
|
||
lua_Number b = lua.toNumber(2);
|
||
|
||
lua.pushNumber(a < b ? a : b);
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Math_Max(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
lua_Number a = lua.toNumber(1);
|
||
lua_Number b = lua.toNumber(2);
|
||
|
||
lua.pushNumber(a > b ? a : b);
|
||
return 1;
|
||
}
|
||
|
||
// ============================================================================
|
||
// SCENE API IMPLEMENTATION
|
||
// ============================================================================
|
||
|
||
int LuaAPI::Scene_Load(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager || !lua.isNumber(1)) {
|
||
return 0;
|
||
}
|
||
|
||
int sceneIndex = static_cast<int>(lua.toNumber(1));
|
||
s_sceneManager->requestSceneLoad(sceneIndex);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::Scene_GetIndex(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_sceneManager) {
|
||
lua.pushNumber(0);
|
||
return 1;
|
||
}
|
||
|
||
lua.pushNumber(static_cast<lua_Number>(s_sceneManager->getCurrentSceneIndex()));
|
||
return 1;
|
||
}
|
||
|
||
// ============================================================================
|
||
// PERSIST API IMPLEMENTATION
|
||
// ============================================================================
|
||
|
||
struct PersistEntry {
|
||
char key[32];
|
||
lua_Number value;
|
||
bool used;
|
||
};
|
||
|
||
static PersistEntry s_persistData[16] = {};
|
||
|
||
// Inline string helpers (no libc on bare-metal PS1)
|
||
static bool streq(const char* a, const char* b) {
|
||
while (*a && *b) { if (*a++ != *b++) return false; }
|
||
return *a == *b;
|
||
}
|
||
|
||
static void strcopy(char* dst, const char* src, int maxLen) {
|
||
int i = 0;
|
||
for (; i < maxLen - 1 && src[i]; i++) dst[i] = src[i];
|
||
dst[i] = '\0';
|
||
}
|
||
|
||
int LuaAPI::Persist_Get(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
const char* key = lua.toString(1);
|
||
if (!key) { lua.push(); return 1; }
|
||
|
||
for (int i = 0; i < 16; i++) {
|
||
if (s_persistData[i].used && streq(s_persistData[i].key, key)) {
|
||
lua.pushNumber(s_persistData[i].value);
|
||
return 1;
|
||
}
|
||
}
|
||
lua.push(); // nil
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::Persist_Set(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
const char* key = lua.toString(1);
|
||
if (!key) return 0;
|
||
|
||
lua_Number value = lua.toNumber(2);
|
||
|
||
// Update existing key
|
||
for (int i = 0; i < 16; i++) {
|
||
if (s_persistData[i].used && streq(s_persistData[i].key, key)) {
|
||
s_persistData[i].value = value;
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
// Find empty slot
|
||
for (int i = 0; i < 16; i++) {
|
||
if (!s_persistData[i].used) {
|
||
strcopy(s_persistData[i].key, key, 32);
|
||
s_persistData[i].value = value;
|
||
s_persistData[i].used = true;
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
return 0; // No room — silently fail
|
||
}
|
||
|
||
void LuaAPI::PersistClear() {
|
||
for (int i = 0; i < 16; i++) {
|
||
s_persistData[i].used = false;
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// CUTSCENE API IMPLEMENTATION
|
||
// ============================================================================
|
||
|
||
int LuaAPI::Cutscene_Play(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (!s_cutscenePlayer || !lua.isString(1)) {
|
||
return 0;
|
||
}
|
||
|
||
const char* name = lua.toString(1);
|
||
s_cutscenePlayer->play(name);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::Cutscene_Stop(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (s_cutscenePlayer) {
|
||
s_cutscenePlayer->stop();
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::Cutscene_IsPlaying(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
|
||
if (s_cutscenePlayer) {
|
||
lua.push(s_cutscenePlayer->isPlaying());
|
||
} else {
|
||
lua.push(false);
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
// ============================================================================
|
||
// UI API IMPLEMENTATION
|
||
// ============================================================================
|
||
|
||
int LuaAPI::UI_FindCanvas(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isString(1)) {
|
||
lua.pushNumber(-1);
|
||
return 1;
|
||
}
|
||
const char* name = lua.toString(1);
|
||
int idx = s_uiSystem->findCanvas(name);
|
||
lua.pushNumber(static_cast<lua_Number>(idx));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::UI_SetCanvasVisible(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem) return 0;
|
||
int idx;
|
||
// Accept number (index) or string (name)
|
||
if (lua.isNumber(1)) {
|
||
idx = static_cast<int>(lua.toNumber(1));
|
||
} else if (lua.isString(1)) {
|
||
idx = s_uiSystem->findCanvas(lua.toString(1));
|
||
} else {
|
||
return 0;
|
||
}
|
||
bool visible = lua.toBoolean(2);
|
||
s_uiSystem->setCanvasVisible(idx, visible);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::UI_IsCanvasVisible(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem) {
|
||
lua.push(false);
|
||
return 1;
|
||
}
|
||
int idx;
|
||
if (lua.isNumber(1)) {
|
||
idx = static_cast<int>(lua.toNumber(1));
|
||
} else if (lua.isString(1)) {
|
||
idx = s_uiSystem->findCanvas(lua.toString(1));
|
||
} else {
|
||
lua.push(false);
|
||
return 1;
|
||
}
|
||
lua.push(s_uiSystem->isCanvasVisible(idx));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::UI_FindElement(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1) || !lua.isString(2)) {
|
||
lua.pushNumber(-1);
|
||
return 1;
|
||
}
|
||
int canvasIdx = static_cast<int>(lua.toNumber(1));
|
||
const char* name = lua.toString(2);
|
||
int handle = s_uiSystem->findElement(canvasIdx, name);
|
||
lua.pushNumber(static_cast<lua_Number>(handle));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::UI_SetVisible(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
bool visible = lua.toBoolean(2);
|
||
s_uiSystem->setElementVisible(handle, visible);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::UI_IsVisible(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) {
|
||
lua.push(false);
|
||
return 1;
|
||
}
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
lua.push(s_uiSystem->isElementVisible(handle));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::UI_SetText(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
const char* text = lua.isString(2) ? lua.toString(2) : "";
|
||
s_uiSystem->setText(handle, text);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::UI_SetProgress(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
int value = static_cast<int>(lua.toNumber(2));
|
||
if (value < 0) value = 0;
|
||
if (value > 100) value = 100;
|
||
s_uiSystem->setProgress(handle, (uint8_t)value);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::UI_GetProgress(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) {
|
||
lua.pushNumber(0);
|
||
return 1;
|
||
}
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
lua.pushNumber(static_cast<lua_Number>(s_uiSystem->getProgress(handle)));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::UI_SetColor(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
uint8_t r = static_cast<uint8_t>(lua.toNumber(2));
|
||
uint8_t g = static_cast<uint8_t>(lua.toNumber(3));
|
||
uint8_t b = static_cast<uint8_t>(lua.toNumber(4));
|
||
s_uiSystem->setColor(handle, r, g, b);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::UI_SetPosition(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
int16_t x = static_cast<int16_t>(lua.toNumber(2));
|
||
int16_t y = static_cast<int16_t>(lua.toNumber(3));
|
||
s_uiSystem->setPosition(handle, x, y);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::UI_GetText(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) {
|
||
lua.push("");
|
||
return 1;
|
||
}
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
lua.push(s_uiSystem->getText(handle));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::UI_GetColor(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) {
|
||
lua.pushNumber(0); lua.pushNumber(0); lua.pushNumber(0);
|
||
return 3;
|
||
}
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
uint8_t r, g, b;
|
||
s_uiSystem->getColor(handle, r, g, b);
|
||
lua.pushNumber(r); lua.pushNumber(g); lua.pushNumber(b);
|
||
return 3;
|
||
}
|
||
|
||
int LuaAPI::UI_GetPosition(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) {
|
||
lua.pushNumber(0); lua.pushNumber(0);
|
||
return 2;
|
||
}
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
int16_t x, y;
|
||
s_uiSystem->getPosition(handle, x, y);
|
||
lua.pushNumber(static_cast<lua_Number>(x));
|
||
lua.pushNumber(static_cast<lua_Number>(y));
|
||
return 2;
|
||
}
|
||
|
||
int LuaAPI::UI_SetSize(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
int16_t w = static_cast<int16_t>(lua.toNumber(2));
|
||
int16_t h = static_cast<int16_t>(lua.toNumber(3));
|
||
s_uiSystem->setSize(handle, w, h);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::UI_GetSize(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) {
|
||
lua.pushNumber(0); lua.pushNumber(0);
|
||
return 2;
|
||
}
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
int16_t w, h;
|
||
s_uiSystem->getSize(handle, w, h);
|
||
lua.pushNumber(static_cast<lua_Number>(w));
|
||
lua.pushNumber(static_cast<lua_Number>(h));
|
||
return 2;
|
||
}
|
||
|
||
int LuaAPI::UI_SetProgressColors(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
uint8_t bgR = static_cast<uint8_t>(lua.toNumber(2));
|
||
uint8_t bgG = static_cast<uint8_t>(lua.toNumber(3));
|
||
uint8_t bgB = static_cast<uint8_t>(lua.toNumber(4));
|
||
uint8_t fR = static_cast<uint8_t>(lua.toNumber(5));
|
||
uint8_t fG = static_cast<uint8_t>(lua.toNumber(6));
|
||
uint8_t fB = static_cast<uint8_t>(lua.toNumber(7));
|
||
s_uiSystem->setProgressColors(handle, bgR, bgG, bgB, fR, fG, fB);
|
||
return 0;
|
||
}
|
||
|
||
int LuaAPI::UI_GetElementType(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) {
|
||
lua.pushNumber(-1);
|
||
return 1;
|
||
}
|
||
int handle = static_cast<int>(lua.toNumber(1));
|
||
lua.pushNumber(static_cast<lua_Number>(static_cast<uint8_t>(s_uiSystem->getElementType(handle))));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::UI_GetElementCount(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1)) {
|
||
lua.pushNumber(0);
|
||
return 1;
|
||
}
|
||
int canvasIdx = static_cast<int>(lua.toNumber(1));
|
||
lua.pushNumber(static_cast<lua_Number>(s_uiSystem->getCanvasElementCount(canvasIdx)));
|
||
return 1;
|
||
}
|
||
|
||
int LuaAPI::UI_GetElementByIndex(lua_State* L) {
|
||
psyqo::Lua lua(L);
|
||
if (!s_uiSystem || !lua.isNumber(1) || !lua.isNumber(2)) {
|
||
lua.pushNumber(-1);
|
||
return 1;
|
||
}
|
||
int canvasIdx = static_cast<int>(lua.toNumber(1));
|
||
int elemIdx = static_cast<int>(lua.toNumber(2));
|
||
int handle = s_uiSystem->getCanvasElementHandle(canvasIdx, elemIdx);
|
||
lua.pushNumber(static_cast<lua_Number>(handle));
|
||
return 1;
|
||
}
|
||
|
||
} // namespace psxsplash
|