This commit is contained in:
Jan Racek
2026-03-29 18:41:58 +02:00
parent 9a82f9e3a1
commit 428725d9ce
16 changed files with 1173 additions and 371 deletions

View File

@@ -20,6 +20,8 @@ src/profiler.cpp \
src/collision.cpp \ src/collision.cpp \
src/bvh.cpp \ src/bvh.cpp \
src/cutscene.cpp \ src/cutscene.cpp \
src/interpolation.cpp \
src/animation.cpp \
src/uisystem.cpp \ src/uisystem.cpp \
src/loadingscreen.cpp \ src/loadingscreen.cpp \
src/memoverlay.cpp \ src/memoverlay.cpp \

297
src/animation.cpp Normal file
View File

@@ -0,0 +1,297 @@
#include "animation.hh"
#include "interpolation.hh"
#include <psyqo/fixed-point.hh>
#include <psyqo/soft-math.hh>
#include "streq.hh"
#include "uisystem.hh"
namespace psxsplash {
void AnimationPlayer::init(Animation* animations, int count, UISystem* uiSystem) {
m_animations = animations;
m_animCount = count;
m_uiSystem = uiSystem;
for (int i = 0; i < MAX_SIMULTANEOUS_ANIMS; i++) {
m_slots[i].anim = nullptr;
m_slots[i].onCompleteRef = LUA_NOREF;
}
}
Animation* AnimationPlayer::findByName(const char* name) const {
if (!name || !m_animations) return nullptr;
for (int i = 0; i < m_animCount; i++) {
if (m_animations[i].name && streq(m_animations[i].name, name))
return &m_animations[i];
}
return nullptr;
}
bool AnimationPlayer::play(const char* name, bool loop) {
Animation* anim = findByName(name);
if (!anim) return false;
// Find a free slot
int freeSlot = -1;
for (int i = 0; i < MAX_SIMULTANEOUS_ANIMS; i++) {
if (!m_slots[i].anim) {
freeSlot = i;
break;
}
}
if (freeSlot < 0) return false;
ActiveSlot& slot = m_slots[freeSlot];
slot.anim = anim;
slot.frame = 0;
slot.loop = loop;
// onCompleteRef is set separately via setOnCompleteRef before play()
captureInitialValues(anim);
return true;
}
void AnimationPlayer::stop(const char* name) {
if (!name) return;
for (int i = 0; i < MAX_SIMULTANEOUS_ANIMS; i++) {
if (m_slots[i].anim && m_slots[i].anim->name && streq(m_slots[i].anim->name, name)) {
fireSlotComplete(m_slots[i]);
m_slots[i].anim = nullptr;
}
}
}
void AnimationPlayer::stopAll() {
for (int i = 0; i < MAX_SIMULTANEOUS_ANIMS; i++) {
if (m_slots[i].anim) {
fireSlotComplete(m_slots[i]);
m_slots[i].anim = nullptr;
}
}
}
bool AnimationPlayer::isPlaying(const char* name) const {
if (!name) return false;
for (int i = 0; i < MAX_SIMULTANEOUS_ANIMS; i++) {
if (m_slots[i].anim && m_slots[i].anim->name && streq(m_slots[i].anim->name, name))
return true;
}
return false;
}
void AnimationPlayer::setOnCompleteRef(const char* name, int ref) {
// Find the most recently started slot for this animation (highest index with frame 0)
// Fallback: find first slot with this name
for (int i = MAX_SIMULTANEOUS_ANIMS - 1; i >= 0; i--) {
if (m_slots[i].anim && m_slots[i].anim->name && streq(m_slots[i].anim->name, name)) {
m_slots[i].onCompleteRef = ref;
return;
}
}
}
void AnimationPlayer::tick() {
for (int i = 0; i < MAX_SIMULTANEOUS_ANIMS; i++) {
ActiveSlot& slot = m_slots[i];
if (!slot.anim) continue;
// Apply all tracks
for (uint8_t ti = 0; ti < slot.anim->trackCount; ti++) {
applyTrack(slot.anim->tracks[ti], slot.frame);
}
slot.frame++;
if (slot.frame > slot.anim->totalFrames) {
if (slot.loop) {
slot.frame = 0;
} else {
Animation* finished = slot.anim;
slot.anim = nullptr;
fireSlotComplete(slot);
(void)finished;
}
}
}
}
void AnimationPlayer::captureInitialValues(Animation* anim) {
for (uint8_t ti = 0; ti < anim->trackCount; ti++) {
CutsceneTrack& track = anim->tracks[ti];
track.initialValues[0] = track.initialValues[1] = track.initialValues[2] = 0;
switch (track.trackType) {
case TrackType::ObjectPosition:
if (track.target) {
track.initialValues[0] = (int16_t)track.target->position.x.value;
track.initialValues[1] = (int16_t)track.target->position.y.value;
track.initialValues[2] = (int16_t)track.target->position.z.value;
}
break;
case TrackType::ObjectRotation:
break;
case TrackType::ObjectActive:
if (track.target) {
track.initialValues[0] = track.target->isActive() ? 1 : 0;
}
break;
case TrackType::UICanvasVisible:
if (m_uiSystem) {
track.initialValues[0] = m_uiSystem->isCanvasVisible(track.uiHandle) ? 1 : 0;
}
break;
case TrackType::UIElementVisible:
if (m_uiSystem) {
track.initialValues[0] = m_uiSystem->isElementVisible(track.uiHandle) ? 1 : 0;
}
break;
case TrackType::UIProgress:
if (m_uiSystem) {
track.initialValues[0] = m_uiSystem->getProgress(track.uiHandle);
}
break;
case TrackType::UIPosition:
if (m_uiSystem) {
int16_t px, py;
m_uiSystem->getPosition(track.uiHandle, px, py);
track.initialValues[0] = px;
track.initialValues[1] = py;
}
break;
case TrackType::UIColor:
if (m_uiSystem) {
uint8_t cr, cg, cb;
m_uiSystem->getColor(track.uiHandle, cr, cg, cb);
track.initialValues[0] = cr;
track.initialValues[1] = cg;
track.initialValues[2] = cb;
}
break;
default:
break;
}
}
}
void AnimationPlayer::applyTrack(CutsceneTrack& track, uint16_t frame) {
if (track.keyframeCount == 0 || !track.keyframes) return;
int16_t out[3];
switch (track.trackType) {
case TrackType::ObjectPosition: {
if (!track.target) return;
psxsplash::lerpKeyframes(track.keyframes, track.keyframeCount, frame, track.initialValues, out);
track.target->position.x.value = (int32_t)out[0];
track.target->position.y.value = (int32_t)out[1];
track.target->position.z.value = (int32_t)out[2];
break;
}
case TrackType::ObjectRotation: {
if (!track.target) return;
psxsplash::lerpKeyframes(track.keyframes, track.keyframeCount, frame, track.initialValues, out);
psyqo::Angle rx, ry, rz;
rx.value = (int32_t)out[0];
ry.value = (int32_t)out[1];
rz.value = (int32_t)out[2];
auto matY = psyqo::SoftMath::generateRotationMatrix33(ry, psyqo::SoftMath::Axis::Y, m_trig);
auto matX = psyqo::SoftMath::generateRotationMatrix33(rx, psyqo::SoftMath::Axis::X, m_trig);
auto matZ = psyqo::SoftMath::generateRotationMatrix33(rz, psyqo::SoftMath::Axis::Z, m_trig);
auto temp = psyqo::SoftMath::multiplyMatrix33(matY, matX);
track.target->rotation = psyqo::SoftMath::multiplyMatrix33(temp, matZ);
break;
}
case TrackType::ObjectActive: {
if (!track.target) return;
CutsceneKeyframe* kf = track.keyframes;
uint8_t count = track.keyframeCount;
int16_t activeVal = (count > 0 && frame < kf[0].getFrame())
? track.initialValues[0]
: kf[0].values[0];
for (uint8_t i = 0; i < count; i++) {
if (kf[i].getFrame() <= frame) {
activeVal = kf[i].values[0];
} else {
break;
}
}
track.target->setActive(activeVal != 0);
break;
}
case TrackType::UICanvasVisible: {
if (!m_uiSystem) return;
CutsceneKeyframe* kf = track.keyframes;
uint8_t count = track.keyframeCount;
int16_t val = (count > 0 && frame < kf[0].getFrame())
? track.initialValues[0] : kf[0].values[0];
for (uint8_t i = 0; i < count; i++) {
if (kf[i].getFrame() <= frame) val = kf[i].values[0];
else break;
}
m_uiSystem->setCanvasVisible(track.uiHandle, val != 0);
break;
}
case TrackType::UIElementVisible: {
if (!m_uiSystem) return;
CutsceneKeyframe* kf = track.keyframes;
uint8_t count = track.keyframeCount;
int16_t val = (count > 0 && frame < kf[0].getFrame())
? track.initialValues[0] : kf[0].values[0];
for (uint8_t i = 0; i < count; i++) {
if (kf[i].getFrame() <= frame) val = kf[i].values[0];
else break;
}
m_uiSystem->setElementVisible(track.uiHandle, val != 0);
break;
}
case TrackType::UIProgress: {
if (!m_uiSystem) return;
psxsplash::lerpKeyframes(track.keyframes, track.keyframeCount, frame, track.initialValues, out);
int16_t v = out[0];
if (v < 0) v = 0;
if (v > 100) v = 100;
m_uiSystem->setProgress(track.uiHandle, (uint8_t)v);
break;
}
case TrackType::UIPosition: {
if (!m_uiSystem) return;
psxsplash::lerpKeyframes(track.keyframes, track.keyframeCount, frame, track.initialValues, out);
m_uiSystem->setPosition(track.uiHandle, out[0], out[1]);
break;
}
case TrackType::UIColor: {
if (!m_uiSystem) return;
psxsplash::lerpKeyframes(track.keyframes, track.keyframeCount, frame, track.initialValues, out);
uint8_t cr = (out[0] < 0) ? 0 : ((out[0] > 255) ? 255 : (uint8_t)out[0]);
uint8_t cg = (out[1] < 0) ? 0 : ((out[1] > 255) ? 255 : (uint8_t)out[1]);
uint8_t cb = (out[2] < 0) ? 0 : ((out[2] > 255) ? 255 : (uint8_t)out[2]);
m_uiSystem->setColor(track.uiHandle, cr, cg, cb);
break;
}
default:
break;
}
}
void AnimationPlayer::fireSlotComplete(ActiveSlot& slot) {
if (slot.onCompleteRef == LUA_NOREF || !m_luaState) return;
psyqo::Lua L(m_luaState);
L.rawGetI(LUA_REGISTRYINDEX, slot.onCompleteRef);
if (L.isFunction(-1)) {
if (L.pcall(0, 0) != LUA_OK) {
L.pop();
}
} else {
L.pop();
}
luaL_unref(m_luaState, LUA_REGISTRYINDEX, slot.onCompleteRef);
slot.onCompleteRef = LUA_NOREF;
}
} // namespace psxsplash

71
src/animation.hh Normal file
View File

@@ -0,0 +1,71 @@
#pragma once
#include <stdint.h>
#include <psyqo/trigonometry.hh>
#include "cutscene.hh"
#include <psyqo-lua/lua.hh>
namespace psxsplash {
class UISystem;
static constexpr int MAX_ANIMATIONS = 16;
static constexpr int MAX_ANIM_TRACKS = 8;
static constexpr int MAX_SIMULTANEOUS_ANIMS = 8;
struct Animation {
const char* name;
uint16_t totalFrames;
uint8_t trackCount;
uint8_t pad;
CutsceneTrack tracks[MAX_ANIM_TRACKS];
};
class AnimationPlayer {
public:
void init(Animation* animations, int count, UISystem* uiSystem = nullptr);
/// Play animation by name. Returns false if not found or no free slots.
bool play(const char* name, bool loop = false);
/// Stop all instances of animation by name.
void stop(const char* name);
/// Stop all running animations.
void stopAll();
/// True if any instance of the named animation is playing.
bool isPlaying(const char* name) const;
/// Set a Lua callback for the next play() call.
void setOnCompleteRef(const char* name, int ref);
/// Set the lua_State for callbacks.
void setLuaState(lua_State* L) { m_luaState = L; }
/// Advance all active animations one frame.
void tick();
private:
struct ActiveSlot {
Animation* anim = nullptr;
uint16_t frame = 0;
bool loop = false;
int onCompleteRef = LUA_NOREF;
};
Animation* m_animations = nullptr;
int m_animCount = 0;
ActiveSlot m_slots[MAX_SIMULTANEOUS_ANIMS];
UISystem* m_uiSystem = nullptr;
lua_State* m_luaState = nullptr;
psyqo::Trig<> m_trig;
Animation* findByName(const char* name) const;
void applyTrack(CutsceneTrack& track, uint16_t frame);
void captureInitialValues(Animation* anim);
void fireSlotComplete(ActiveSlot& slot);
};
} // namespace psxsplash

View File

@@ -1,4 +1,5 @@
#include "cutscene.hh" #include "cutscene.hh"
#include "interpolation.hh"
#include <psyqo/fixed-point.hh> #include <psyqo/fixed-point.hh>
#include <psyqo/soft-math.hh> #include <psyqo/soft-math.hh>
@@ -114,6 +115,16 @@ void CutscenePlayer::stop() {
fireOnComplete(); fireOnComplete();
} }
bool CutscenePlayer::hasCameraTracks() const {
if (!m_active) return false;
for (uint8_t i = 0; i < m_active->trackCount; i++) {
auto t = m_active->tracks[i].trackType;
if (t == TrackType::CameraPosition || t == TrackType::CameraRotation)
return true;
}
return false;
}
void CutscenePlayer::tick() { void CutscenePlayer::tick() {
if (!m_active) return; if (!m_active) return;
@@ -162,114 +173,6 @@ void CutscenePlayer::fireOnComplete() {
m_onCompleteRef = LUA_NOREF; m_onCompleteRef = LUA_NOREF;
} }
static int32_t applyCurve(int32_t t, InterpMode mode) {
switch (mode) {
default:
case InterpMode::Linear:
return t;
case InterpMode::Step:
return 0;
case InterpMode::EaseIn:
return (int32_t)((int64_t)t * t >> 12);
case InterpMode::EaseOut:
return (int32_t)(((int64_t)t * (8192 - t)) >> 12);
case InterpMode::EaseInOut: {
int64_t t2 = (int64_t)t * t;
int64_t t3 = t2 * t;
return (int32_t)((3 * t2 - 2 * (t3 >> 12)) >> 12);
}
}
}
static bool findKfPair(CutsceneKeyframe* kf, uint8_t count, uint16_t frame,
uint8_t& a, uint8_t& b, int32_t& t, int16_t out[3]) {
if (count == 0) {
out[0] = out[1] = out[2] = 0;
return false;
}
if (frame <= kf[0].getFrame() || count == 1) {
out[0] = kf[0].values[0];
out[1] = kf[0].values[1];
out[2] = kf[0].values[2];
return false;
}
if (frame >= kf[count - 1].getFrame()) {
out[0] = kf[count - 1].values[0];
out[1] = kf[count - 1].values[1];
out[2] = kf[count - 1].values[2];
return false;
}
b = 1;
while (b < count && kf[b].getFrame() <= frame) b++;
a = b - 1;
uint16_t span = kf[b].getFrame() - kf[a].getFrame();
if (span == 0) {
out[0] = kf[a].values[0];
out[1] = kf[a].values[1];
out[2] = kf[a].values[2];
return false;
}
uint32_t num = (uint32_t)(frame - kf[a].getFrame()) << 12;
int32_t rawT = (int32_t)(num / span);
t = applyCurve(rawT, kf[b].getInterp());
return true;
}
void CutscenePlayer::lerpKeyframes(CutsceneKeyframe* kf, uint8_t count, const int16_t initial[3], int16_t out[3]) {
uint8_t a, b;
int32_t t;
if (!findKfPair(kf, count, m_frame, a, b, t, out)) {
if (count > 0 && kf[0].getFrame() > 0 && m_frame < kf[0].getFrame()) {
uint16_t span = kf[0].getFrame();
uint32_t num = (uint32_t)m_frame << 12;
int32_t rawT = (int32_t)(num / span);
int32_t ct = applyCurve(rawT, kf[0].getInterp());
for (int i = 0; i < 3; i++) {
int32_t delta = (int32_t)kf[0].values[i] - (int32_t)initial[i];
out[i] = (int16_t)((int32_t)initial[i] + ((delta * ct) >> 12));
}
}
return;
}
for (int i = 0; i < 3; i++) {
int32_t delta = (int32_t)kf[b].values[i] - (int32_t)kf[a].values[i];
out[i] = (int16_t)((int32_t)kf[a].values[i] + ((delta * t) >> 12));
}
}
static constexpr int32_t ANGLE_FULL_CIRCLE = 2048;
static constexpr int32_t ANGLE_HALF_CIRCLE = 1024;
void CutscenePlayer::lerpAngles(CutsceneKeyframe* kf, uint8_t count, const int16_t initial[3], int16_t out[3]) {
uint8_t a, b;
int32_t t;
if (!findKfPair(kf, count, m_frame, a, b, t, out)) {
if (count > 0 && kf[0].getFrame() > 0 && m_frame < kf[0].getFrame()) {
uint16_t span = kf[0].getFrame();
uint32_t num = (uint32_t)m_frame << 12;
int32_t rawT = (int32_t)(num / span);
int32_t ct = applyCurve(rawT, kf[0].getInterp());
for (int i = 0; i < 3; i++) {
int32_t from = (int32_t)initial[i];
int32_t to = (int32_t)kf[0].values[i];
int32_t delta = to - from;
delta = ((delta + ANGLE_HALF_CIRCLE) % ANGLE_FULL_CIRCLE + ANGLE_FULL_CIRCLE) % ANGLE_FULL_CIRCLE - ANGLE_HALF_CIRCLE;
out[i] = (int16_t)(from + ((delta * ct) >> 12));
}
}
return;
}
for (int i = 0; i < 3; i++) {
int32_t from = (int32_t)kf[a].values[i];
int32_t to = (int32_t)kf[b].values[i];
int32_t delta = to - from;
delta = ((delta + ANGLE_HALF_CIRCLE) % ANGLE_FULL_CIRCLE + ANGLE_FULL_CIRCLE) % ANGLE_FULL_CIRCLE - ANGLE_HALF_CIRCLE;
out[i] = (int16_t)(from + ((delta * t) >> 12));
}
}
void CutscenePlayer::applyTrack(CutsceneTrack& track) { void CutscenePlayer::applyTrack(CutsceneTrack& track) {
if (track.keyframeCount == 0 || !track.keyframes) return; if (track.keyframeCount == 0 || !track.keyframes) return;
@@ -278,7 +181,7 @@ void CutscenePlayer::applyTrack(CutsceneTrack& track) {
switch (track.trackType) { switch (track.trackType) {
case TrackType::CameraPosition: { case TrackType::CameraPosition: {
if (!m_camera) return; if (!m_camera) return;
lerpKeyframes(track.keyframes, track.keyframeCount, track.initialValues, out); psxsplash::lerpKeyframes(track.keyframes, track.keyframeCount, m_frame, track.initialValues, out);
psyqo::FixedPoint<12> x, y, z; psyqo::FixedPoint<12> x, y, z;
x.value = (int32_t)out[0]; x.value = (int32_t)out[0];
y.value = (int32_t)out[1]; y.value = (int32_t)out[1];
@@ -289,7 +192,7 @@ void CutscenePlayer::applyTrack(CutsceneTrack& track) {
case TrackType::CameraRotation: { case TrackType::CameraRotation: {
if (!m_camera) return; if (!m_camera) return;
lerpKeyframes(track.keyframes, track.keyframeCount, track.initialValues, out); psxsplash::lerpKeyframes(track.keyframes, track.keyframeCount, m_frame, track.initialValues, out);
psyqo::Angle rx, ry, rz; psyqo::Angle rx, ry, rz;
rx.value = (int32_t)out[0]; rx.value = (int32_t)out[0];
ry.value = (int32_t)out[1]; ry.value = (int32_t)out[1];
@@ -300,7 +203,7 @@ void CutscenePlayer::applyTrack(CutsceneTrack& track) {
case TrackType::ObjectPosition: { case TrackType::ObjectPosition: {
if (!track.target) return; if (!track.target) return;
lerpKeyframes(track.keyframes, track.keyframeCount, track.initialValues, out); psxsplash::lerpKeyframes(track.keyframes, track.keyframeCount, m_frame, track.initialValues, out);
track.target->position.x.value = (int32_t)out[0]; track.target->position.x.value = (int32_t)out[0];
track.target->position.y.value = (int32_t)out[1]; track.target->position.y.value = (int32_t)out[1];
track.target->position.z.value = (int32_t)out[2]; track.target->position.z.value = (int32_t)out[2];
@@ -309,7 +212,7 @@ void CutscenePlayer::applyTrack(CutsceneTrack& track) {
case TrackType::ObjectRotation: { case TrackType::ObjectRotation: {
if (!track.target) return; if (!track.target) return;
lerpKeyframes(track.keyframes, track.keyframeCount, track.initialValues, out); psxsplash::lerpKeyframes(track.keyframes, track.keyframeCount, m_frame, track.initialValues, out);
psyqo::Angle rx, ry, rz; psyqo::Angle rx, ry, rz;
rx.value = (int32_t)out[0]; rx.value = (int32_t)out[0];
ry.value = (int32_t)out[1]; ry.value = (int32_t)out[1];
@@ -372,7 +275,7 @@ void CutscenePlayer::applyTrack(CutsceneTrack& track) {
case TrackType::UIProgress: { case TrackType::UIProgress: {
if (!m_uiSystem) return; if (!m_uiSystem) return;
lerpKeyframes(track.keyframes, track.keyframeCount, track.initialValues, out); psxsplash::lerpKeyframes(track.keyframes, track.keyframeCount, m_frame, track.initialValues, out);
int16_t v = out[0]; int16_t v = out[0];
if (v < 0) v = 0; if (v < 0) v = 0;
if (v > 100) v = 100; if (v > 100) v = 100;
@@ -382,14 +285,14 @@ void CutscenePlayer::applyTrack(CutsceneTrack& track) {
case TrackType::UIPosition: { case TrackType::UIPosition: {
if (!m_uiSystem) return; if (!m_uiSystem) return;
lerpKeyframes(track.keyframes, track.keyframeCount, track.initialValues, out); psxsplash::lerpKeyframes(track.keyframes, track.keyframeCount, m_frame, track.initialValues, out);
m_uiSystem->setPosition(track.uiHandle, out[0], out[1]); m_uiSystem->setPosition(track.uiHandle, out[0], out[1]);
break; break;
} }
case TrackType::UIColor: { case TrackType::UIColor: {
if (!m_uiSystem) return; if (!m_uiSystem) return;
lerpKeyframes(track.keyframes, track.keyframeCount, track.initialValues, out); psxsplash::lerpKeyframes(track.keyframes, track.keyframeCount, m_frame, track.initialValues, out);
uint8_t cr = (out[0] < 0) ? 0 : ((out[0] > 255) ? 255 : (uint8_t)out[0]); uint8_t cr = (out[0] < 0) ? 0 : ((out[0] > 255) ? 255 : (uint8_t)out[0]);
uint8_t cg = (out[1] < 0) ? 0 : ((out[1] > 255) ? 255 : (uint8_t)out[1]); uint8_t cg = (out[1] < 0) ? 0 : ((out[1] > 255) ? 255 : (uint8_t)out[1]);
uint8_t cb = (out[2] < 0) ? 0 : ((out[2] > 255) ? 255 : (uint8_t)out[2]); uint8_t cb = (out[2] < 0) ? 0 : ((out[2] > 255) ? 255 : (uint8_t)out[2]);

View File

@@ -97,6 +97,10 @@ public:
/// True if a cutscene is currently active. /// True if a cutscene is currently active.
bool isPlaying() const { return m_active != nullptr; } bool isPlaying() const { return m_active != nullptr; }
/// True if the active cutscene has camera tracks (position or rotation).
/// Use this to decide whether to suppress player camera follow.
bool hasCameraTracks() const;
/// Set a Lua registry reference to call when the cutscene finishes. /// Set a Lua registry reference to call when the cutscene finishes.
/// Pass LUA_NOREF to clear. The callback is called ONCE when the /// Pass LUA_NOREF to clear. The callback is called ONCE when the
/// cutscene ends (not on each loop iteration - only when it truly stops). /// cutscene ends (not on each loop iteration - only when it truly stops).
@@ -125,8 +129,6 @@ private:
psyqo::Trig<> m_trig; psyqo::Trig<> m_trig;
void applyTrack(CutsceneTrack& track); 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(); void fireOnComplete();
}; };

View File

@@ -1,140 +1,141 @@
#pragma once #pragma once
// Pre-compiled PS1 Lua bytecode for GAMEOBJECT_SCRIPT // Pre-compiled PS1 Lua bytecode for GAMEOBJECT_SCRIPT
// Generated by luac_psx - do not edit manually // Generated by luac_psx (32-bit, LUA_NUMBER=long) - do not edit manually
// 1581 bytes // 943 bytes
//
// Original Lua source:
// return function(metatable)
// local get_position = metatable.get_position
// local set_position = metatable.set_position
// local get_active = metatable.get_active
// local set_active = metatable.set_active
// local get_rotation = metatable.get_rotation
// local set_rotation = metatable.set_rotation
// local get_rotationY = metatable.get_rotationY
// local set_rotationY = metatable.set_rotationY
//
// metatable.get_position = nil
// metatable.set_position = nil
// metatable.get_active = nil
// metatable.set_active = nil
// metatable.get_rotation = nil
// metatable.set_rotation = nil
// metatable.get_rotationY = nil
// metatable.set_rotationY = nil
//
// function metatable.__index(self, key)
// local raw = rawget(self, key)
// if raw ~= nil then return raw end
// if key == "position" then
// return get_position(self.__cpp_ptr)
// elseif key == "active" then
// return get_active(self.__cpp_ptr)
// elseif key == "rotation" then
// return get_rotation(self.__cpp_ptr)
// elseif key == "rotationY" then
// return get_rotationY(self.__cpp_ptr)
// end
// return nil
// end
//
// function metatable.__newindex(self, key, value)
// if key == "position" then
// set_position(self.__cpp_ptr, value)
// return
// elseif key == "active" then
// set_active(self.__cpp_ptr, value)
// return
// elseif key == "rotation" then
// set_rotation(self.__cpp_ptr, value)
// return
// elseif key == "rotationY" then
// set_rotationY(self.__cpp_ptr, value)
// return
// end
// rawset(self, key, value)
// end
// end
static const unsigned char GAMEOBJECT_BYTECODE[] = { static const unsigned char GAMEOBJECT_BYTECODE[] = {
0x1b, 0x4c, 0x75, 0x61, 0x52, 0x00, 0x01, 0x04, 0x04, 0x04, 0x04, 0x01, 0x1b, 0x4c, 0x75, 0x61, 0x52, 0x00, 0x01, 0x04, 0x04, 0x04, 0x04, 0x01,
0x19, 0x93, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x93, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00,
0x00, 0x1f, 0x00, 0x00, 0x01, 0x1f, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x01, 0x1f, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00,
0x00, 0x01, 0x00, 0x08, 0x11, 0x00, 0x00, 0x00, 0x47, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x0a, 0x15, 0x00, 0x00, 0x00, 0x47, 0x00, 0x40, 0x00,
0x87, 0x40, 0x40, 0x00, 0xc7, 0x80, 0x40, 0x00, 0x07, 0xc1, 0x40, 0x00, 0x87, 0x40, 0x40, 0x00, 0xc7, 0x80, 0x40, 0x00, 0x07, 0xc1, 0x40, 0x00,
0x47, 0x01, 0x41, 0x00, 0x87, 0x41, 0x41, 0x00, 0x0a, 0x80, 0x41, 0x80, 0x47, 0x01, 0x41, 0x00, 0x87, 0x41, 0x41, 0x00, 0xc7, 0x81, 0x41, 0x00,
0x0a, 0x80, 0xc1, 0x80, 0x0a, 0x80, 0x41, 0x81, 0x0a, 0x80, 0xc1, 0x81, 0x07, 0xc2, 0x41, 0x00, 0x0a, 0x00, 0x42, 0x80, 0x0a, 0x00, 0xc2, 0x80,
0x0a, 0x80, 0x41, 0x82, 0x0a, 0x80, 0xc1, 0x82, 0xe5, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x42, 0x81, 0x0a, 0x00, 0xc2, 0x81, 0x0a, 0x00, 0x42, 0x82,
0x0a, 0xc0, 0x81, 0x83, 0xe5, 0x41, 0x00, 0x00, 0x0a, 0xc0, 0x01, 0x84, 0x0a, 0x00, 0xc2, 0x82, 0x0a, 0x00, 0x42, 0x83, 0x0a, 0x00, 0xc2, 0x83,
0x1f, 0x00, 0x80, 0x00, 0x09, 0x00, 0x00, 0x00, 0x04, 0x0d, 0x00, 0x00, 0x65, 0x02, 0x00, 0x00, 0x0a, 0x40, 0x82, 0x84, 0x65, 0x42, 0x00, 0x00,
0x00, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x0a, 0x40, 0x02, 0x85, 0x1f, 0x00, 0x80, 0x00, 0x0b, 0x00, 0x00, 0x00,
0x6e, 0x00, 0x04, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x65, 0x74, 0x5f, 0x70, 0x04, 0x0d, 0x00, 0x00, 0x00, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x73,
0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x04, 0x0b, 0x00, 0x00, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x04, 0x0d, 0x00, 0x00, 0x00, 0x73,
0x00, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00,
0x04, 0x0b, 0x00, 0x00, 0x00, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x74,
0x69, 0x76, 0x65, 0x00, 0x04, 0x0e, 0x00, 0x00, 0x00, 0x67, 0x65, 0x74,
0x5f, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x04,
0x0e, 0x00, 0x00, 0x00, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x6f, 0x74, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00,
0x5f, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, 0x04, 0x0b, 0x00, 0x00,
0x00, 0x5f, 0x5f, 0x6e, 0x65, 0x77, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00,
0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
0x02, 0x00, 0x05, 0x1e, 0x00, 0x00, 0x00, 0x86, 0x00, 0x40, 0x00, 0xc0,
0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x9d, 0x80, 0x80, 0x01, 0x58,
0x40, 0x40, 0x01, 0x17, 0x00, 0x00, 0x80, 0x9f, 0x00, 0x00, 0x01, 0x18,
0x80, 0xc0, 0x00, 0x17, 0x00, 0x01, 0x80, 0xc5, 0x00, 0x80, 0x00, 0x07,
0xc1, 0x40, 0x00, 0xde, 0x00, 0x00, 0x01, 0xdf, 0x00, 0x00, 0x00, 0x17,
0x00, 0x03, 0x80, 0x18, 0x00, 0xc1, 0x00, 0x17, 0x00, 0x01, 0x80, 0xc5,
0x00, 0x00, 0x01, 0x07, 0xc1, 0x40, 0x00, 0xde, 0x00, 0x00, 0x01, 0xdf,
0x00, 0x00, 0x00, 0x17, 0x40, 0x01, 0x80, 0x18, 0x40, 0xc1, 0x00, 0x17,
0xc0, 0x00, 0x80, 0xc5, 0x00, 0x80, 0x01, 0x07, 0xc1, 0x40, 0x00, 0xde,
0x00, 0x00, 0x01, 0xdf, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0xdf,
0x00, 0x00, 0x01, 0x1f, 0x00, 0x80, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04,
0x07, 0x00, 0x00, 0x00, 0x72, 0x61, 0x77, 0x67, 0x65, 0x74, 0x00, 0x00,
0x04, 0x09, 0x00, 0x00, 0x00, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f,
0x6e, 0x00, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x5f, 0x5f, 0x63, 0x70, 0x70,
0x5f, 0x70, 0x74, 0x72, 0x00, 0x04, 0x07, 0x00, 0x00, 0x00, 0x61, 0x63,
0x74, 0x69, 0x76, 0x65, 0x00, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x72, 0x6f,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x03, 0x01, 0x05,
0x0f, 0x00, 0x00, 0x00, 0x67, 0x61, 0x6d, 0x65, 0x6f, 0x62, 0x6a, 0x65,
0x63, 0x74, 0x2e, 0x6c, 0x75, 0x61, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x11,
0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x11,
0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x12,
0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14,
0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14,
0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15,
0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16,
0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17,
0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18,
0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1a,
0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x73, 0x65, 0x6c, 0x66, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x6b, 0x65, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x72, 0x61, 0x77, 0x00, 0x04, 0x00, 0x00, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x5f, 0x45, 0x4e, 0x56, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x67, 0x65, 0x74,
0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x0b, 0x00,
0x00, 0x00, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65,
0x00, 0x0e, 0x00, 0x00, 0x00, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x6f, 0x74,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x29,
0x00, 0x00, 0x00, 0x03, 0x00, 0x07, 0x1d, 0x00, 0x00, 0x00, 0x18, 0x00,
0xc0, 0x00, 0x17, 0x40, 0x01, 0x80, 0xc5, 0x00, 0x00, 0x00, 0x07, 0x41,
0x40, 0x00, 0x40, 0x01, 0x00, 0x01, 0xdd, 0x40, 0x80, 0x01, 0x1f, 0x00,
0x80, 0x00, 0x17, 0x80, 0x03, 0x80, 0x18, 0x80, 0xc0, 0x00, 0x17, 0x40,
0x01, 0x80, 0xc5, 0x00, 0x80, 0x00, 0x07, 0x41, 0x40, 0x00, 0x40, 0x01,
0x00, 0x01, 0xdd, 0x40, 0x80, 0x01, 0x1f, 0x00, 0x80, 0x00, 0x17, 0x80,
0x01, 0x80, 0x18, 0xc0, 0xc0, 0x00, 0x17, 0x00, 0x01, 0x80, 0xc5, 0x00,
0x00, 0x01, 0x07, 0x41, 0x40, 0x00, 0x40, 0x01, 0x00, 0x01, 0xdd, 0x40,
0x80, 0x01, 0x1f, 0x00, 0x80, 0x00, 0xc6, 0x00, 0xc1, 0x01, 0x00, 0x01,
0x00, 0x00, 0x40, 0x01, 0x80, 0x00, 0x80, 0x01, 0x00, 0x01, 0xdd, 0x40,
0x00, 0x02, 0x1f, 0x00, 0x80, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x09,
0x00, 0x00, 0x00, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00,
0x04, 0x0a, 0x00, 0x00, 0x00, 0x5f, 0x5f, 0x63, 0x70, 0x70, 0x5f, 0x70,
0x74, 0x72, 0x00, 0x04, 0x07, 0x00, 0x00, 0x00, 0x61, 0x63, 0x74, 0x69,
0x76, 0x65, 0x00, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x74, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x04, 0x07, 0x00, 0x00, 0x00, 0x72,
0x61, 0x77, 0x73, 0x65, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0x01, 0x02, 0x01, 0x04, 0x01, 0x06, 0x00, 0x00, 0x0f, 0x00,
0x00, 0x00, 0x67, 0x61, 0x6d, 0x65, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74,
0x2e, 0x6c, 0x75, 0x61, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00,
0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00,
0x00, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00,
0x00, 0x22, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00,
0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00,
0x00, 0x24, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00,
0x00, 0x25, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00,
0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00,
0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00,
0x00, 0x29, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
0x00, 0x73, 0x65, 0x6c, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6b, 0x65, 0x79, 0x00, 0x00, 0x00,
0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x65, 0x74, 0x5f,
0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x0b, 0x00, 0x00,
0x00, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00,
0x0e, 0x00, 0x00, 0x00, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x6f, 0x74, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x05, 0x00, 0x00, 0x00, 0x5f, 0x45,
0x4e, 0x56, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00,
0x00, 0x67, 0x61, 0x6d, 0x65, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e,
0x6c, 0x75, 0x61, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
0x2a, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x6d, 0x65, 0x74, 0x61, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x00, 0x00, 0x00,
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x67, 0x65,
0x74, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x01,
0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x73,
0x65, 0x74, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00,
0x02, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x04, 0x0b, 0x00, 0x00, 0x00, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x74,
0x67, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x03, 0x69, 0x76, 0x65, 0x00, 0x04, 0x0b, 0x00, 0x00, 0x00, 0x73, 0x65, 0x74,
0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x73, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x04, 0x0d, 0x00, 0x00,
0x65, 0x74, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x04, 0x00, 0x00, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f,
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x67, 0x65, 0x6e, 0x00, 0x04, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x65, 0x74, 0x5f, 0x72,
0x74, 0x5f, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x04, 0x0e, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f,
0x73, 0x65, 0x74, 0x5f, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x6e, 0x59, 0x00, 0x04, 0x0e, 0x00, 0x00, 0x00, 0x73, 0x65, 0x74, 0x5f,
0x59, 0x00, 0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x00, 0x04,
0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x5f, 0x45, 0x4e, 0x56, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x5f, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x67, 0x61, 0x6d, 0x04, 0x0b, 0x00, 0x00, 0x00, 0x5f, 0x5f, 0x6e, 0x65, 0x77, 0x69, 0x6e,
0x65, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x6c, 0x75, 0x61, 0x00, 0x64, 0x65, 0x78, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x25, 0x00, 0x00, 0x00, 0x86,
0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x9d,
0x05, 0x00, 0x00, 0x00, 0x5f, 0x45, 0x4e, 0x56, 0x00, 0x80, 0x80, 0x01, 0x58, 0x40, 0x40, 0x01, 0x17, 0x00, 0x00, 0x80, 0x9f,
0x00, 0x00, 0x01, 0x18, 0x80, 0xc0, 0x00, 0x17, 0x00, 0x01, 0x80, 0xc5,
0x00, 0x80, 0x00, 0x07, 0xc1, 0x40, 0x00, 0xde, 0x00, 0x00, 0x01, 0xdf,
0x00, 0x00, 0x00, 0x17, 0xc0, 0x04, 0x80, 0x18, 0x00, 0xc1, 0x00, 0x17,
0x00, 0x01, 0x80, 0xc5, 0x00, 0x00, 0x01, 0x07, 0xc1, 0x40, 0x00, 0xde,
0x00, 0x00, 0x01, 0xdf, 0x00, 0x00, 0x00, 0x17, 0x00, 0x03, 0x80, 0x18,
0x40, 0xc1, 0x00, 0x17, 0x00, 0x01, 0x80, 0xc5, 0x00, 0x80, 0x01, 0x07,
0xc1, 0x40, 0x00, 0xde, 0x00, 0x00, 0x01, 0xdf, 0x00, 0x00, 0x00, 0x17,
0x40, 0x01, 0x80, 0x18, 0x80, 0xc1, 0x00, 0x17, 0xc0, 0x00, 0x80, 0xc5,
0x00, 0x00, 0x02, 0x07, 0xc1, 0x40, 0x00, 0xde, 0x00, 0x00, 0x01, 0xdf,
0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0xdf, 0x00, 0x00, 0x01, 0x1f,
0x00, 0x80, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x07, 0x00, 0x00, 0x00,
0x72, 0x61, 0x77, 0x67, 0x65, 0x74, 0x00, 0x00, 0x04, 0x09, 0x00, 0x00,
0x00, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x04, 0x0a,
0x00, 0x00, 0x00, 0x5f, 0x5f, 0x63, 0x70, 0x70, 0x5f, 0x70, 0x74, 0x72,
0x00, 0x04, 0x07, 0x00, 0x00, 0x00, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65,
0x00, 0x04, 0x09, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x00, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x74, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x03, 0x01, 0x05, 0x01, 0x07,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
0x03, 0x00, 0x07, 0x25, 0x00, 0x00, 0x00, 0x18, 0x00, 0xc0, 0x00, 0x17,
0x40, 0x01, 0x80, 0xc5, 0x00, 0x00, 0x00, 0x07, 0x41, 0x40, 0x00, 0x40,
0x01, 0x00, 0x01, 0xdd, 0x40, 0x80, 0x01, 0x1f, 0x00, 0x80, 0x00, 0x17,
0x80, 0x05, 0x80, 0x18, 0x80, 0xc0, 0x00, 0x17, 0x40, 0x01, 0x80, 0xc5,
0x00, 0x80, 0x00, 0x07, 0x41, 0x40, 0x00, 0x40, 0x01, 0x00, 0x01, 0xdd,
0x40, 0x80, 0x01, 0x1f, 0x00, 0x80, 0x00, 0x17, 0x80, 0x03, 0x80, 0x18,
0xc0, 0xc0, 0x00, 0x17, 0x40, 0x01, 0x80, 0xc5, 0x00, 0x00, 0x01, 0x07,
0x41, 0x40, 0x00, 0x40, 0x01, 0x00, 0x01, 0xdd, 0x40, 0x80, 0x01, 0x1f,
0x00, 0x80, 0x00, 0x17, 0x80, 0x01, 0x80, 0x18, 0x00, 0xc1, 0x00, 0x17,
0x00, 0x01, 0x80, 0xc5, 0x00, 0x80, 0x01, 0x07, 0x41, 0x40, 0x00, 0x40,
0x01, 0x00, 0x01, 0xdd, 0x40, 0x80, 0x01, 0x1f, 0x00, 0x80, 0x00, 0xc6,
0x40, 0x41, 0x02, 0x00, 0x01, 0x00, 0x00, 0x40, 0x01, 0x80, 0x00, 0x80,
0x01, 0x00, 0x01, 0xdd, 0x40, 0x00, 0x02, 0x1f, 0x00, 0x80, 0x00, 0x06,
0x00, 0x00, 0x00, 0x04, 0x09, 0x00, 0x00, 0x00, 0x70, 0x6f, 0x73, 0x69,
0x74, 0x69, 0x6f, 0x6e, 0x00, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x5f, 0x5f,
0x63, 0x70, 0x70, 0x5f, 0x70, 0x74, 0x72, 0x00, 0x04, 0x07, 0x00, 0x00,
0x00, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x04, 0x09, 0x00, 0x00,
0x00, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x04, 0x0a,
0x00, 0x00, 0x00, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59,
0x00, 0x04, 0x07, 0x00, 0x00, 0x00, 0x72, 0x61, 0x77, 0x73, 0x65, 0x74,
0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x02, 0x01,
0x04, 0x01, 0x06, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}; };

115
src/interpolation.cpp Normal file
View File

@@ -0,0 +1,115 @@
#include "interpolation.hh"
namespace psxsplash {
int32_t applyCurve(int32_t t, InterpMode mode) {
switch (mode) {
default:
case InterpMode::Linear:
return t;
case InterpMode::Step:
return 0;
case InterpMode::EaseIn:
return (int32_t)((int64_t)t * t >> 12);
case InterpMode::EaseOut:
return (int32_t)(((int64_t)t * (8192 - t)) >> 12);
case InterpMode::EaseInOut: {
int64_t t2 = (int64_t)t * t;
int64_t t3 = t2 * t;
return (int32_t)((3 * t2 - 2 * (t3 >> 12)) >> 12);
}
}
}
bool findKfPair(CutsceneKeyframe* kf, uint8_t count, uint16_t frame,
uint8_t& a, uint8_t& b, int32_t& t, int16_t out[3]) {
if (count == 0) {
out[0] = out[1] = out[2] = 0;
return false;
}
if (frame <= kf[0].getFrame() || count == 1) {
out[0] = kf[0].values[0];
out[1] = kf[0].values[1];
out[2] = kf[0].values[2];
return false;
}
if (frame >= kf[count - 1].getFrame()) {
out[0] = kf[count - 1].values[0];
out[1] = kf[count - 1].values[1];
out[2] = kf[count - 1].values[2];
return false;
}
b = 1;
while (b < count && kf[b].getFrame() <= frame) b++;
a = b - 1;
uint16_t span = kf[b].getFrame() - kf[a].getFrame();
if (span == 0) {
out[0] = kf[a].values[0];
out[1] = kf[a].values[1];
out[2] = kf[a].values[2];
return false;
}
uint32_t num = (uint32_t)(frame - kf[a].getFrame()) << 12;
int32_t rawT = (int32_t)(num / span);
t = applyCurve(rawT, kf[b].getInterp());
return true;
}
void lerpKeyframes(CutsceneKeyframe* kf, uint8_t count, uint16_t frame,
const int16_t initial[3], int16_t out[3]) {
uint8_t a, b;
int32_t t;
if (!findKfPair(kf, count, frame, a, b, t, out)) {
if (count > 0 && kf[0].getFrame() > 0 && frame < kf[0].getFrame()) {
uint16_t span = kf[0].getFrame();
uint32_t num = (uint32_t)frame << 12;
int32_t rawT = (int32_t)(num / span);
int32_t ct = applyCurve(rawT, kf[0].getInterp());
for (int i = 0; i < 3; i++) {
int32_t delta = (int32_t)kf[0].values[i] - (int32_t)initial[i];
out[i] = (int16_t)((int32_t)initial[i] + ((delta * ct) >> 12));
}
}
return;
}
for (int i = 0; i < 3; i++) {
int32_t delta = (int32_t)kf[b].values[i] - (int32_t)kf[a].values[i];
out[i] = (int16_t)((int32_t)kf[a].values[i] + ((delta * t) >> 12));
}
}
static constexpr int32_t ANGLE_FULL_CIRCLE = 2048;
static constexpr int32_t ANGLE_HALF_CIRCLE = 1024;
void lerpAngles(CutsceneKeyframe* kf, uint8_t count, uint16_t frame,
const int16_t initial[3], int16_t out[3]) {
uint8_t a, b;
int32_t t;
if (!findKfPair(kf, count, frame, a, b, t, out)) {
if (count > 0 && kf[0].getFrame() > 0 && frame < kf[0].getFrame()) {
uint16_t span = kf[0].getFrame();
uint32_t num = (uint32_t)frame << 12;
int32_t rawT = (int32_t)(num / span);
int32_t ct = applyCurve(rawT, kf[0].getInterp());
for (int i = 0; i < 3; i++) {
int32_t from = (int32_t)initial[i];
int32_t to = (int32_t)kf[0].values[i];
int32_t delta = to - from;
delta = ((delta + ANGLE_HALF_CIRCLE) % ANGLE_FULL_CIRCLE + ANGLE_FULL_CIRCLE) % ANGLE_FULL_CIRCLE - ANGLE_HALF_CIRCLE;
out[i] = (int16_t)(from + ((delta * ct) >> 12));
}
}
return;
}
for (int i = 0; i < 3; i++) {
int32_t from = (int32_t)kf[a].values[i];
int32_t to = (int32_t)kf[b].values[i];
int32_t delta = to - from;
delta = ((delta + ANGLE_HALF_CIRCLE) % ANGLE_FULL_CIRCLE + ANGLE_FULL_CIRCLE) % ANGLE_FULL_CIRCLE - ANGLE_HALF_CIRCLE;
out[i] = (int16_t)(from + ((delta * t) >> 12));
}
}
} // namespace psxsplash

19
src/interpolation.hh Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <stdint.h>
#include "cutscene.hh"
namespace psxsplash {
int32_t applyCurve(int32_t t, InterpMode mode);
bool findKfPair(CutsceneKeyframe* kf, uint8_t count, uint16_t frame,
uint8_t& a, uint8_t& b, int32_t& t, int16_t out[3]);
void lerpKeyframes(CutsceneKeyframe* kf, uint8_t count, uint16_t frame,
const int16_t initial[3], int16_t out[3]);
void lerpAngles(CutsceneKeyframe* kf, uint8_t count, uint16_t frame,
const int16_t initial[3], int16_t out[3]);
} // namespace psxsplash

View File

@@ -26,18 +26,24 @@ extern "C" void *lua_oom_realloc(void *ptr, size_t size) {
// Lua helpers // Lua helpers
static constexpr lua_Number kFixedScale = 4096; static constexpr int32_t kFixedScale = 4096;
// Accept FixedPoint object or plain number from Lua
static psyqo::FixedPoint<12> readFP(psyqo::Lua& L, int idx) {
if (L.isFixedPoint(idx)) return L.toFixedPoint(idx);
return psyqo::FixedPoint<12>(static_cast<int32_t>(L.toNumber(idx) * kFixedScale), psyqo::FixedPoint<12>::RAW);
}
static int gameobjectGetPosition(psyqo::Lua L) { static int gameobjectGetPosition(psyqo::Lua L) {
auto go = L.toUserdata<psxsplash::GameObject>(1); auto go = L.toUserdata<psxsplash::GameObject>(1);
L.newTable(); L.newTable();
L.pushNumber(static_cast<lua_Number>(go->position.x.raw()) / kFixedScale); L.push(go->position.x);
L.setField(2, "x"); L.setField(2, "x");
L.pushNumber(static_cast<lua_Number>(go->position.y.raw()) / kFixedScale); L.push(go->position.y);
L.setField(2, "y"); L.setField(2, "y");
L.pushNumber(static_cast<lua_Number>(go->position.z.raw()) / kFixedScale); L.push(go->position.z);
L.setField(2, "z"); L.setField(2, "z");
return 1; return 1;
@@ -49,15 +55,15 @@ static int gameobjectSetPosition(psyqo::Lua L) {
auto go = L.toUserdata<psxsplash::GameObject>(1); auto go = L.toUserdata<psxsplash::GameObject>(1);
L.getField(2, "x"); L.getField(2, "x");
go->position.x = psyqo::FixedPoint<>(static_cast<int32_t>(L.toNumber(3) * kFixedScale), psyqo::FixedPoint<>::RAW); go->position.x = readFP(L, 3);
L.pop(); L.pop();
L.getField(2, "y"); L.getField(2, "y");
go->position.y = psyqo::FixedPoint<>(static_cast<int32_t>(L.toNumber(3) * kFixedScale), psyqo::FixedPoint<>::RAW); go->position.y = readFP(L, 3);
L.pop(); L.pop();
L.getField(2, "z");
go->position.z = psyqo::FixedPoint<>(static_cast<int32_t>(L.toNumber(3) * kFixedScale), psyqo::FixedPoint<>::RAW); L.getField(2, "z");
go->position.z = readFP(L, 3);
L.pop(); L.pop();
return 0; return 0;
@@ -76,10 +82,8 @@ static int gameobjectSetActive(psyqo::Lua L) {
return 0; return 0;
} }
static constexpr lua_Number kAngleScale = 1024;
static psyqo::Trig<> s_trig; static psyqo::Trig<> s_trig;
static psyqo::Angle fastAtan2(int32_t sinVal, int32_t cosVal) { static psyqo::Angle fastAtan2(int32_t sinVal, int32_t cosVal) {
psyqo::Angle result; psyqo::Angle result;
if (cosVal == 0 && sinVal == 0) { result.value = 0; return result; } if (cosVal == 0 && sinVal == 0) { result.value = 0; return result; }
@@ -99,20 +103,85 @@ static psyqo::Angle fastAtan2(int32_t sinVal, int32_t cosVal) {
return result; return result;
} }
static int gameobjectGetRotation(psyqo::Lua L) {
auto go = L.toUserdata<psxsplash::GameObject>(1);
// Decompose Y-axis rotation from the matrix (vs[0].x = cos, vs[0].z = sin)
// For full XYZ, we extract approximate Euler angles from the rotation matrix.
// Row 0: [cos(Y)cos(Z), -cos(Y)sin(Z), sin(Y)]
// This is a simplified extraction assuming common rotation order Y*X*Z.
int32_t sinY = go->rotation.vs[0].z.raw();
int32_t cosY = go->rotation.vs[0].x.raw();
int32_t sinX = -go->rotation.vs[1].z.raw();
int32_t cosX = go->rotation.vs[2].z.raw();
int32_t sinZ = -go->rotation.vs[0].y.raw();
int32_t cosZ = go->rotation.vs[0].x.raw();
auto toFP12 = [](psyqo::Angle a) -> psyqo::FixedPoint<12> {
psyqo::FixedPoint<12> fp;
fp.value = a.value << 2;
return fp;
};
L.newTable();
L.push(toFP12(fastAtan2(sinX, cosX)));
L.setField(2, "x");
L.push(toFP12(fastAtan2(sinY, cosY)));
L.setField(2, "y");
L.push(toFP12(fastAtan2(sinZ, cosZ)));
L.setField(2, "z");
return 1;
}
static int gameobjectSetRotation(psyqo::Lua L) {
auto go = L.toUserdata<psxsplash::GameObject>(1);
L.getField(2, "x");
psyqo::FixedPoint<12> fpX = readFP(L, 3);
L.pop();
L.getField(2, "y");
psyqo::FixedPoint<12> fpY = readFP(L, 3);
L.pop();
L.getField(2, "z");
psyqo::FixedPoint<12> fpZ = readFP(L, 3);
L.pop();
// Convert FixedPoint<12> to Angle (FixedPoint<10>)
psyqo::Angle rx, ry, rz;
rx.value = fpX.value >> 2;
ry.value = fpY.value >> 2;
rz.value = fpZ.value >> 2;
// Compose Y * X * Z rotation matrix
auto matY = psyqo::SoftMath::generateRotationMatrix33(ry, psyqo::SoftMath::Axis::Y, s_trig);
auto matX = psyqo::SoftMath::generateRotationMatrix33(rx, psyqo::SoftMath::Axis::X, s_trig);
auto matZ = psyqo::SoftMath::generateRotationMatrix33(rz, psyqo::SoftMath::Axis::Z, s_trig);
auto temp = psyqo::SoftMath::multiplyMatrix33(matY, matX);
go->rotation = psyqo::SoftMath::multiplyMatrix33(temp, matZ);
return 0;
}
static int gameobjectGetRotationY(psyqo::Lua L) { static int gameobjectGetRotationY(psyqo::Lua L) {
auto go = L.toUserdata<psxsplash::GameObject>(1); auto go = L.toUserdata<psxsplash::GameObject>(1);
int32_t sinRaw = go->rotation.vs[0].z.raw(); int32_t sinRaw = go->rotation.vs[0].z.raw();
int32_t cosRaw = go->rotation.vs[0].x.raw(); int32_t cosRaw = go->rotation.vs[0].x.raw();
psyqo::Angle angle = fastAtan2(sinRaw, cosRaw); psyqo::Angle angle = fastAtan2(sinRaw, cosRaw);
L.pushNumber(static_cast<lua_Number>(angle.value) / kAngleScale); // Angle is FixedPoint<10> (pi-units). Convert to FixedPoint<12> for Lua.
psyqo::FixedPoint<12> fp12;
fp12.value = angle.value << 2;
L.push(fp12);
return 1; return 1;
} }
static int gameobjectSetRotationY(psyqo::Lua L) { static int gameobjectSetRotationY(psyqo::Lua L) {
auto go = L.toUserdata<psxsplash::GameObject>(1); auto go = L.toUserdata<psxsplash::GameObject>(1);
lua_Number piUnits = L.toNumber(2); // Accept FixedPoint<12> from Lua, convert to Angle (FixedPoint<10>)
psyqo::FixedPoint<12> fp12 = readFP(L, 2);
psyqo::Angle angle; psyqo::Angle angle;
angle.value = static_cast<int32_t>(piUnits * kAngleScale); angle.value = fp12.value >> 2;
go->rotation = psyqo::SoftMath::generateRotationMatrix33(angle, psyqo::SoftMath::Axis::Y, s_trig); go->rotation = psyqo::SoftMath::generateRotationMatrix33(angle, psyqo::SoftMath::Axis::Y, s_trig);
return 0; return 0;
} }
@@ -137,6 +206,12 @@ void psxsplash::Lua::Init() {
L.push(gameobjectSetActive); L.push(gameobjectSetActive);
L.setField(-2, "set_active"); L.setField(-2, "set_active");
L.push(gameobjectGetRotation);
L.setField(-2, "get_rotation");
L.push(gameobjectSetRotation);
L.setField(-2, "set_rotation");
L.push(gameobjectGetRotationY); L.push(gameobjectGetRotationY);
L.setField(-2, "get_rotationY"); L.setField(-2, "get_rotationY");
@@ -168,6 +243,49 @@ void psxsplash::Lua::Init() {
L.newTable(); L.newTable();
m_luascriptsReference = L.ref(); m_luascriptsReference = L.ref();
// Add __concat to the FixedPoint metatable so FixedPoint values work with ..
// psyqo-lua doesn't provide this, but scripts need it for Debug.Log etc.
L.getField(LUA_REGISTRYINDEX, "psyqo.FixedPoint");
if (L.isTable(-1)) {
L.push([](psyqo::Lua L) -> int {
// Convert both operands to strings and concatenate
char buf[64];
int len = 0;
for (int i = 1; i <= 2; i++) {
if (L.isFixedPoint(i)) {
auto fp = L.toFixedPoint(i);
int32_t raw = fp.raw();
int integer = raw >> 12;
unsigned fraction = (raw < 0 ? -raw : raw) & 0xfff;
if (fraction == 0) {
len += snprintf(buf + len, sizeof(buf) - len, "%d", integer);
} else {
unsigned decimal = (fraction * 1000) >> 12;
if (raw < 0 && integer == 0)
len += snprintf(buf + len, sizeof(buf) - len, "-%d.%03u", integer, decimal);
else
len += snprintf(buf + len, sizeof(buf) - len, "%d.%03u", integer, decimal);
}
} else {
const char* s = L.toString(i);
if (s) {
int slen = 0;
while (s[slen]) slen++;
if (len + slen < (int)sizeof(buf)) {
for (int j = 0; j < slen; j++) buf[len++] = s[j];
}
}
}
}
buf[len] = '\0';
L.push(buf, len);
return 1;
});
L.setField(-2, "__concat");
}
L.pop();
} }
void psxsplash::Lua::Shutdown() { void psxsplash::Lua::Shutdown() {
@@ -357,14 +475,18 @@ void psxsplash::Lua::RegisterGameObject(GameObject* go) {
// Store the event mask directly in the GameObject // Store the event mask directly in the GameObject
go->eventMask = eventMask; go->eventMask = eventMask;
L.pop(); L.pop();
// empty stack // empty stack
// Note: onCreate is NOT fired here. Call FireAllOnCreate() after all objects
// are registered so that Entity.Find works across all objects in onCreate.
}
void psxsplash::Lua::FireAllOnCreate(GameObject** objects, size_t count) {
// Fire onCreate event if this object handles it for (size_t i = 0; i < count; i++) {
if (eventMask & EVENT_ON_CREATE) { if (objects[i] && (objects[i]->eventMask & EVENT_ON_CREATE)) {
onCreateMethodWrapper.callMethod(*this, go); onCreateMethodWrapper.callMethod(*this, objects[i]);
}
} }
} }

View File

@@ -49,6 +49,7 @@ class Lua {
void LoadLuaFile(const char* code, size_t len, int index); void LoadLuaFile(const char* code, size_t len, int index);
void RegisterSceneScripts(int index); void RegisterSceneScripts(int index);
void RegisterGameObject(GameObject* go); void RegisterGameObject(GameObject* go);
void FireAllOnCreate(GameObject** objects, size_t count);
void RelocateGameObjects(GameObject** objects, size_t count, intptr_t delta); void RelocateGameObjects(GameObject** objects, size_t count, intptr_t delta);
// Get the underlying psyqo::Lua state for API registration // Get the underlying psyqo::Lua state for API registration

View File

@@ -4,6 +4,7 @@
#include "controls.hh" #include "controls.hh"
#include "camera.hh" #include "camera.hh"
#include "cutscene.hh" #include "cutscene.hh"
#include "animation.hh"
#include "uisystem.hh" #include "uisystem.hh"
#include <psyqo/soft-math.hh> #include <psyqo/soft-math.hh>
@@ -16,18 +17,20 @@ namespace psxsplash {
// Static member // Static member
SceneManager* LuaAPI::s_sceneManager = nullptr; SceneManager* LuaAPI::s_sceneManager = nullptr;
CutscenePlayer* LuaAPI::s_cutscenePlayer = nullptr; CutscenePlayer* LuaAPI::s_cutscenePlayer = nullptr;
AnimationPlayer* LuaAPI::s_animationPlayer = nullptr;
UISystem* LuaAPI::s_uiSystem = nullptr; UISystem* LuaAPI::s_uiSystem = nullptr;
// Scale factor: FixedPoint<12> stores 1.0 as raw 4096. // Scale factor: FixedPoint<12> stores 1.0 as raw 4096.
// Lua scripts work in world-space units (1 = one unit), so we convert. // Lua scripts work in world-space units (1 = one unit), so we convert.
static constexpr lua_Number kFixedScale = 4096; static constexpr lua_Number kFixedScale = 4096;
static lua_Number fpToLua(psyqo::FixedPoint<12> fp) { // Read a FixedPoint<12> from the stack, accepting either a FixedPoint object
return static_cast<lua_Number>(fp.raw()) / kFixedScale; // or a plain integer (which gets scaled by 4096 to become fp12).
} static psyqo::FixedPoint<12> readFP(psyqo::Lua& L, int idx) {
if (L.isFixedPoint(idx)) {
static psyqo::FixedPoint<12> luaToFp(lua_Number val) { return L.toFixedPoint(idx);
return psyqo::FixedPoint<12>(static_cast<int32_t>(val * kFixedScale), psyqo::FixedPoint<12>::RAW); }
return psyqo::FixedPoint<12>(static_cast<int32_t>(L.toNumber(idx) * kFixedScale), psyqo::FixedPoint<12>::RAW);
} }
// Angle scale: psyqo::Angle is FixedPoint<10>, so 1.0_pi = raw 1024 // Angle scale: psyqo::Angle is FixedPoint<10>, so 1.0_pi = raw 1024
@@ -38,9 +41,10 @@ static psyqo::Trig<> s_trig;
// REGISTRATION // REGISTRATION
// ============================================================================ // ============================================================================
void LuaAPI::RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cutscenePlayer, UISystem* uiSystem) { void LuaAPI::RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cutscenePlayer, AnimationPlayer* animationPlayer, UISystem* uiSystem) {
s_sceneManager = scene; s_sceneManager = scene;
s_cutscenePlayer = cutscenePlayer; s_cutscenePlayer = cutscenePlayer;
s_animationPlayer = animationPlayer;
s_uiSystem = uiSystem; s_uiSystem = uiSystem;
// ======================================================================== // ========================================================================
@@ -285,6 +289,22 @@ void LuaAPI::RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cut
L.setGlobal("Cutscene"); L.setGlobal("Cutscene");
// ========================================================================
// ANIMATION API
// ========================================================================
L.newTable();
L.push(Animation_Play);
L.setField(-2, "Play");
L.push(Animation_Stop);
L.setField(-2, "Stop");
L.push(Animation_IsPlaying);
L.setField(-2, "IsPlaying");
L.setGlobal("Animation");
// ======================================================================== // ========================================================================
// CONTROLS API // CONTROLS API
// ======================================================================== // ========================================================================
@@ -598,26 +618,28 @@ int LuaAPI::Entity_GetRotationY(lua_State* L) {
angle.value = a; angle.value = a;
} }
// Return in pi-units: 0.5 = π/2 = 90° // Return as FixedPoint<12> (Angle is FixedPoint<10>, shift left 2 for fp12)
lua.pushNumber(static_cast<lua_Number>(angle.value) / kAngleScale); psyqo::FixedPoint<12> fp12;
fp12.value = angle.value << 2;
lua.push(fp12);
return 1; return 1;
} }
int LuaAPI::Entity_SetRotationY(lua_State* L) { int LuaAPI::Entity_SetRotationY(lua_State* L) {
psyqo::Lua lua(L); psyqo::Lua lua(L);
if (!lua.isTable(1)) return 0; if (!lua.isTable(1)) return 0;
lua.getField(1, "__cpp_ptr"); lua.getField(1, "__cpp_ptr");
auto go = lua.toUserdata<GameObject>(-1); auto go = lua.toUserdata<GameObject>(-1);
lua.pop(); lua.pop();
if (!go) return 0; if (!go) return 0;
// Accept angle in pi-units (0.5 = π/2 = 90°) // Accept FixedPoint or number, convert to Angle (FixedPoint<10>)
lua_Number piUnits = lua.toNumber(2); psyqo::FixedPoint<12> fp12 = readFP(lua, 2);
psyqo::Angle angle; psyqo::Angle angle;
angle.value = static_cast<int32_t>(piUnits * kAngleScale); angle.value = fp12.value >> 2;
go->rotation = psyqo::SoftMath::generateRotationMatrix33(angle, psyqo::SoftMath::Axis::Y, s_trig); go->rotation = psyqo::SoftMath::generateRotationMatrix33(angle, psyqo::SoftMath::Axis::Y, s_trig);
return 0; return 0;
} }
@@ -654,41 +676,41 @@ int LuaAPI::Entity_ForEach(lua_State* L) {
// VEC3 API IMPLEMENTATION // VEC3 API IMPLEMENTATION
// ============================================================================ // ============================================================================
void LuaAPI::PushVec3(psyqo::Lua& L, psyqo::FixedPoint<12> x, void LuaAPI::PushVec3(psyqo::Lua& L, psyqo::FixedPoint<12> x,
psyqo::FixedPoint<12> y, psyqo::FixedPoint<12> z) { psyqo::FixedPoint<12> y, psyqo::FixedPoint<12> z) {
L.newTable(); L.newTable();
L.pushNumber(fpToLua(x)); L.push(x);
L.setField(-2, "x"); L.setField(-2, "x");
L.pushNumber(fpToLua(y)); L.push(y);
L.setField(-2, "y"); L.setField(-2, "y");
L.pushNumber(fpToLua(z)); L.push(z);
L.setField(-2, "z"); L.setField(-2, "z");
} }
void LuaAPI::ReadVec3(psyqo::Lua& L, int idx, void LuaAPI::ReadVec3(psyqo::Lua& L, int idx,
psyqo::FixedPoint<12>& x, psyqo::FixedPoint<12>& x,
psyqo::FixedPoint<12>& y, psyqo::FixedPoint<12>& y,
psyqo::FixedPoint<12>& z) { psyqo::FixedPoint<12>& z) {
L.getField(idx, "x"); L.getField(idx, "x");
x = luaToFp(L.toNumber(-1)); x = readFP(L, -1);
L.pop(); L.pop();
L.getField(idx, "y"); L.getField(idx, "y");
y = luaToFp(L.toNumber(-1)); y = readFP(L, -1);
L.pop(); L.pop();
L.getField(idx, "z"); L.getField(idx, "z");
z = luaToFp(L.toNumber(-1)); z = readFP(L, -1);
L.pop(); L.pop();
} }
int LuaAPI::Vec3_New(lua_State* L) { int LuaAPI::Vec3_New(lua_State* L) {
psyqo::Lua lua(L); psyqo::Lua lua(L);
psyqo::FixedPoint<12> x = luaToFp(lua.optNumber(1, 0)); psyqo::FixedPoint<12> x = lua.isNoneOrNil(1) ? psyqo::FixedPoint<12>() : readFP(lua, 1);
psyqo::FixedPoint<12> y = luaToFp(lua.optNumber(2, 0)); psyqo::FixedPoint<12> y = lua.isNoneOrNil(2) ? psyqo::FixedPoint<12>() : readFP(lua, 2);
psyqo::FixedPoint<12> z = luaToFp(lua.optNumber(3, 0)); psyqo::FixedPoint<12> z = lua.isNoneOrNil(3) ? psyqo::FixedPoint<12>() : readFP(lua, 3);
PushVec3(lua, x, y, z); PushVec3(lua, x, y, z);
return 1; return 1;
} }
@@ -740,7 +762,7 @@ int LuaAPI::Vec3_Mul(lua_State* L) {
psyqo::FixedPoint<12> x, y, z; psyqo::FixedPoint<12> x, y, z;
ReadVec3(lua, 1, x, y, z); ReadVec3(lua, 1, x, y, z);
psyqo::FixedPoint<12> scalar = luaToFp(lua.toNumber(2)); psyqo::FixedPoint<12> scalar = readFP(lua, 2);
PushVec3(lua, x * scalar, y * scalar, z * scalar); PushVec3(lua, x * scalar, y * scalar, z * scalar);
return 1; return 1;
@@ -761,7 +783,7 @@ int LuaAPI::Vec3_Dot(lua_State* L) {
ReadVec3(lua, 2, bx, by, bz); ReadVec3(lua, 2, bx, by, bz);
auto dot = ax * bx + ay * by + az * bz; auto dot = ax * bx + ay * by + az * bz;
lua.pushNumber(fpToLua(dot)); lua.push(dot);
return 1; return 1;
} }
@@ -799,7 +821,7 @@ int LuaAPI::Vec3_LengthSq(lua_State* L) {
ReadVec3(lua, 1, x, y, z); ReadVec3(lua, 1, x, y, z);
auto lengthSq = x * x + y * y + z * z; auto lengthSq = x * x + y * y + z * z;
lua.pushNumber(fpToLua(lengthSq)); lua.push(lengthSq);
return 1; return 1;
} }
@@ -814,26 +836,32 @@ int LuaAPI::Vec3_Length(lua_State* L) {
psyqo::FixedPoint<12> x, y, z; psyqo::FixedPoint<12> x, y, z;
ReadVec3(lua, 1, x, y, z); ReadVec3(lua, 1, x, y, z);
// Compute length in scaled world-space to avoid fp12×fp12 overflow issues. // lengthSq in fp12: (x*x + y*y + z*z) is fp24 (two fp12 multiplied).
// Convert to Lua-number domain, sqrt there, return directly. // We need sqrt(lengthSq) as fp12.
lua_Number sx = fpToLua(x); // lengthSq raw = sum of (raw*raw >> 12) values = fp12 result
lua_Number sy = fpToLua(y); auto lengthSq = x * x + y * y + z * z;
lua_Number sz = fpToLua(z); int32_t lsRaw = lengthSq.raw();
lua_Number sqVal = sx * sx + sy * sy + sz * sz;
if (lsRaw <= 0) {
if (sqVal <= 0) { lua.push(psyqo::FixedPoint<12>());
lua.pushNumber(0);
return 1; return 1;
} }
// Newton's method sqrt (integer-safe) // Integer sqrt of (lsRaw << 12) to get result in fp12
lua_Number guess = sqVal / 2; // sqrt(fp12_value) = sqrt(raw/4096) = sqrt(raw)/64
if (guess == 0) guess = 1; // So: result_raw = isqrt(raw * 4096) = isqrt(raw << 12)
for (int i = 0; i < 12; i++) { // isqrt(lsRaw) gives integer sqrt. Multiply by 64 (sqrt(4096)) to get fp12.
guess = (guess + sqVal / guess) / 2; // Newton's method in 32-bit: isqrt(n)
uint32_t n = (uint32_t)lsRaw;
uint32_t guess = n;
for (int i = 0; i < 16; i++) {
if (guess == 0) break;
guess = (guess + n / guess) / 2;
} }
// guess = isqrt(lsRaw). lsRaw is in fp12, so sqrt needs * sqrt(4096) = 64
lua.pushNumber(guess); psyqo::FixedPoint<12> result;
result.value = (int32_t)(guess * 64);
lua.push(result);
return 1; return 1;
} }
@@ -848,26 +876,31 @@ int LuaAPI::Vec3_Normalize(lua_State* L) {
psyqo::FixedPoint<12> x, y, z; psyqo::FixedPoint<12> x, y, z;
ReadVec3(lua, 1, x, y, z); ReadVec3(lua, 1, x, y, z);
// Work in Lua-number (world-space) domain for the sqrt auto lengthSq = x * x + y * y + z * z;
lua_Number sx = fpToLua(x); int32_t lsRaw = lengthSq.raw();
lua_Number sy = fpToLua(y);
lua_Number sz = fpToLua(z); if (lsRaw <= 0) {
lua_Number sLen = sx * sx + sy * sy + sz * sz; PushVec3(lua, psyqo::FixedPoint<12>(), psyqo::FixedPoint<12>(), psyqo::FixedPoint<12>());
if (sLen <= 0) {
PushVec3(lua, psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0));
return 1; return 1;
} }
// Newton's method sqrt // isqrt(lsRaw) * 64 = length in fp12
lua_Number guess = sLen / 2; uint32_t n = (uint32_t)lsRaw;
if (guess == 0) guess = 1; uint32_t guess = n;
for (int i = 0; i < 12; i++) { for (int i = 0; i < 16; i++) {
guess = (guess + sLen / guess) / 2; if (guess == 0) break;
guess = (guess + n / guess) / 2;
} }
if (guess == 0) guess = 1; int32_t len = (int32_t)(guess * 64);
if (len == 0) len = 1;
PushVec3(lua, luaToFp(sx / guess), luaToFp(sy / guess), luaToFp(sz / guess));
// Divide each component by length: component / length in fp12
// (x.raw * 4096) / len using 32-bit math (safe since raw values fit int16 range)
psyqo::FixedPoint<12> nx, ny, nz;
nx.value = (x.raw() * 4096) / len;
ny.value = (y.raw() * 4096) / len;
nz.value = (z.raw() * 4096) / len;
PushVec3(lua, nx, ny, nz);
return 1; return 1;
} }
@@ -890,7 +923,7 @@ int LuaAPI::Vec3_DistanceSq(lua_State* L) {
auto dz = az - bz; auto dz = az - bz;
auto distSq = dx * dx + dy * dy + dz * dz; auto distSq = dx * dx + dy * dy + dz * dz;
lua.pushNumber(fpToLua(distSq)); lua.push(distSq);
return 1; return 1;
} }
@@ -908,24 +941,28 @@ int LuaAPI::Vec3_Distance(lua_State* L) {
ReadVec3(lua, 1, ax, ay, az); ReadVec3(lua, 1, ax, ay, az);
ReadVec3(lua, 2, bx, by, bz); ReadVec3(lua, 2, bx, by, bz);
lua_Number dx = fpToLua(ax) - fpToLua(bx); auto dx = ax - bx;
lua_Number dy = fpToLua(ay) - fpToLua(by); auto dy = ay - by;
lua_Number dz = fpToLua(az) - fpToLua(bz); auto dz = az - bz;
lua_Number sqVal = dx * dx + dy * dy + dz * dz; auto distSq = dx * dx + dy * dy + dz * dz;
int32_t dsRaw = distSq.raw();
if (sqVal <= 0) {
lua.pushNumber(0); if (dsRaw <= 0) {
lua.push(psyqo::FixedPoint<12>());
return 1; return 1;
} }
lua_Number guess = sqVal / 2; uint32_t n = (uint32_t)dsRaw;
if (guess == 0) guess = 1; uint32_t guess = n;
for (int i = 0; i < 12; i++) { for (int i = 0; i < 16; i++) {
guess = (guess + sqVal / guess) / 2; if (guess == 0) break;
guess = (guess + n / guess) / 2;
} }
lua.pushNumber(guess); psyqo::FixedPoint<12> result;
result.value = (int32_t)(guess * 64);
lua.push(result);
return 1; return 1;
} }
@@ -943,7 +980,7 @@ int LuaAPI::Vec3_Lerp(lua_State* L) {
ReadVec3(lua, 1, ax, ay, az); ReadVec3(lua, 1, ax, ay, az);
ReadVec3(lua, 2, bx, by, bz); ReadVec3(lua, 2, bx, by, bz);
psyqo::FixedPoint<12> t = luaToFp(lua.toNumber(3)); psyqo::FixedPoint<12> t = readFP(lua, 3);
psyqo::FixedPoint<12> oneMinusT = psyqo::FixedPoint<12>(4096, psyqo::FixedPoint<12>::RAW) - t; psyqo::FixedPoint<12> oneMinusT = psyqo::FixedPoint<12>(4096, psyqo::FixedPoint<12>::RAW) - t;
psyqo::FixedPoint<12> rx = ax * oneMinusT + bx * t; psyqo::FixedPoint<12> rx = ax * oneMinusT + bx * t;
@@ -1158,25 +1195,27 @@ int LuaAPI::Camera_LookAt(lua_State* L) {
if (lua.isTable(1)) { if (lua.isTable(1)) {
ReadVec3(lua, 1, tx, ty, tz); ReadVec3(lua, 1, tx, ty, tz);
} else { } else {
tx = luaToFp(lua.optNumber(1, 0)); tx = lua.isNoneOrNil(1) ? psyqo::FixedPoint<12>() : readFP(lua, 1);
ty = luaToFp(lua.optNumber(2, 0)); ty = lua.isNoneOrNil(2) ? psyqo::FixedPoint<12>() : readFP(lua, 2);
tz = luaToFp(lua.optNumber(3, 0)); tz = lua.isNoneOrNil(3) ? psyqo::FixedPoint<12>() : readFP(lua, 3);
} }
auto& cam = s_sceneManager->getCamera(); auto& cam = s_sceneManager->getCamera();
auto& pos = cam.GetPosition(); auto& pos = cam.GetPosition();
// Compute direction vector from camera to target // Compute direction vector from camera to target
lua_Number dx = fpToLua(tx) - fpToLua(pos.x); auto dx = tx - pos.x;
lua_Number dy = fpToLua(ty) - fpToLua(pos.y); auto dy = ty - pos.y;
lua_Number dz = fpToLua(tz) - fpToLua(pos.z); auto dz = tz - pos.z;
// Compute horizontal distance for pitch calculation // Compute horizontal distance for pitch calculation
lua_Number horizDistSq = dx * dx + dz * dz; auto horizDistSq = dx * dx + dz * dz;
lua_Number horizGuess = horizDistSq / 2; int32_t hdsRaw = horizDistSq.raw();
if (horizGuess == 0) horizGuess = 1; uint32_t hn = (uint32_t)(hdsRaw > 0 ? hdsRaw : 1);
for (int i = 0; i < 12; i++) { uint32_t horizGuess = hn;
horizGuess = (horizGuess + horizDistSq / horizGuess) / 2; for (int i = 0; i < 16; i++) {
if (horizGuess == 0) break;
horizGuess = (horizGuess + hn / horizGuess) / 2;
} }
// Yaw = atan2(dx, dz) — approximate with lookup or use psyqo trig // Yaw = atan2(dx, dz) — approximate with lookup or use psyqo trig
@@ -1554,6 +1593,67 @@ int LuaAPI::Cutscene_IsPlaying(lua_State* L) {
return 1; return 1;
} }
// ============================================================================
// ANIMATION API IMPLEMENTATION
// ============================================================================
int LuaAPI::Animation_Play(lua_State* L) {
psyqo::Lua lua(L);
if (!s_animationPlayer || !lua.isString(1)) {
return 0;
}
const char* name = lua.toString(1);
bool loop = false;
int onCompleteRef = LUA_NOREF;
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();
}
}
s_animationPlayer->setLuaState(L);
s_animationPlayer->play(name, loop);
if (onCompleteRef != LUA_NOREF) {
s_animationPlayer->setOnCompleteRef(name, onCompleteRef);
}
return 0;
}
int LuaAPI::Animation_Stop(lua_State* L) {
psyqo::Lua lua(L);
if (!s_animationPlayer) return 0;
if (lua.isString(1)) {
s_animationPlayer->stop(lua.toString(1));
} else {
s_animationPlayer->stopAll();
}
return 0;
}
int LuaAPI::Animation_IsPlaying(lua_State* L) {
psyqo::Lua lua(L);
if (s_animationPlayer && lua.isString(1)) {
lua.push(s_animationPlayer->isPlaying(lua.toString(1)));
} else {
lua.push(false);
}
return 1;
}
// ============================================================================ // ============================================================================
// CONTROLS API IMPLEMENTATION // CONTROLS API IMPLEMENTATION
// ============================================================================ // ============================================================================

View File

@@ -8,6 +8,7 @@ namespace psxsplash {
class SceneManager; // Forward declaration class SceneManager; // Forward declaration
class CutscenePlayer; // Forward declaration class CutscenePlayer; // Forward declaration
class AnimationPlayer; // Forward declaration
class UISystem; // Forward declaration class UISystem; // Forward declaration
/** /**
@@ -25,7 +26,7 @@ class UISystem; // Forward declaration
class LuaAPI { class LuaAPI {
public: public:
// Initialize all API modules // Initialize all API modules
static void RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cutscenePlayer = nullptr, UISystem* uiSystem = nullptr); static void RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cutscenePlayer = nullptr, AnimationPlayer* animationPlayer = nullptr, UISystem* uiSystem = nullptr);
// Called once per frame to advance the Lua frame counter // Called once per frame to advance the Lua frame counter
static void IncrementFrameCount(); static void IncrementFrameCount();
@@ -39,6 +40,9 @@ private:
// Cutscene player pointer (set during RegisterAll) // Cutscene player pointer (set during RegisterAll)
static CutscenePlayer* s_cutscenePlayer; static CutscenePlayer* s_cutscenePlayer;
// Animation player pointer (set during RegisterAll)
static AnimationPlayer* s_animationPlayer;
// UI system pointer (set during RegisterAll) // UI system pointer (set during RegisterAll)
static UISystem* s_uiSystem; static UISystem* s_uiSystem;
@@ -271,6 +275,19 @@ private:
// Cutscene.IsPlaying() -> boolean // Cutscene.IsPlaying() -> boolean
static int Cutscene_IsPlaying(lua_State* L); static int Cutscene_IsPlaying(lua_State* L);
// ========================================================================
// ANIMATION API - Multi-instance animation playback
// ========================================================================
// Animation.Play(name) or Animation.Play(name, {loop=bool, onComplete=fn})
static int Animation_Play(lua_State* L);
// Animation.Stop(name) -> nil
static int Animation_Stop(lua_State* L);
// Animation.IsPlaying(name) -> boolean
static int Animation_IsPlaying(lua_State* L);
// Controls.SetEnabled(bool) - enable/disable all player input // Controls.SetEnabled(bool) - enable/disable all player input
static int Controls_SetEnabled(lua_State* L); static int Controls_SetEnabled(lua_State* L);

View File

@@ -44,7 +44,7 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
m_audio.init(); m_audio.init();
// Register the Lua API // Register the Lua API
LuaAPI::RegisterAll(L.getState(), this, &m_cutscenePlayer, &m_uiSystem); LuaAPI::RegisterAll(L.getState(), this, &m_cutscenePlayer, &m_animationPlayer, &m_uiSystem);
#ifdef PSXSPLASH_PROFILER #ifdef PSXSPLASH_PROFILER
debug::Profiler::getInstance().initialize(); debug::Profiler::getInstance().initialize();
@@ -113,6 +113,19 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
&m_uiSystem &m_uiSystem
); );
// Copy animation data into scene manager storage
m_animationCount = sceneSetup.animationCount;
for (int i = 0; i < m_animationCount; i++) {
m_animations[i] = sceneSetup.loadedAnimations[i];
}
// Initialize animation player
m_animationPlayer.init(
m_animationCount > 0 ? m_animations : nullptr,
m_animationCount,
&m_uiSystem
);
// Initialize UI system (v13+) // Initialize UI system (v13+)
if (sceneSetup.uiCanvasCount > 0 && sceneSetup.uiTableOffset != 0 && s_font != nullptr) { if (sceneSetup.uiCanvasCount > 0 && sceneSetup.uiTableOffset != 0 && s_font != nullptr) {
m_uiSystem.init(*s_font); m_uiSystem.init(*s_font);
@@ -157,6 +170,35 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
} }
} }
} }
// Resolve UI track handles for animation tracks (same logic)
for (int ai = 0; ai < m_animationCount; ai++) {
for (uint8_t ti = 0; ti < m_animations[ai].trackCount; ti++) {
auto& track = m_animations[ai].tracks[ti];
bool isUI = static_cast<uint8_t>(track.trackType) >= 5;
if (!isUI || track.target == nullptr) continue;
const char* nameStr = reinterpret_cast<const char*>(track.target);
track.target = nullptr;
if (track.trackType == TrackType::UICanvasVisible) {
track.uiHandle = static_cast<int16_t>(m_uiSystem.findCanvas(nameStr));
} else {
const char* sep = nameStr;
while (*sep && *sep != '/') sep++;
if (*sep == '/') {
char* mutableSep = const_cast<char*>(sep);
*mutableSep = '\0';
int canvasIdx = m_uiSystem.findCanvas(nameStr);
*mutableSep = '/';
if (canvasIdx >= 0) {
track.uiHandle = static_cast<int16_t>(
m_uiSystem.findElement(canvasIdx, sep + 1));
}
}
}
}
}
} else { } else {
Renderer::GetInstance().SetUISystem(nullptr); Renderer::GetInstance().SetUISystem(nullptr);
} }
@@ -242,7 +284,15 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
for (auto object : m_gameObjects) { for (auto object : m_gameObjects) {
L.RegisterGameObject(object); L.RegisterGameObject(object);
} }
// Fire all onCreate events AFTER all objects are registered,
// so Entity.Find works across all objects in onCreate handlers.
if (!m_gameObjects.empty()) {
L.FireAllOnCreate(
reinterpret_cast<GameObject**>(m_gameObjects.data()),
m_gameObjects.size());
}
m_controls.forceAnalogMode(); m_controls.forceAnalogMode();
m_controls.Init(); m_controls.Init();
@@ -262,7 +312,8 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
LuaAPI::IncrementFrameCount(); LuaAPI::IncrementFrameCount();
m_cutscenePlayer.tick(); m_cutscenePlayer.tick();
m_animationPlayer.tick();
{ {
uint32_t now = gpu.now(); uint32_t now = gpu.now();
if (m_lastFrameTime != 0) { if (m_lastFrameTime != 0) {
@@ -279,7 +330,7 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
int camRoom = -1; int camRoom = -1;
if (m_navRegions.isLoaded()) { if (m_navRegions.isLoaded()) {
if (m_cutscenePlayer.isPlaying()) { if (m_cutscenePlayer.isPlaying() && m_cutscenePlayer.hasCameraTracks()) {
auto& camPos = m_currentCamera.GetPosition(); auto& camPos = m_currentCamera.GetPosition();
uint16_t camRegion = m_navRegions.findRegion(camPos.x.value, camPos.z.value); uint16_t camRegion = m_navRegions.findRegion(camPos.x.value, camPos.z.value);
if (camRegion != NAV_NO_REGION) { if (camRegion != NAV_NO_REGION) {
@@ -478,7 +529,7 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
// (no nav regions / no PSXPlayer), the camera is driven entirely // (no nav regions / no PSXPlayer), the camera is driven entirely
// by cutscenes and Lua. After a cutscene ends in free mode, the // by cutscenes and Lua. After a cutscene ends in free mode, the
// camera stays at the last cutscene position. // camera stays at the last cutscene position.
if (m_cameraFollowsPlayer && !m_cutscenePlayer.isPlaying()) { if (m_cameraFollowsPlayer && !(m_cutscenePlayer.isPlaying() && m_cutscenePlayer.hasCameraTracks())) {
m_currentCamera.SetPosition(static_cast<psyqo::FixedPoint<12>>(m_playerPosition.x), 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.y),
static_cast<psyqo::FixedPoint<12>>(m_playerPosition.z)); static_cast<psyqo::FixedPoint<12>>(m_playerPosition.z));
@@ -647,7 +698,6 @@ void psxsplash::SceneManager::processEnableDisableEvents() {
// ============================================================================ // ============================================================================
void psxsplash::SceneManager::requestSceneLoad(int sceneIndex) { void psxsplash::SceneManager::requestSceneLoad(int sceneIndex) {
if (sceneIndex == m_currentSceneIndex) return;
m_pendingSceneIndex = sceneIndex; m_pendingSceneIndex = sceneIndex;
} }
@@ -763,6 +813,8 @@ void psxsplash::SceneManager::clearScene() {
m_cutsceneCount = 0; m_cutsceneCount = 0;
s_activePromptCanvas = -1; // Reset prompt tracking s_activePromptCanvas = -1; // Reset prompt tracking
m_cutscenePlayer.init(nullptr, 0, nullptr, nullptr); // Reset cutscene player m_cutscenePlayer.init(nullptr, 0, nullptr, nullptr); // Reset cutscene player
m_animationCount = 0;
m_animationPlayer.init(nullptr, 0); // Reset animation player
// BVH 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) // Reset UI system (disconnect from renderer before splashpack data disappears)
@@ -825,6 +877,16 @@ void psxsplash::SceneManager::shrinkBuffer() {
} }
} }
for (int ai = 0; ai < m_animationCount; ai++) {
auto& an = m_animations[ai];
an.name = reloc(an.name);
for (uint8_t ti = 0; ti < an.trackCount; ti++) {
auto& track = an.tracks[ti];
track.keyframes = reloc(track.keyframes);
if (track.target) track.target = reloc(track.target);
}
}
m_uiSystem.relocate(delta); m_uiSystem.relocate(delta);
if (!m_gameObjects.empty()) { if (!m_gameObjects.empty()) {

View File

@@ -19,6 +19,7 @@
#include "luaapi.hh" #include "luaapi.hh"
#include "fileloader.hh" #include "fileloader.hh"
#include "cutscene.hh" #include "cutscene.hh"
#include "animation.hh"
#include "uisystem.hh" #include "uisystem.hh"
#ifdef PSXSPLASH_MEMOVERLAY #ifdef PSXSPLASH_MEMOVERLAY
#include "memoverlay.hh" #include "memoverlay.hh"
@@ -146,6 +147,10 @@ class SceneManager {
Cutscene m_cutscenes[MAX_CUTSCENES]; Cutscene m_cutscenes[MAX_CUTSCENES];
int m_cutsceneCount = 0; int m_cutsceneCount = 0;
CutscenePlayer m_cutscenePlayer; CutscenePlayer m_cutscenePlayer;
Animation m_animations[MAX_ANIMATIONS];
int m_animationCount = 0;
AnimationPlayer m_animationPlayer;
UISystem m_uiSystem; UISystem m_uiSystem;
#ifdef PSXSPLASH_MEMOVERLAY #ifdef PSXSPLASH_MEMOVERLAY

View File

@@ -64,8 +64,11 @@ struct SPLASHPACKFileHeader {
uint8_t uiPad5; uint8_t uiPad5;
uint32_t uiTableOffset; uint32_t uiTableOffset;
uint32_t pixelDataOffset; uint32_t pixelDataOffset;
uint16_t animationCount;
uint16_t animPad;
uint32_t animationTableOffset;
}; };
static_assert(sizeof(SPLASHPACKFileHeader) == 104, "SPLASHPACKFileHeader must be 104 bytes"); static_assert(sizeof(SPLASHPACKFileHeader) == 112, "SPLASHPACKFileHeader must be 112 bytes");
struct SPLASHPACKTextureAtlas { struct SPLASHPACKTextureAtlas {
uint32_t polygonsOffset; uint32_t polygonsOffset;
@@ -85,7 +88,7 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
psyqo::Kernel::assert(data != nullptr, "Splashpack loading data pointer is null"); psyqo::Kernel::assert(data != nullptr, "Splashpack loading data pointer is null");
psxsplash::SPLASHPACKFileHeader *header = reinterpret_cast<psxsplash::SPLASHPACKFileHeader *>(data); psxsplash::SPLASHPACKFileHeader *header = reinterpret_cast<psxsplash::SPLASHPACKFileHeader *>(data);
psyqo::Kernel::assert(__builtin_memcmp(header->magic, "SP", 2) == 0, "Splashpack has incorrect magic"); psyqo::Kernel::assert(__builtin_memcmp(header->magic, "SP", 2) == 0, "Splashpack has incorrect magic");
psyqo::Kernel::assert(header->version >= 16, "Splashpack version too old (need v16+): re-export from SplashEdit"); psyqo::Kernel::assert(header->version >= 17, "Splashpack version too old (need v17+): re-export from SplashEdit");
setup.playerStartPosition = header->playerStartPos; setup.playerStartPosition = header->playerStartPos;
setup.playerStartRotation = header->playerStartRot; setup.playerStartRotation = header->playerStartRot;
@@ -364,6 +367,84 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
setup.uiTableOffset = header->uiTableOffset; setup.uiTableOffset = header->uiTableOffset;
} }
// Animation loading (v17+)
if (header->animationCount > 0 && header->animationTableOffset != 0) {
setup.animationCount = 0;
uint8_t* tablePtr = data + header->animationTableOffset;
int anCount = header->animationCount;
if (anCount > MAX_ANIMATIONS) anCount = MAX_ANIMATIONS;
for (int ai = 0; ai < anCount; ai++) {
// SPLASHPACKAnimationEntry: 12 bytes (same layout as cutscene entry)
uint32_t dataOffset = *reinterpret_cast<uint32_t*>(tablePtr); tablePtr += 4;
uint8_t nameLen = *tablePtr++;
tablePtr += 3; // pad
uint32_t nameOffset = *reinterpret_cast<uint32_t*>(tablePtr); tablePtr += 4;
Animation& an = setup.loadedAnimations[ai];
an.name = (nameLen > 0 && nameOffset != 0)
? reinterpret_cast<const char*>(data + nameOffset)
: nullptr;
// SPLASHPACKAnimation: 8 bytes (no audio)
uint8_t* anPtr = data + dataOffset;
an.totalFrames = *reinterpret_cast<uint16_t*>(anPtr); anPtr += 2;
an.trackCount = *anPtr++;
an.pad = *anPtr++;
uint32_t tracksOff = *reinterpret_cast<uint32_t*>(anPtr); anPtr += 4;
if (an.trackCount > MAX_ANIM_TRACKS) an.trackCount = MAX_ANIM_TRACKS;
// Parse tracks (same format as cutscene tracks)
uint8_t* trackPtr = data + tracksOff;
for (uint8_t ti = 0; ti < an.trackCount; ti++) {
CutsceneTrack& track = an.tracks[ti];
track.trackType = static_cast<TrackType>(*trackPtr++);
track.keyframeCount = *trackPtr++;
uint8_t objNameLen = *trackPtr++;
trackPtr++; // pad
uint32_t objNameOff = *reinterpret_cast<uint32_t*>(trackPtr); trackPtr += 4;
uint32_t kfOff = *reinterpret_cast<uint32_t*>(trackPtr); trackPtr += 4;
track.keyframes = (track.keyframeCount > 0 && kfOff != 0)
? reinterpret_cast<CutsceneKeyframe*>(data + kfOff)
: nullptr;
track.target = nullptr;
track.uiHandle = -1;
if (objNameLen > 0 && objNameOff != 0) {
const char* objName = reinterpret_cast<const char*>(data + objNameOff);
bool isUITrack = static_cast<uint8_t>(track.trackType) >= 5;
if (isUITrack) {
track.target = reinterpret_cast<GameObject*>(const_cast<char*>(objName));
} else {
for (size_t oi = 0; oi < setup.objectNames.size(); oi++) {
if (setup.objectNames[oi] &&
streq(setup.objectNames[oi], objName)) {
track.target = setup.objects[oi];
break;
}
}
}
}
}
// Zero unused track slots
for (uint8_t ti = an.trackCount; ti < MAX_ANIM_TRACKS; ti++) {
an.tracks[ti].keyframeCount = 0;
an.tracks[ti].keyframes = nullptr;
an.tracks[ti].target = nullptr;
an.tracks[ti].uiHandle = -1;
an.tracks[ti].initialValues[0] = 0;
an.tracks[ti].initialValues[1] = 0;
an.tracks[ti].initialValues[2] = 0;
}
setup.animationCount++;
}
}
setup.liveDataSize = header->pixelDataOffset; setup.liveDataSize = header->pixelDataOffset;
} }

View File

@@ -12,6 +12,7 @@
#include "audiomanager.hh" #include "audiomanager.hh"
#include "interactable.hh" #include "interactable.hh"
#include "cutscene.hh" #include "cutscene.hh"
#include "animation.hh"
#include "uisystem.hh" #include "uisystem.hh"
namespace psxsplash { namespace psxsplash {
@@ -94,6 +95,9 @@ struct SplashpackSceneSetup {
Cutscene loadedCutscenes[MAX_CUTSCENES]; Cutscene loadedCutscenes[MAX_CUTSCENES];
int cutsceneCount = 0; int cutsceneCount = 0;
Animation loadedAnimations[MAX_ANIMATIONS];
int animationCount = 0;
uint16_t uiCanvasCount = 0; uint16_t uiCanvasCount = 0;
uint8_t uiFontCount = 0; uint8_t uiFontCount = 0;
uint32_t uiTableOffset = 0; uint32_t uiTableOffset = 0;