From b01b72751a101b31e9343655a383fb99a5a7c8f8 Mon Sep 17 00:00:00 2001 From: Jan Racek Date: Sat, 28 Mar 2026 01:32:07 +0100 Subject: [PATCH] more fixes --- src/interactable.hh | 50 ++++++---- src/luaapi.cpp | 6 +- src/scenemanager.cpp | 219 +++++++++++++++++++++---------------------- src/scenemanager.hh | 12 +-- src/splashpack.cpp | 13 ++- src/splashpack.hh | 2 - 6 files changed, 158 insertions(+), 144 deletions(-) diff --git a/src/interactable.hh b/src/interactable.hh index d035009..1053295 100644 --- a/src/interactable.hh +++ b/src/interactable.hh @@ -7,48 +7,60 @@ namespace psxsplash { /** * Interactable component - enables player interaction with objects. - * + * * When the player is within interaction radius and presses the interact button, * the onInteract Lua event fires on the associated GameObject. */ struct Interactable { // Interaction radius squared (fixed-point 12-bit, pre-squared for fast distance checks) psyqo::FixedPoint<12> radiusSquared; - - // Interaction point offset from object center - psyqo::FixedPoint<12> offsetX; - psyqo::FixedPoint<12> offsetY; - psyqo::FixedPoint<12> offsetZ; - - // Button index that triggers interaction (0-15) + + // Button index that triggers interaction (0-15, matches psyqo::AdvancedPad::Button) uint8_t interactButton; - + // Configuration flags uint8_t flags; // bit 0: isRepeatable, bit 1: showPrompt, bit 2: requireLineOfSight - + // Cooldown between interactions (in frames) uint16_t cooldownFrames; - + // Runtime state uint16_t currentCooldown; // Frames remaining until can interact again uint16_t gameObjectIndex; // Index of associated GameObject - + + // Prompt canvas name (null-terminated, max 15 chars + null) + char promptCanvasName[16]; + // Flag accessors bool isRepeatable() const { return flags & 0x01; } bool showPrompt() const { return flags & 0x02; } bool requireLineOfSight() const { return flags & 0x04; } - + // Check if ready to interact - bool canInteract() const { return currentCooldown == 0; } - + bool canInteract() const { + // Non-repeatable interactions: once cooldown was set, it stays permanently + if (!isRepeatable() && cooldownFrames > 0 && currentCooldown == 0) { + // Check if we already triggered once (cooldownFrames acts as sentinel) + // We use a special value to mark "already used" + } + return currentCooldown == 0; + } + // Called when interaction happens - void triggerCooldown() { currentCooldown = cooldownFrames; } - + void triggerCooldown() { + if (isRepeatable()) { + currentCooldown = cooldownFrames; + } else { + // Non-repeatable: set to max to permanently disable + currentCooldown = 0xFFFF; + } + } + // Called each frame to decrement cooldown void update() { - if (currentCooldown > 0) currentCooldown--; + if (currentCooldown > 0 && currentCooldown != 0xFFFF) currentCooldown--; } }; -static_assert(sizeof(Interactable) == 24, "Interactable is not 24 bytes"); +static_assert(sizeof(Interactable) == 28, "Interactable must be 28 bytes"); } // namespace psxsplash diff --git a/src/luaapi.cpp b/src/luaapi.cpp index 78c606c..0e1c1bb 100644 --- a/src/luaapi.cpp +++ b/src/luaapi.cpp @@ -1263,9 +1263,9 @@ int LuaAPI::Audio_StopAll(lua_State* L) { 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. + if (lua.isString(1)) { + printf("%s\n", lua.toString(1)); + } return 0; } diff --git a/src/scenemanager.cpp b/src/scenemanager.cpp index 1152320..13fc356 100644 --- a/src/scenemanager.cpp +++ b/src/scenemanager.cpp @@ -11,6 +11,7 @@ #include "loadingscreen.hh" #include +#include #include "lua.h" @@ -23,6 +24,13 @@ using namespace psxsplash; // Static member definition psyqo::Font<>* psxsplash::SceneManager::s_font = nullptr; +// Default player collision radius: ~0.5 world units at GTE 100 -> 20 in 20.12 +static constexpr int32_t PLAYER_RADIUS = 20; + +// Interaction system state +static psyqo::Trig<> s_interactTrig; +static int s_activePromptCanvas = -1; // Currently shown prompt canvas index (-1 = none) + void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingScreen* loading) { auto& gpu = Renderer::GetInstance().getGPU(); @@ -47,7 +55,6 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc m_gameObjects = std::move(sceneSetup.objects); m_objectNames = std::move(sceneSetup.objectNames); m_bvh = sceneSetup.bvh; // Copy BVH for frustum culling - m_worldCollision = sceneSetup.worldCollision; // World collision soup (v7+) m_navRegions = sceneSetup.navRegions; // Nav region system (v7+) m_playerNavRegion = m_navRegions.isLoaded() ? m_navRegions.getStartRegion() : NAV_NO_REGION; @@ -406,91 +413,41 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) { #endif uint32_t navmeshStart = gpu.now(); - if (!freecam) { - if (m_worldCollision.isLoaded()) { - psyqo::Vec3 slid = m_worldCollision.moveAndSlide( - oldPlayerPosition, m_playerPosition, m_playerRadius, 0xFF); + if (!freecam && m_navRegions.isLoaded()) { + // Apply gravity + for (int f = 0; f < m_deltaFrames; f++) { + m_velocityY += m_gravityPerFrame; + } + // Apply vertical velocity to position + int32_t posYDelta = (m_velocityY * m_deltaFrames) / 30; + m_playerPosition.y.value += posYDelta; - m_playerPosition.x = slid.x; - m_playerPosition.z = slid.z; + // Resolve position via nav regions + uint16_t prevRegion = m_playerNavRegion; + int32_t px = m_playerPosition.x.value; + int32_t pz = m_playerPosition.z.value; + int32_t floorY = m_navRegions.resolvePosition( + px, pz, m_playerNavRegion); - // Apply gravity: velocity changes each frame - for (int f = 0; f < m_deltaFrames; f++) { - m_velocityY += m_gravityPerFrame; - } - - // Apply vertical velocity to position - // velocityY is in fp12 per-second; convert per-frame: pos += vel / 30 - int32_t posYDelta = (m_velocityY * m_deltaFrames) / 30; - m_playerPosition.y.value += posYDelta; + if (m_playerNavRegion != NAV_NO_REGION) { + m_playerPosition.x.value = px; + m_playerPosition.z.value = pz; - // Resolve floor Y from nav regions if available - if (m_navRegions.isLoaded()) { - uint16_t prevRegion = m_playerNavRegion; - int32_t px = m_playerPosition.x.value; - int32_t pz = m_playerPosition.z.value; - int32_t floorY = m_navRegions.resolvePosition( - px, pz, m_playerNavRegion); + int32_t cameraAtFloor = floorY - m_playerHeight.raw(); - - if (m_playerNavRegion != NAV_NO_REGION) { - m_playerPosition.x.value = px; - m_playerPosition.z.value = pz; - - // Ground (feet) position in PSX coords: - // Camera is at position.y, feet are at position.y + playerHeight - // (Y-down: larger Y = lower) - int32_t cameraAtFloor = floorY - m_playerHeight.raw(); - - if (m_playerPosition.y.value >= cameraAtFloor) { - // Player is at or below floor — snap to ground - m_playerPosition.y.value = cameraAtFloor; - m_velocityY = 0; - m_isGrounded = true; - } else { - // Player is above floor (jumping/airborne) - m_isGrounded = false; - } - } else { - // Off all nav regions — revert to old position - m_playerPosition = oldPlayerPosition; - m_playerNavRegion = prevRegion; - m_velocityY = 0; - m_isGrounded = true; - } + if (m_playerPosition.y.value >= cameraAtFloor) { + m_playerPosition.y.value = cameraAtFloor; + m_velocityY = 0; + m_isGrounded = true; } else { - // Ground trace fallback (no nav regions) - int32_t groundY; - int32_t groundNormalY; - uint8_t surfFlags; - if (m_worldCollision.groundTrace(m_playerPosition, - 4096 * 4, // max 4 units down - groundY, groundNormalY, surfFlags, 0xFF)) { - int32_t cameraAtFloor = groundY - m_playerHeight.raw(); - if (m_playerPosition.y.value >= cameraAtFloor) { - m_playerPosition.y.value = cameraAtFloor; - m_velocityY = 0; - m_isGrounded = true; - } else { - m_isGrounded = false; - } - } else { - m_playerPosition = oldPlayerPosition; - m_velocityY = 0; - m_isGrounded = true; - } - } - - // Ceiling check: if jumping upward, check for ceiling collision - if (m_velocityY < 0 && m_worldCollision.isLoaded()) { - int32_t ceilingY; - if (m_worldCollision.ceilingTrace(m_playerPosition, - m_playerHeight.raw(), ceilingY, 0xFF)) { - // Hit a ceiling — stop upward velocity - m_velocityY = 0; - } + m_isGrounded = false; } + } else { + m_playerPosition = oldPlayerPosition; + m_playerNavRegion = prevRegion; + m_velocityY = 0; + m_isGrounded = true; } } @@ -527,48 +484,88 @@ void psxsplash::SceneManager::fireTriggerExit(int16_t luaFileIndex, uint16_t tri // ============================================================================ void psxsplash::SceneManager::updateInteractionSystem() { - // Get interact button state - Cross button by default - auto interactButton = psyqo::AdvancedPad::Button::Cross; - bool buttonPressed = m_controls.wasButtonPressed(interactButton); - - if (!buttonPressed) return; // Early out if no interaction attempt - - // Player position for distance check + // Tick cooldowns for all interactables + for (auto* interactable : m_interactables) { + if (interactable) interactable->update(); + } + + // Player position for distance checks psyqo::FixedPoint<12> playerX = static_cast>(m_playerPosition.x); psyqo::FixedPoint<12> playerY = static_cast>(m_playerPosition.y); psyqo::FixedPoint<12> playerZ = static_cast>(m_playerPosition.z); - - // Find closest interactable in range - Interactable* closest = nullptr; + + // Player forward direction from Y rotation (for line-of-sight checks) + psyqo::FixedPoint<12> forwardX = s_interactTrig.sin(playerRotationY); + psyqo::FixedPoint<12> forwardZ = s_interactTrig.cos(playerRotationY); + + // First pass: find which interactable is closest and in range (for prompt display) + Interactable* inRange = nullptr; psyqo::FixedPoint<12> closestDistSq; - closestDistSq.value = 0x7FFFFFFF; // Max positive value - + closestDistSq.value = 0x7FFFFFFF; + for (auto* interactable : m_interactables) { - if (!interactable || !interactable->canInteract()) continue; - - // Check if object is active + if (!interactable) continue; + auto* go = getGameObject(interactable->gameObjectIndex); if (!go || !go->isActive()) continue; - - // Calculate distance squared - psyqo::FixedPoint<12> dx = playerX - interactable->offsetX - go->position.x; - psyqo::FixedPoint<12> dy = playerY - interactable->offsetY - go->position.y; - psyqo::FixedPoint<12> dz = playerZ - interactable->offsetZ - go->position.z; - + + // Distance check + psyqo::FixedPoint<12> dx = playerX - go->position.x; + psyqo::FixedPoint<12> dy = playerY - go->position.y; + psyqo::FixedPoint<12> dz = playerZ - go->position.z; psyqo::FixedPoint<12> distSq = dx * dx + dy * dy + dz * dz; - - // Check if in range and closer than current closest - if (distSq <= interactable->radiusSquared && distSq < closestDistSq) { - closest = interactable; + + if (distSq > interactable->radiusSquared) continue; + + // Line-of-sight check: dot product of forward vector and direction to object + if (interactable->requireLineOfSight()) { + // dot = forwardX * dx + forwardZ * dz (XZ plane only) + // Negative dot means object is behind the player + psyqo::FixedPoint<12> dot = forwardX * dx + forwardZ * dz; + // Object must be in front of the player (dot < 0 in the coordinate system + // because dx points FROM player TO object, and forward points where player faces) + // Actually: dx = playerX - objX, so it points FROM object TO player. + // We want the object in front, so we need -dx direction to align with forward. + // dot(forward, objDir) where objDir = obj - player = -dx, -dz + psyqo::FixedPoint<12> facingDot = -(forwardX * dx + forwardZ * dz); + if (facingDot.value <= 0) continue; // Object is behind the player + } + + if (distSq < closestDistSq) { + inRange = interactable; closestDistSq = distSq; } } - - // Interact with closest - if (closest != nullptr) { - triggerInteraction(getGameObject(closest->gameObjectIndex)); - closest->triggerCooldown(); + + // Prompt canvas management: show only when in range AND can interact + int newPromptCanvas = -1; + if (inRange && inRange->canInteract() && inRange->showPrompt() && inRange->promptCanvasName[0] != '\0') { + newPromptCanvas = m_uiSystem.findCanvas(inRange->promptCanvasName); } + + if (newPromptCanvas != s_activePromptCanvas) { + // Hide old prompt + if (s_activePromptCanvas >= 0) { + m_uiSystem.setCanvasVisible(s_activePromptCanvas, false); + } + // Show new prompt + if (newPromptCanvas >= 0) { + m_uiSystem.setCanvasVisible(newPromptCanvas, true); + } + s_activePromptCanvas = newPromptCanvas; + } + + // Check if the closest in-range interactable can be activated + if (!inRange || !inRange->canInteract()) return; + + // Check if the correct button for this interactable was pressed + auto button = static_cast( + static_cast(inRange->interactButton)); + if (!m_controls.wasButtonPressed(button)) return; + + // Trigger the interaction + triggerInteraction(getGameObject(inRange->gameObjectIndex)); + inRange->triggerCooldown(); } void psxsplash::SceneManager::triggerInteraction(GameObject* interactable) { @@ -728,8 +725,9 @@ void psxsplash::SceneManager::clearScene() { m_audio.reset(); // Free SPU RAM and stop all voices m_collisionSystem.init(); // Re-init collision system m_cutsceneCount = 0; + s_activePromptCanvas = -1; // Reset prompt tracking m_cutscenePlayer.init(nullptr, 0, nullptr, nullptr); // Reset cutscene player - // BVH, WorldCollision, and NavRegions will be overwritten by next load + // BVH and NavRegions will be overwritten by next load // Reset UI system (disconnect from renderer before splashpack data disappears) Renderer::GetInstance().SetUISystem(nullptr); @@ -774,7 +772,6 @@ void psxsplash::SceneManager::shrinkBuffer() { for (auto& inter : m_interactables) inter = reloc(inter); m_bvh.relocate(delta); - m_worldCollision.relocate(delta); m_navRegions.relocate(delta); m_rooms = reloc(m_rooms); diff --git a/src/scenemanager.hh b/src/scenemanager.hh index 69e9503..82d52d4 100644 --- a/src/scenemanager.hh +++ b/src/scenemanager.hh @@ -13,7 +13,6 @@ #include "gameobject.hh" #include "lua.h" #include "splashpack.hh" -#include "worldcollision.hh" #include "navregion.hh" #include "audiomanager.hh" #include "interactable.hh" @@ -104,7 +103,6 @@ class SceneManager { psxsplash::SplashPackLoader m_loader; CollisionSystem m_collisionSystem; BVHManager m_bvh; // Spatial acceleration for frustum culling - WorldCollision m_worldCollision; // Triangle-level world collision (v7+) NavRegionSystem m_navRegions; // Convex region navigation (v7+) uint16_t m_playerNavRegion = NAV_NO_REGION; // Current nav region for player @@ -153,11 +151,11 @@ class SceneManager { psyqo::FixedPoint<12, uint16_t> m_playerHeight; - int32_t m_playerRadius; // Collision radius in fp12 (replaces hardcoded PLAYER_RADIUS) - int32_t m_velocityY; // Vertical velocity in fp12 per second (negative = up) - int32_t m_gravityPerFrame; // Gravity velocity change per frame (fp12) - int32_t m_jumpVelocityRaw; // Initial jump velocity in fp12 per second - bool m_isGrounded; // On the ground (can jump) + int32_t m_playerRadius; + int32_t m_velocityY; + int32_t m_gravityPerFrame; + int32_t m_jumpVelocityRaw; + bool m_isGrounded; // Frame timing uint32_t m_lastFrameTime; // gpu.now() timestamp of previous frame diff --git a/src/splashpack.cpp b/src/splashpack.cpp index 1d4664d..ce95cea 100644 --- a/src/splashpack.cpp +++ b/src/splashpack.cpp @@ -13,7 +13,6 @@ #include "lua.h" #include "mesh.hh" #include "streq.hh" -#include "worldcollision.hh" #include "navregion.hh" #include "renderer.hh" @@ -151,10 +150,20 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup cursor += sizeof(psxsplash::Interactable); } + // Skip over legacy world collision data if present in older binaries if (header->worldCollisionMeshCount > 0) { uintptr_t addr = reinterpret_cast(cursor); cursor = reinterpret_cast((addr + 3) & ~3); - cursor = const_cast(setup.worldCollision.initializeFromData(cursor)); + // CollisionDataHeader: 20 bytes + const uint16_t meshCount = *reinterpret_cast(cursor); + const uint16_t triCount = *reinterpret_cast(cursor + 2); + const uint16_t chunkW = *reinterpret_cast(cursor + 4); + const uint16_t chunkH = *reinterpret_cast(cursor + 6); + cursor += 20; // CollisionDataHeader + cursor += meshCount * 32; // CollisionMeshHeader (32 bytes each) + cursor += triCount * 52; // CollisionTri (52 bytes each) + if (chunkW > 0 && chunkH > 0) + cursor += chunkW * chunkH * 4; // CollisionChunk (4 bytes each) } if (header->navRegionCount > 0) { diff --git a/src/splashpack.hh b/src/splashpack.hh index d906697..8772537 100644 --- a/src/splashpack.hh +++ b/src/splashpack.hh @@ -8,7 +8,6 @@ #include "collision.hh" #include "gameobject.hh" #include "lua.h" -#include "worldcollision.hh" #include "navregion.hh" #include "audiomanager.hh" #include "interactable.hh" @@ -66,7 +65,6 @@ struct SplashpackSceneSetup { eastl::vector audioClipNames; BVHManager bvh; // Spatial acceleration structure for culling - WorldCollision worldCollision; NavRegionSystem navRegions; psyqo::GTE::PackedVec3 playerStartPosition; psyqo::GTE::PackedVec3 playerStartRotation;