Camera API, improved cutscene API
This commit is contained in:
@@ -10,18 +10,21 @@ namespace psxsplash {
|
||||
|
||||
void CutscenePlayer::init(Cutscene* cutscenes, int count, Camera* camera, AudioManager* audio,
|
||||
UISystem* uiSystem) {
|
||||
m_cutscenes = cutscenes;
|
||||
m_count = count;
|
||||
m_active = nullptr;
|
||||
m_frame = 0;
|
||||
m_nextAudio = 0;
|
||||
m_camera = camera;
|
||||
m_audio = audio;
|
||||
m_uiSystem = uiSystem;
|
||||
m_cutscenes = cutscenes;
|
||||
m_count = count;
|
||||
m_active = nullptr;
|
||||
m_frame = 0;
|
||||
m_nextAudio = 0;
|
||||
m_loop = false;
|
||||
m_camera = camera;
|
||||
m_audio = audio;
|
||||
m_uiSystem = uiSystem;
|
||||
m_onCompleteRef = LUA_NOREF;
|
||||
}
|
||||
|
||||
bool CutscenePlayer::play(const char* name) {
|
||||
bool CutscenePlayer::play(const char* name, bool loop) {
|
||||
if (!name || !m_cutscenes) return false;
|
||||
m_loop = loop;
|
||||
|
||||
for (int i = 0; i < m_count; i++) {
|
||||
if (m_cutscenes[i].name && streq(m_cutscenes[i].name, name)) {
|
||||
@@ -106,7 +109,9 @@ bool CutscenePlayer::play(const char* name) {
|
||||
}
|
||||
|
||||
void CutscenePlayer::stop() {
|
||||
if (!m_active) return;
|
||||
m_active = nullptr;
|
||||
fireOnComplete();
|
||||
}
|
||||
|
||||
void CutscenePlayer::tick() {
|
||||
@@ -130,10 +135,34 @@ void CutscenePlayer::tick() {
|
||||
|
||||
m_frame++;
|
||||
if (m_frame > m_active->totalFrames) {
|
||||
m_active = nullptr; // Cutscene finished
|
||||
if (m_loop) {
|
||||
// Restart from the beginning
|
||||
m_frame = 0;
|
||||
m_nextAudio = 0;
|
||||
} else {
|
||||
m_active = nullptr; // Cutscene finished
|
||||
fireOnComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CutscenePlayer::fireOnComplete() {
|
||||
if (m_onCompleteRef == LUA_NOREF || !m_luaState) return;
|
||||
psyqo::Lua L(m_luaState);
|
||||
L.rawGetI(LUA_REGISTRYINDEX, m_onCompleteRef);
|
||||
if (L.isFunction(-1)) {
|
||||
if (L.pcall(0, 0) != LUA_OK) {
|
||||
printf("Cutscene onComplete error: %s\n", L.optString(-1, "unknown"));
|
||||
L.pop();
|
||||
}
|
||||
} else {
|
||||
L.pop();
|
||||
}
|
||||
// Unreference the callback (one-shot)
|
||||
luaL_unref(m_luaState, LUA_REGISTRYINDEX, m_onCompleteRef);
|
||||
m_onCompleteRef = LUA_NOREF;
|
||||
}
|
||||
|
||||
static int32_t applyCurve(int32_t t, InterpMode mode) {
|
||||
switch (mode) {
|
||||
default:
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "gameobject.hh"
|
||||
#include "audiomanager.hh"
|
||||
|
||||
#include <psyqo-lua/lua.hh>
|
||||
|
||||
namespace psxsplash {
|
||||
|
||||
class UISystem; // Forward declaration
|
||||
@@ -86,7 +88,8 @@ public:
|
||||
UISystem* uiSystem = nullptr);
|
||||
|
||||
/// Play cutscene by name. Returns false if not found.
|
||||
bool play(const char* name);
|
||||
/// If loop is true, the cutscene replays from the start when it ends.
|
||||
bool play(const char* name, bool loop = false);
|
||||
|
||||
/// Stop the current cutscene immediately.
|
||||
void stop();
|
||||
@@ -94,6 +97,15 @@ public:
|
||||
/// True if a cutscene is currently active.
|
||||
bool isPlaying() const { return m_active != nullptr; }
|
||||
|
||||
/// Set a Lua registry reference to call when the cutscene finishes.
|
||||
/// Pass LUA_NOREF to clear. The callback is called ONCE when the
|
||||
/// cutscene ends (not on each loop iteration - only when it truly stops).
|
||||
void setOnCompleteRef(int ref) { m_onCompleteRef = ref; }
|
||||
int getOnCompleteRef() const { return m_onCompleteRef; }
|
||||
|
||||
/// Set the lua_State for callbacks. Must be called before play().
|
||||
void setLuaState(lua_State* L) { m_luaState = L; }
|
||||
|
||||
/// Advance one frame. Call once per frame. Does nothing when idle.
|
||||
void tick();
|
||||
|
||||
@@ -103,15 +115,19 @@ private:
|
||||
Cutscene* m_active = nullptr;
|
||||
uint16_t m_frame = 0;
|
||||
uint8_t m_nextAudio = 0;
|
||||
bool m_loop = false;
|
||||
Camera* m_camera = nullptr;
|
||||
AudioManager* m_audio = nullptr;
|
||||
UISystem* m_uiSystem = nullptr;
|
||||
lua_State* m_luaState = nullptr;
|
||||
int m_onCompleteRef = LUA_NOREF;
|
||||
|
||||
psyqo::Trig<> m_trig;
|
||||
|
||||
void applyTrack(CutsceneTrack& track);
|
||||
void lerpKeyframes(CutsceneKeyframe* kf, uint8_t count, const int16_t initial[3], int16_t out[3]);
|
||||
void lerpAngles(CutsceneKeyframe* kf, uint8_t count, const int16_t initial[3], int16_t out[3]);
|
||||
void fireOnComplete();
|
||||
};
|
||||
|
||||
} // namespace psxsplash
|
||||
|
||||
@@ -285,6 +285,19 @@ void LuaAPI::RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cut
|
||||
|
||||
L.setGlobal("Cutscene");
|
||||
|
||||
// ========================================================================
|
||||
// CONTROLS API
|
||||
// ========================================================================
|
||||
L.newTable();
|
||||
|
||||
L.push(Controls_SetEnabled);
|
||||
L.setField(-2, "SetEnabled");
|
||||
|
||||
L.push(Controls_IsEnabled);
|
||||
L.setField(-2, "IsEnabled");
|
||||
|
||||
L.setGlobal("Controls");
|
||||
|
||||
// ========================================================================
|
||||
// UI API
|
||||
// ========================================================================
|
||||
@@ -1479,7 +1492,32 @@ int LuaAPI::Cutscene_Play(lua_State* L) {
|
||||
}
|
||||
|
||||
const char* name = lua.toString(1);
|
||||
s_cutscenePlayer->play(name);
|
||||
bool loop = false;
|
||||
int onCompleteRef = LUA_NOREF;
|
||||
|
||||
// Optional second argument: options table {loop=bool, onComplete=function}
|
||||
if (lua.isTable(2)) {
|
||||
lua.getField(2, "loop");
|
||||
if (lua.isBoolean(-1)) loop = lua.toBoolean(-1);
|
||||
lua.pop();
|
||||
|
||||
lua.getField(2, "onComplete");
|
||||
if (lua.isFunction(-1)) {
|
||||
onCompleteRef = lua.ref(); // pops and stores in registry
|
||||
} else {
|
||||
lua.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear any previous callback before starting a new cutscene
|
||||
int oldRef = s_cutscenePlayer->getOnCompleteRef();
|
||||
if (oldRef != LUA_NOREF) {
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, oldRef);
|
||||
}
|
||||
|
||||
s_cutscenePlayer->setLuaState(L);
|
||||
s_cutscenePlayer->setOnCompleteRef(onCompleteRef);
|
||||
s_cutscenePlayer->play(name, loop);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1503,6 +1541,28 @@ int LuaAPI::Cutscene_IsPlaying(lua_State* L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CONTROLS API IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
int LuaAPI::Controls_SetEnabled(lua_State* L) {
|
||||
psyqo::Lua lua(L);
|
||||
if (s_sceneManager && lua.isBoolean(1)) {
|
||||
s_sceneManager->setControlsEnabled(lua.toBoolean(1));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LuaAPI::Controls_IsEnabled(lua_State* L) {
|
||||
psyqo::Lua lua(L);
|
||||
if (s_sceneManager) {
|
||||
lua.push(s_sceneManager->isControlsEnabled());
|
||||
} else {
|
||||
lua.push(false);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// UI API IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
@@ -262,7 +262,7 @@ private:
|
||||
// CUTSCENE API - Cutscene playback control
|
||||
// ========================================================================
|
||||
|
||||
// Cutscene.Play(name) -> nil
|
||||
// Cutscene.Play(name) or Cutscene.Play(name, {loop=bool, onComplete=fn})
|
||||
static int Cutscene_Play(lua_State* L);
|
||||
|
||||
// Cutscene.Stop() -> nil
|
||||
@@ -271,6 +271,12 @@ private:
|
||||
// Cutscene.IsPlaying() -> boolean
|
||||
static int Cutscene_IsPlaying(lua_State* L);
|
||||
|
||||
// Controls.SetEnabled(bool) - enable/disable all player input
|
||||
static int Controls_SetEnabled(lua_State* L);
|
||||
|
||||
// Controls.IsEnabled() -> boolean
|
||||
static int Controls_IsEnabled(lua_State* L);
|
||||
|
||||
// ========================================================================
|
||||
// UI API - Canvas and element control
|
||||
// ========================================================================
|
||||
|
||||
@@ -62,6 +62,11 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
|
||||
m_navRegions = sceneSetup.navRegions; // Nav region system (v7+)
|
||||
m_playerNavRegion = m_navRegions.isLoaded() ? m_navRegions.getStartRegion() : NAV_NO_REGION;
|
||||
|
||||
// If nav regions are loaded, camera follows the player. Otherwise the
|
||||
// scene is in "free camera" mode where cutscenes and Lua drive the camera.
|
||||
m_cameraFollowsPlayer = m_navRegions.isLoaded();
|
||||
m_controlsEnabled = true;
|
||||
|
||||
// Scene type and render path
|
||||
m_sceneType = sceneSetup.sceneType;
|
||||
|
||||
@@ -397,12 +402,14 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
|
||||
// Save position BEFORE movement for collision detection
|
||||
psyqo::Vec3 oldPlayerPosition = m_playerPosition;
|
||||
|
||||
m_controls.HandleControls(m_playerPosition, playerRotationX, playerRotationY, playerRotationZ, freecam, m_deltaFrames);
|
||||
if (m_controlsEnabled) {
|
||||
m_controls.HandleControls(m_playerPosition, playerRotationX, playerRotationY, playerRotationZ, freecam, m_deltaFrames);
|
||||
|
||||
// Jump input: Cross button triggers jump when grounded
|
||||
if (m_isGrounded && m_controls.wasButtonPressed(psyqo::AdvancedPad::Button::Cross)) {
|
||||
m_velocityY = -m_jumpVelocityRaw; // Negative = upward (PSX Y-down)
|
||||
m_isGrounded = false;
|
||||
// Jump input: Cross button triggers jump when grounded
|
||||
if (m_isGrounded && m_controls.wasButtonPressed(psyqo::AdvancedPad::Button::Cross)) {
|
||||
m_velocityY = -m_jumpVelocityRaw; // Negative = upward (PSX Y-down)
|
||||
m_isGrounded = false;
|
||||
}
|
||||
}
|
||||
|
||||
gpu.pumpCallbacks();
|
||||
@@ -460,10 +467,17 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
|
||||
psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_NAVMESH, navmeshTime);
|
||||
#endif
|
||||
|
||||
m_currentCamera.SetPosition(static_cast<psyqo::FixedPoint<12>>(m_playerPosition.x),
|
||||
static_cast<psyqo::FixedPoint<12>>(m_playerPosition.y),
|
||||
static_cast<psyqo::FixedPoint<12>>(m_playerPosition.z));
|
||||
m_currentCamera.SetRotation(playerRotationX, playerRotationY, playerRotationZ);
|
||||
// Only snap camera to player when in player-follow mode and no
|
||||
// cutscene is actively controlling the camera. In free camera mode
|
||||
// (no nav regions / no PSXPlayer), the camera is driven entirely
|
||||
// by cutscenes and Lua. After a cutscene ends in free mode, the
|
||||
// camera stays at the last cutscene position.
|
||||
if (m_cameraFollowsPlayer && !m_cutscenePlayer.isPlaying()) {
|
||||
m_currentCamera.SetPosition(static_cast<psyqo::FixedPoint<12>>(m_playerPosition.x),
|
||||
static_cast<psyqo::FixedPoint<12>>(m_playerPosition.y),
|
||||
static_cast<psyqo::FixedPoint<12>>(m_playerPosition.z));
|
||||
m_currentCamera.SetRotation(playerRotationX, playerRotationY, playerRotationZ);
|
||||
}
|
||||
|
||||
// Process pending scene transitions (at end of frame)
|
||||
processPendingSceneLoad();
|
||||
|
||||
@@ -82,6 +82,10 @@ class SceneManager {
|
||||
Lua& getLua() { return L; }
|
||||
AudioManager& getAudio() { return m_audio; }
|
||||
|
||||
// Controls enable/disable (Lua-driven)
|
||||
void setControlsEnabled(bool enabled) { m_controlsEnabled = enabled; }
|
||||
bool isControlsEnabled() const { return m_controlsEnabled; }
|
||||
|
||||
// Scene loading (for multi-scene support)
|
||||
void requestSceneLoad(int sceneIndex);
|
||||
int getCurrentSceneIndex() const { return m_currentSceneIndex; }
|
||||
@@ -162,6 +166,8 @@ class SceneManager {
|
||||
int m_deltaFrames; // Elapsed frame count (1 normally, 2+ if dropped)
|
||||
|
||||
bool freecam = false;
|
||||
bool m_controlsEnabled = true; // Lua can disable all player input
|
||||
bool m_cameraFollowsPlayer = true; // False when scene has no nav regions (freecam/cutscene mode)
|
||||
|
||||
// Static font pointer (set from main.cpp)
|
||||
static psyqo::Font<>* s_font;
|
||||
|
||||
Reference in New Issue
Block a user