more fixes
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user