more fixes

This commit is contained in:
Jan Racek
2026-03-28 01:32:07 +01:00
parent bfab154547
commit b01b72751a
6 changed files with 158 additions and 144 deletions

View File

@@ -15,12 +15,7 @@ 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
@@ -33,22 +28,39 @@ struct Interactable {
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

View File

@@ -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;
}

View File

@@ -11,6 +11,7 @@
#include "loadingscreen.hh"
#include <psyqo/primitives/misc.hh>
#include <psyqo/trigonometry.hh>
#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;
}
if (m_playerNavRegion != NAV_NO_REGION) {
m_playerPosition.x.value = px;
m_playerPosition.z.value = pz;
// 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;
int32_t cameraAtFloor = floorY - m_playerHeight.raw();
// 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);
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);
// Tick cooldowns for all interactables
for (auto* interactable : m_interactables) {
if (interactable) interactable->update();
}
if (!buttonPressed) return; // Early out if no interaction attempt
// Player position for distance check
// Player position for distance checks
psyqo::FixedPoint<12> playerX = static_cast<psyqo::FixedPoint<12>>(m_playerPosition.x);
psyqo::FixedPoint<12> playerY = static_cast<psyqo::FixedPoint<12>>(m_playerPosition.y);
psyqo::FixedPoint<12> playerZ = static_cast<psyqo::FixedPoint<12>>(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;
if (!interactable) continue;
// Check if object is active
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<psyqo::AdvancedPad::Button>(
static_cast<uint16_t>(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);

View File

@@ -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

View File

@@ -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<uintptr_t>(cursor);
cursor = reinterpret_cast<uint8_t*>((addr + 3) & ~3);
cursor = const_cast<uint8_t*>(setup.worldCollision.initializeFromData(cursor));
// CollisionDataHeader: 20 bytes
const uint16_t meshCount = *reinterpret_cast<const uint16_t*>(cursor);
const uint16_t triCount = *reinterpret_cast<const uint16_t*>(cursor + 2);
const uint16_t chunkW = *reinterpret_cast<const uint16_t*>(cursor + 4);
const uint16_t chunkH = *reinterpret_cast<const uint16_t*>(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) {

View File

@@ -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<const char*> audioClipNames;
BVHManager bvh; // Spatial acceleration structure for culling
WorldCollision worldCollision;
NavRegionSystem navRegions;
psyqo::GTE::PackedVec3 playerStartPosition;
psyqo::GTE::PackedVec3 playerStartRotation;