diff --git a/src/collision.cpp b/src/collision.cpp index aa4da19..a66304f 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -169,11 +169,12 @@ int CollisionSystem::detectCollisions(const AABB& playerAABB, psyqo::Vec3& pushB m_resultCount = 0; const FP zero(0); pushBack = psyqo::Vec3{zero, zero, zero}; - - // Rebuild spatial grid with all colliders + + // Rebuild spatial grid with active colliders only m_grid.clear(); for (int i = 0; i < m_colliderCount; i++) { - if(scene.getGameObject(m_colliders[i].gameObjectIndex)->isActive()) { + auto* go = scene.getGameObject(m_colliders[i].gameObjectIndex); + if (go && go->isActive()) { m_grid.insert(i, m_colliders[i].bounds); } } diff --git a/src/cutscene.cpp b/src/cutscene.cpp index ad7dc53..38b9788 100644 --- a/src/cutscene.cpp +++ b/src/cutscene.cpp @@ -59,8 +59,8 @@ bool CutscenePlayer::play(const char* name, bool loop) { track.initialValues[2] = (int16_t)track.target->position.z.value; } break; - case TrackType::ObjectRotationY: - // TODO: I added this. Then realized that it only stores rotation matrix. That sucks. To anyone who finds this Pull requests are open :P + case TrackType::ObjectRotation: + // Initial rotation angles: 0,0,0 (no way to extract Euler from matrix) break; case TrackType::ObjectActive: if (track.target) { @@ -289,7 +289,7 @@ void CutscenePlayer::applyTrack(CutsceneTrack& track) { case TrackType::CameraRotation: { if (!m_camera) return; - lerpAngles(track.keyframes, track.keyframeCount, track.initialValues, out); + lerpKeyframes(track.keyframes, track.keyframeCount, track.initialValues, out); psyqo::Angle rx, ry, rz; rx.value = (int32_t)out[0]; ry.value = (int32_t)out[1]; @@ -307,13 +307,18 @@ void CutscenePlayer::applyTrack(CutsceneTrack& track) { break; } - case TrackType::ObjectRotationY: { + case TrackType::ObjectRotation: { if (!track.target) return; - lerpAngles(track.keyframes, track.keyframeCount, track.initialValues, out); - psyqo::Angle yAngle; - yAngle.value = (int32_t)out[1]; - track.target->rotation = psyqo::SoftMath::generateRotationMatrix33( - yAngle, psyqo::SoftMath::Axis::Y, m_trig); + lerpKeyframes(track.keyframes, track.keyframeCount, track.initialValues, out); + psyqo::Angle rx, ry, rz; + rx.value = (int32_t)out[0]; + ry.value = (int32_t)out[1]; + rz.value = (int32_t)out[2]; + auto matY = psyqo::SoftMath::generateRotationMatrix33(ry, psyqo::SoftMath::Axis::Y, m_trig); + auto matX = psyqo::SoftMath::generateRotationMatrix33(rx, psyqo::SoftMath::Axis::X, m_trig); + auto matZ = psyqo::SoftMath::generateRotationMatrix33(rz, psyqo::SoftMath::Axis::Z, m_trig); + auto temp = psyqo::SoftMath::multiplyMatrix33(matY, matX); + track.target->rotation = psyqo::SoftMath::multiplyMatrix33(temp, matZ); break; } diff --git a/src/cutscene.hh b/src/cutscene.hh index 50c15ae..12fc087 100644 --- a/src/cutscene.hh +++ b/src/cutscene.hh @@ -24,7 +24,7 @@ enum class TrackType : uint8_t { CameraPosition = 0, CameraRotation = 1, ObjectPosition = 2, - ObjectRotationY = 3, + ObjectRotation = 3, ObjectActive = 4, UICanvasVisible = 5, UIElementVisible= 6, diff --git a/src/interactable.hh b/src/interactable.hh index 1053295..57f4d02 100644 --- a/src/interactable.hh +++ b/src/interactable.hh @@ -19,7 +19,7 @@ struct Interactable { uint8_t interactButton; // Configuration flags - uint8_t flags; // bit 0: isRepeatable, bit 1: showPrompt, bit 2: requireLineOfSight + uint8_t flags; // bit 0: isRepeatable, bit 1: showPrompt, bit 2: requireLineOfSight, bit 3: disabled // Cooldown between interactions (in frames) uint16_t cooldownFrames; @@ -35,6 +35,11 @@ struct Interactable { bool isRepeatable() const { return flags & 0x01; } bool showPrompt() const { return flags & 0x02; } bool requireLineOfSight() const { return flags & 0x04; } + bool isDisabled() const { return flags & 0x08; } + void setDisabled(bool disabled) { + if (disabled) flags |= 0x08; + else flags &= ~0x08; + } // Check if ready to interact bool canInteract() const { diff --git a/src/lua.cpp b/src/lua.cpp index dd556c8..e4b3eb3 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -187,6 +187,12 @@ void psxsplash::Lua::Reset() { } void psxsplash::Lua::LoadLuaFile(const char* code, size_t len, int index) { + // Store bytecode reference for per-object re-execution in RegisterGameObject. + if (index < MAX_LUA_FILES) { + m_bytecodeRefs[index] = {code, len}; + if (index >= m_bytecodeRefCount) m_bytecodeRefCount = index + 1; + } + auto L = m_state; char filename[32]; snprintf(filename, sizeof(filename), "lua_asset:%d", index); @@ -200,7 +206,7 @@ void psxsplash::Lua::LoadLuaFile(const char* code, size_t len, int index) { // (1) script func (2) scripts table L.newTable(); // (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(); @@ -211,7 +217,7 @@ void psxsplash::Lua::LoadLuaFile(const char* code, size_t len, int 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) env (4) index L.copy(-2); @@ -288,35 +294,64 @@ void psxsplash::Lua::RegisterGameObject(GameObject* go) { // 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 - - // 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); + + if (go->luaFileIndex != -1 && go->luaFileIndex < m_bytecodeRefCount) { + auto& ref = m_bytecodeRefs[go->luaFileIndex]; + char filename[32]; + snprintf(filename, sizeof(filename), "lua_asset:%d", go->luaFileIndex); + + if (L.loadBuffer(ref.code, ref.len, filename) == LUA_OK) { + // (1) method_table (2) chunk_func + + // Create a per-object environment with __index = _G + // so this object's file-level locals are isolated. + L.newTable(); + L.newTable(); + L.pushGlobalTable(); + L.setField(-2, "__index"); + L.setMetatable(-2); + // (1) method_table (2) chunk_func (3) env + + // Set env as the chunk's _ENV upvalue + L.copy(-1); + // (1) method_table (2) chunk_func (3) env (4) env_copy + lua_setupvalue(L.getState(), -3, 1); + // (1) method_table (2) chunk_func (3) env + + // Move chunk to top for pcall + lua_insert(L.getState(), -2); + // (1) method_table (2) env (3) chunk_func + + if (L.pcall(0, 0) == LUA_OK) { + // (1) method_table (2) env + // resolveGlobal expects: (1) method_table, (3) env + // Insert a placeholder at position 2 to push env to position 3 + L.push(); // push nil + // (1) method_table (2) env (3) nil + lua_insert(L.getState(), 2); + // (1) method_table (2) nil (3) env + + // Resolve each event - creates fresh function refs with isolated upvalues + if (onCreateMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_CREATE; + if (onCollideWithPlayerMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_COLLISION; + if (onInteractMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_INTERACT; + if (onTriggerEnterMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_ENTER; + 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); // pop nil and env + } else { + printf("Lua error: %s\n", L.toString(-1)); + L.pop(2); // pop error msg and env + } } 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 (onCollideWithPlayerMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_COLLISION; - if (onInteractMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_INTERACT; - if (onTriggerEnterMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_ENTER; - 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) {} + printf("Lua error: %s\n", L.toString(-1)); + L.pop(); // pop error msg } } diff --git a/src/lua.h b/src/lua.h index 8935c99..632833d 100644 --- a/src/lua.h +++ b/src/lua.h @@ -179,8 +179,16 @@ class Lua { int m_metatableReference = LUA_NOREF; int m_luascriptsReference = LUA_NOREF; int m_luaSceneScriptsReference = LUA_NOREF; - - // Event mask now stored inline in GameObject::eventMask + + // Bytecode references for per-object re-execution. + // Points into splashpack data which stays in memory for the scene lifetime. + static constexpr int MAX_LUA_FILES = 32; + struct BytecodeRef { + const char* code; + size_t len; + }; + BytecodeRef m_bytecodeRefs[MAX_LUA_FILES]; + int m_bytecodeRefCount = 0; template friend struct FunctionWrapper; diff --git a/src/luaapi.cpp b/src/luaapi.cpp index 9e1b98a..a5efc83 100644 --- a/src/luaapi.cpp +++ b/src/luaapi.cpp @@ -298,6 +298,19 @@ void LuaAPI::RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cut L.setGlobal("Controls"); + // ======================================================================== + // INTERACT API + // ======================================================================== + L.newTable(); + + L.push(Interact_SetEnabled); + L.setField(-2, "SetEnabled"); + + L.push(Interact_IsEnabled); + L.setField(-2, "IsEnabled"); + + L.setGlobal("Interact"); + // ======================================================================== // UI API // ======================================================================== @@ -1563,6 +1576,49 @@ int LuaAPI::Controls_IsEnabled(lua_State* L) { return 1; } +// ============================================================================ +// INTERACT API IMPLEMENTATION +// ============================================================================ + +int LuaAPI::Interact_SetEnabled(lua_State* L) { + psyqo::Lua lua(L); + if (!s_sceneManager || !lua.isTable(1) || !lua.isBoolean(2)) return 0; + + lua.getField(1, "__cpp_ptr"); + auto go = lua.toUserdata(-1); + lua.pop(); + + if (go && go->hasInteractable()) { + auto* inter = s_sceneManager->getInteractable(go->interactableIndex); + if (inter) { + inter->setDisabled(!lua.toBoolean(2)); + } + } + return 0; +} + +int LuaAPI::Interact_IsEnabled(lua_State* L) { + psyqo::Lua lua(L); + if (!s_sceneManager || !lua.isTable(1)) { + lua.push(false); + return 1; + } + + lua.getField(1, "__cpp_ptr"); + auto go = lua.toUserdata(-1); + lua.pop(); + + if (go && go->hasInteractable()) { + auto* inter = s_sceneManager->getInteractable(go->interactableIndex); + if (inter) { + lua.push(!inter->isDisabled()); + return 1; + } + } + lua.push(false); + return 1; +} + // ============================================================================ // UI API IMPLEMENTATION // ============================================================================ diff --git a/src/luaapi.hh b/src/luaapi.hh index b4bdce8..c0cb61e 100644 --- a/src/luaapi.hh +++ b/src/luaapi.hh @@ -276,7 +276,13 @@ private: // Controls.IsEnabled() -> boolean static int Controls_IsEnabled(lua_State* L); - + + // Interact.SetEnabled(entity, bool) - enable/disable interaction + prompt for an object + static int Interact_SetEnabled(lua_State* L); + + // Interact.IsEnabled(entity) -> boolean + static int Interact_IsEnabled(lua_State* L); + // ======================================================================== // UI API - Canvas and element control // ======================================================================== diff --git a/src/scenemanager.cpp b/src/scenemanager.cpp index 27d372b..ecc931c 100644 --- a/src/scenemanager.cpp +++ b/src/scenemanager.cpp @@ -314,8 +314,14 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) { psyqo::FixedPoint<12> py = static_cast>(m_playerPosition.y); psyqo::FixedPoint<12> pz = static_cast>(m_playerPosition.z); psyqo::FixedPoint<12> h = static_cast>(m_playerHeight); - playerAABB.min = psyqo::Vec3{px - r, py - h, pz - r}; - playerAABB.max = psyqo::Vec3{px + r, py, pz + r}; + // Y is inverted on PS1: negative = up, positive = down. + // m_playerPosition.y is the camera (head), feet are at py + h. + // Leave a small gap at the bottom so the floor geometry doesn't + // trigger constant collisions (floor contact is handled by nav). + psyqo::FixedPoint<12> bodyBottom; + bodyBottom.value = h.value * 3 / 4; // 75% of height below camera + playerAABB.min = psyqo::Vec3{px - r, py, pz - r}; + playerAABB.max = psyqo::Vec3{px + r, py + bodyBottom, pz + r}; } psyqo::Vec3 pushBack; @@ -519,6 +525,7 @@ void psxsplash::SceneManager::updateInteractionSystem() { for (auto* interactable : m_interactables) { if (!interactable) continue; + if (interactable->isDisabled()) continue; auto* go = getGameObject(interactable->gameObjectIndex); if (!go || !go->isActive()) continue; @@ -853,4 +860,4 @@ int psxsplash::SceneManager::findAudioClipByName(const char* name) const { } } return -1; -} \ No newline at end of file +} diff --git a/src/scenemanager.hh b/src/scenemanager.hh index 5930866..e907995 100644 --- a/src/scenemanager.hh +++ b/src/scenemanager.hh @@ -85,6 +85,12 @@ class SceneManager { // Controls enable/disable (Lua-driven) void setControlsEnabled(bool enabled) { m_controlsEnabled = enabled; } bool isControlsEnabled() const { return m_controlsEnabled; } + + // Interactable access (for Lua API) + Interactable* getInteractable(uint16_t index) { + if (index < m_interactables.size()) return m_interactables[index]; + return nullptr; + } // Scene loading (for multi-scene support) void requestSceneLoad(int sceneIndex);