diff --git a/configure.py b/configure.py index 9509ae3..2587e98 100644 --- a/configure.py +++ b/configure.py @@ -462,7 +462,7 @@ def MatchingFor(*versions): Object(NonMatching, "ty/source/props/LavaBurner.cpp"), Object(NonMatching, "ty/source/props/LilyPad.cpp"), Object(NonMatching, "ty/source/props/Log.cpp"), - Object(NonMatching, "ty/source/props/MobilePlatform.cpp"), + Object(Matching, "ty/source/props/MobilePlatform.cpp"), Object(NonMatching, "ty/source/props/MorayEel.cpp"), Object(NonMatching, "ty/source/props/MusicalIcicle.cpp"), Object(NonMatching, "ty/source/props/OpalCollector.cpp"), diff --git a/include/common/MKSceneManager.h b/include/common/MKSceneManager.h index 0f2cd0d..24a84cd 100644 --- a/include/common/MKSceneManager.h +++ b/include/common/MKSceneManager.h @@ -19,6 +19,7 @@ // these are the values used in GameObject::Message #define MKMSG_Reset -1 #define MKMSG_Deinit -2 +#define MKMSG_UNK_3 -3 struct MKMessage { int unk0; }; diff --git a/include/ty/props/MobilePlatform.h b/include/ty/props/MobilePlatform.h new file mode 100644 index 0000000..1045ebb --- /dev/null +++ b/include/ty/props/MobilePlatform.h @@ -0,0 +1,64 @@ +#ifndef MOBILEPLATFORM_H +#define MOBILEPLATFORM_H + +#include "ty/props/Platform.h" +#include "ty/CommonGameObjectFlags.h" +#include "ty/Spline.h" +#include "ty/tools.h" + +void MobilePlatform_LoadResources(KromeIni* pIni); + +struct MobilePlatformDesc : PlatformDesc { + float bobSpeed; // Unofficial: unkF0. Number of bobs per second + float bobHeight; // Unofficial: unkF4 + + virtual void Init(ModuleInfoBase* pMod, char* pMdlName, char* pDescrName, int _searchMask, int _flags); +}; + +// Temporary info used while loading a MobilePlatform +struct MobilePlatformLoadInfo { + Tools_WayPoints wayPoints; // Unofficial: unk0 + float speed; + float startTime; // Normalised starting position on the path [0, 1] + bool bNonCircular; +}; + +struct MobilePlatform : Platform { + float turnSpeed; + int bReverse; // Unofficial: unkD8. Set when moving backwards along a non-circular path + int pathFrames; // Unofficial: unkDC. Number of frames to travel the path once + int startFrame; // Unofficial: unkE0 + int defaultStartFrame; // Unofficial: unkE4 + int frameOffset; // Unofficial: unkE8 + int bobFrameOffset; // Unofficial: unkEC + UniformSpline spline; // Unofficial: unkF0 + bool bCircular; + bool bMoveOne; // Deactivates once the end of the path is reached + CommonGameObjFlagsComponent gameObjFlags; // Unofficial: unk106 + + static MobilePlatformLoadInfo mpfLoadInfo; // Unofficial + + virtual bool LoadLine(KromeIniLine* pLine); + virtual void LoadDone(void); + virtual void Reset(void); + virtual void Update(void); + virtual void Message(MKMessage* pMsg); + virtual void Init(GameObjDesc* pDesc); + virtual void Deinit(void); + void UpdateMove(void); + void UpdateBob(void); + float GetTime(void); + void SetYaw(float time, float maxTurn); // Unofficial: time, maxTurn + + MobilePlatformDesc* GetDesc(void) { + return descr_cast(pDescriptor); + } + + // Returns the number of frames of a full cycle + // Non-circular paths travel forwards then backwards + uint GetCycleFrames(void) { + return pathFrames * (!bCircular + 1); + } +}; + +#endif // MOBILEPLATFORM_H diff --git a/src/ty/source/main.cpp b/src/ty/source/main.cpp index c739a98..fa28da9 100644 --- a/src/ty/source/main.cpp +++ b/src/ty/source/main.cpp @@ -12,6 +12,7 @@ #include "ty/Mist.h" #include "ty/props/AnimatingProp.h" +#include "ty/props/MobilePlatform.h" #include "ty/props/Shatterable.h" #include "ty/props/WeatherProp.h" @@ -118,6 +119,7 @@ void Main_LoadStaticResources(void) { Shatterable_LoadResources(&ini); Platform_LoadResources(&ini); + MobilePlatform_LoadResources(&ini); AnimatingProp_LoadResources(&ini); TyAttributes_LoadResources(&ini); diff --git a/src/ty/source/props/MobilePlatform.cpp b/src/ty/source/props/MobilePlatform.cpp new file mode 100644 index 0000000..98cdeca --- /dev/null +++ b/src/ty/source/props/MobilePlatform.cpp @@ -0,0 +1,223 @@ +#include "ty/props/MobilePlatform.h" +#include "ty/GameObjectManager.h" +#include "ty/global.h" +#include "common/StdMath.h" +#include "common/System_GC.h" + +extern "C" double atan2(double, double); +void GameCamera_AddDynamicCollisionItem(Model* pModel, int subObjectIndex); + +static ModuleInfo mobilePlatformModuleInfo; // Unofficial +MobilePlatformLoadInfo MobilePlatform::mpfLoadInfo; + +void MobilePlatformDesc::Init(ModuleInfoBase* pMod, char* pMdlName, char* pDescrName, int _searchMask, int _flags) { + PlatformDesc::Init(pMod, pMdlName, pDescrName, _searchMask, _flags | 0x80); + bobSpeed = 0.0f; + bobHeight = 0.0f; +} + +void MobilePlatform_LoadResources(KromeIni* pIni) { // Unofficial: pIni + MobilePlatformDesc defaultMobilePlatformDesc; // Unofficial + defaultMobilePlatformDesc.Init(&mobilePlatformModuleInfo, "", "", GOID_Platform, 1); + LoadDescriptors(pIni, "MobilePlatforms", &defaultMobilePlatformDesc); +} + +void MobilePlatform::Init(GameObjDesc* pDesc) { + Platform::Init(pDesc); + mpfLoadInfo.wayPoints.Init(); + mpfLoadInfo.bNonCircular = false; + mpfLoadInfo.speed = 7.0f; + mpfLoadInfo.startTime = 0.0f; + bReverse = 0; + bCircular = false; + bMoveOne = false; + pathFrames = 0; + turnSpeed = 0.0f; + startFrame = 0; + bobFrameOffset = 0; + gameObjFlags.Init(GameObjFlags_All); +} + +void MobilePlatform::Deinit(void) { + spline.Deinit(); + Platform::Deinit(); +} + +void MobilePlatform::Reset(void) { + Platform::Reset(); + gameObjFlags.Reset(); + startFrame = defaultStartFrame; + UpdateMove(); +} + +bool MobilePlatform::LoadLine(KromeIniLine* pLine) { + return gameObjFlags.LoadLine(pLine) || + LoadLevel_LoadBool(pLine, "bCircular", &bCircular) || + LoadLevel_LoadFloat(pLine, "turnSpeed", &turnSpeed) || + LoadLevel_LoadFloat(pLine, "speed", &mpfLoadInfo.speed) || + LoadLevel_LoadFloat(pLine, "startTime", &mpfLoadInfo.startTime) || + LoadLevel_LoadBool(pLine, "bMoveOne", &bMoveOne) || + LoadLevel_LoadBool(pLine, "bNonCircular", &mpfLoadInfo.bNonCircular) || + mpfLoadInfo.wayPoints.LoadLine(pLine, Tools_WayPoints::LOAD_MODE_1) || + Platform::LoadLine(pLine); +} + +void MobilePlatform::LoadDone(void) { + gameObjFlags.SetDefaultFlags(); + + GetPos()->Copy(&mpfLoadInfo.wayPoints.vecs[0]); + + if (mpfLoadInfo.wayPoints.unk104 > 1) { + if (mpfLoadInfo.wayPoints.unk104 > 2 && !mpfLoadInfo.bNonCircular) { + bCircular = true; + } + + // Face the platform towards the second waypoint + float dx = mpfLoadInfo.wayPoints.vecs[1].x - mpfLoadInfo.wayPoints.vecs[0].x; // Unofficial + float dz = mpfLoadInfo.wayPoints.vecs[1].z - mpfLoadInfo.wayPoints.vecs[0].z; // Unofficial + if (Sqr(dx) + Sqr(dz) > Sqr(0.01f) && turnSpeed > 0.0f) { + StaticProp::loadInfo.defaultRot.y = PI2 + (float)atan2(dz, dx); + } + + spline.Init(mpfLoadInfo.wayPoints.unk104 + bCircular, true); + + for (int i = 0; i < mpfLoadInfo.wayPoints.unk104; i++) { // Unofficial: i + spline.AddNode(&mpfLoadInfo.wayPoints.vecs[i]); + } + + if (bCircular) { + spline.AddNode(&mpfLoadInfo.wayPoints.vecs[0]); + spline.MergeEnds(); + } else { + spline.mpPoints[0].unk10.SetZero(); + spline.mpPoints[spline.mNumPoints - 1].unk10.SetZero(); + } + + spline.RegulateSpeed(); + + pathFrames = gDisplay.fps * (spline.unk8 / mpfLoadInfo.speed); + } else { + spline.Init(mpfLoadInfo.wayPoints.unk104, false); + spline.AddNode(&mpfLoadInfo.wayPoints.vecs[0]); + } + + startFrame = pathFrames * Clamp(0.0f, mpfLoadInfo.startTime, 1.0f); + defaultStartFrame = startFrame; + frameOffset = startFrame - gb.logicGameCount; + + if (GetDesc()->bobSpeed) { + bobFrameOffset = (gDisplay.fps * RandomFR(&gb.mRandSeed, 0.0f, 1.0f)) / GetDesc()->bobSpeed; + } + + GameCamera_AddDynamicCollisionItem(pModel, -1); + Platform::LoadDone(); +} + +void MobilePlatform::Update(void) { + BeginUpdate(); + UpdateTilt(); + UpdateMove(); + UpdateBob(); + EndUpdate(); +} + +void MobilePlatform::Message(MKMessage* pMsg) { + switch (pMsg->unk0) { + case MSG_Activate: + if (!gameObjFlags.CheckFlags(GameObjFlags_Active)) { + frameOffset = startFrame - gb.logicGameCount; + } + break; + case MSG_Deactivate: + if (gameObjFlags.CheckFlags(GameObjFlags_Active)) { + startFrame = (frameOffset + gb.logicGameCount) % GetCycleFrames(); + } + break; + case MSG_Resolve: + rider.Resolve(); + rider.Attach(this); + for (int i = 0; i < spline.mNumPoints; i++) { // Unofficial: i + rider.ToLocal(&spline.mpPoints[i].mPos); + rider.ToLocalDir(&spline.mpPoints[i].unk10); + } + break; + case MKMSG_UNK_3: + if (turnSpeed > 0.0f && spline.mNumPoints > 1) { + BeginUpdate(); + SetYaw(GetTime(), 2.0f * PI); + EndUpdate(); + } + break; + } + + gameObjFlags.Message(pMsg); + Platform::Message(pMsg); +} + +void MobilePlatform::UpdateMove(void) { + if (spline.mNumPoints > 1) { + float time = GetTime(); // Unofficial + *GetPos() = spline.GetPosition(time); + GetPos()->w = 1.0f; + rider.ToWorld(GetPos()); + + if (turnSpeed > 0.0f) { + SetYaw(time, turnSpeed); + } + + if (bMoveOne && gameObjFlags.CheckFlags(GameObjFlags_Active) && time > 0.99f && bReverse == 0) { + MKMessage msg = {MSG_Deactivate}; // Unofficial + Message(&msg); + } + } else { + *GetPos() = spline.mpPoints[0].mPos; + GetPos()->w = 1.0f; + rider.ToWorld(GetPos()); + } +} + +void MobilePlatform::UpdateBob(void) { + if (GetDesc()->bobHeight > 0.0f && GetDesc()->bobSpeed > 0.0f) { + int bobFrames = gDisplay.fps / GetDesc()->bobSpeed; // Unofficial + float t = (float)((gb.logicGameCount + bobFrameOffset) % bobFrames) / (float)bobFrames; // Unofficial: t + GetPos()->y += _table_sinf((2.0f * PI) * t) * GetDesc()->bobHeight; + } +} + +float MobilePlatform::GetTime(void) { + if (!gameObjFlags.CheckFlags(GameObjFlags_Active)) { + frameOffset = startFrame - gb.logicGameCount; + } + + float time = (float)((gb.logicGameCount + frameOffset) % GetCycleFrames()) / (float)pathFrames; // Unofficial: time + + if (time > 1.0f) { + bReverse = 1; + time = 2.0f - time; + } else { + bReverse = 0; + } + + return time; +} + +void MobilePlatform::SetYaw(float time, float maxTurn) { // Unofficial: time, maxTurn + if (spline.mNumPoints > 0) { + Vector dir = spline.GetVelocity(time); // Unofficial + rider.ToWorldDir(&dir); + dir.y = 0.0f; + + if (dir.MagSquared() > Sqr(0.01f)) { + float yaw = atan2(dir.z, dir.x); // Unofficial + float direction; // Unofficial + if (bReverse == 0) { + direction = 1.0f; + } else { + direction = -1.0f; + } + + float angle = GetSmallestAngle(mCurrRot.y, (2.0f * PI) * direction + (PI2 + yaw)); // Unofficial: angle + mCurrRot.y += Clamp(-Abs(maxTurn), angle, Abs(maxTurn)); + } + } +}