Files
secretpsxsplash/src/cutscene.hh
2026-03-29 16:14:15 +02:00

134 lines
4.0 KiB
C++

#pragma once
#include <stdint.h>
#include <psyqo/fixed-point.hh>
#include <psyqo/trigonometry.hh>
#include <psyqo/soft-math.hh>
#include "camera.hh"
#include "gameobject.hh"
#include "audiomanager.hh"
#include <psyqo-lua/lua.hh>
namespace psxsplash {
class UISystem; // Forward declaration
static constexpr int MAX_CUTSCENES = 16;
static constexpr int MAX_TRACKS = 8;
static constexpr int MAX_KEYFRAMES = 64;
static constexpr int MAX_AUDIO_EVENTS = 64;
enum class TrackType : uint8_t {
CameraPosition = 0,
CameraRotation = 1,
ObjectPosition = 2,
ObjectRotation = 3,
ObjectActive = 4,
UICanvasVisible = 5,
UIElementVisible= 6,
UIProgress = 7,
UIPosition = 8,
UIColor = 9,
};
enum class InterpMode : uint8_t {
Linear = 0,
Step = 1,
EaseIn = 2,
EaseOut = 3,
EaseInOut = 4,
};
struct CutsceneKeyframe {
// Upper 3 bits = InterpMode (0-7), lower 13 bits = frame number (0-8191).
// At 30fps, max frame 8191 ≈ 4.5 minutes.
uint16_t frameAndInterp;
int16_t values[3];
uint16_t getFrame() const { return frameAndInterp & 0x1FFF; }
InterpMode getInterp() const { return static_cast<InterpMode>(frameAndInterp >> 13); }
};
static_assert(sizeof(CutsceneKeyframe) == 8, "CutsceneKeyframe must be 8 bytes");
struct CutsceneAudioEvent {
uint16_t frame;
uint8_t clipIndex;
uint8_t volume;
uint8_t pan;
uint8_t pad[3];
};
static_assert(sizeof(CutsceneAudioEvent) == 8, "CutsceneAudioEvent must be 8 bytes");
struct CutsceneTrack {
TrackType trackType;
uint8_t keyframeCount;
uint8_t pad[2];
CutsceneKeyframe* keyframes;
GameObject* target;
int16_t uiHandle;
int16_t initialValues[3];
};
struct Cutscene {
const char* name; // Points into splashpack data
uint16_t totalFrames;
uint8_t trackCount;
uint8_t audioEventCount;
CutsceneTrack tracks[MAX_TRACKS];
CutsceneAudioEvent* audioEvents; // Points into splashpack data
};
class CutscenePlayer {
public:
/// Initialize with loaded cutscene data. Safe to pass nullptr/0 if no cutscenes.
void init(Cutscene* cutscenes, int count, Camera* camera, AudioManager* audio,
UISystem* uiSystem = nullptr);
/// Play cutscene by name. Returns false if not found.
/// 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();
/// 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();
private:
Cutscene* m_cutscenes = nullptr;
int m_count = 0;
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