diff options
| author | MegaMech <MegaMech@users.noreply.github.com> | 2025-11-09 19:07:44 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-09 19:07:44 -0700 |
| commit | de9c5d510175bba11ab8704e67be3a3a94f9149e (patch) | |
| tree | 34c42dbfe9f2a6eb5fbce5f6a31b532ff2a3f243 /src/engine | |
| parent | 760fa3bf5226c2a6052a5d4d53fd0e27f2911e60 (diff) | |
Implement SpawnParams struct (#536)
* Impl SpawnParams
* Added json submodule
* Update json
* Update
* Works
* Remove comment
* Works refactor
* Snowman and thwomp working
* Impl hot air balloon
* More progress
* All OObjects are done
* cleanup
* Refactor 2Dpath to normal path
* Update nlohmann json & fix compile
* Rest of actors
* MORE CHANGES
* Finish actors
* Done PR, some fix to collision viewer
* Impl falling rocks
* Add const
* wip editor refactor
* Property work
* continue
* Overridable editor properties
* Actor saving/loading works now
* Fix light alignment
* Clarification
* Impl penguin
* params impl signs
* properties impl falling rock
* More property impls
* impl air balloon
* Add spawnParams to OObject Translate
* Snowman translate better
* impl hedgehog properly
* properties impl trophy
* thwomp progress
* Finish impl properties
* Fix compile
* Fix cursor collisions
* Move registered actors
* Rename pathPoint XYZ to xyz
* Fix editor pause bug
* Clean up
* Review comments
* Remove SpawnParams struct from actor classes
* Rename
* Player Label First Iteration
* Work now
* Working 3d text
* Fix boo bug
* Finish AText actor
* Fix spawnparams compile
* Register AText
* Finish Text Actor
* Fix thwomp interpolation
* Fix compile
* Fix crab and hedgehog
* Fix loading flagpole
* Fix Hot Air Balloon
* Turn zbuffer on for AText
* Update
---------
Co-authored-by: MegaMech <7255464+MegaMech@users.noreply.github.com>
Diffstat (limited to 'src/engine')
132 files changed, 5432 insertions, 1504 deletions
diff --git a/src/engine/Actor.cpp b/src/engine/Actor.cpp index f3254fed0..c2526f392 100644 --- a/src/engine/Actor.cpp +++ b/src/engine/Actor.cpp @@ -1,13 +1,35 @@ #include <libultraship.h> #include "Matrix.h" - #include "Actor.h" +#include "engine/World.h" + +// Editor +#include "engine/editor/Collision.h" extern "C" { #include "math_util.h" } AActor::AActor() {} +AActor::AActor(SpawnParams params) { + ResourceName = "mk:actor"; // This needs to be overridden in derived classes + SpawnPos = params.Location.value_or(FVector{0.0f, 0.0f, 0.0f}); + SpawnRot = params.Rotation.value_or(IRotator{0, 0, 0}); + SpawnScale = params.Scale.value_or(FVector(0, 0, 0)); + Speed = params.Speed.value_or(0.0f); +} + +void AActor::BeginPlay() { + // This makes actors clickable in the editor + if (CVarGetInteger("gEditorEnabled", false) == true) { + if ((nullptr != Model) && (Model[0] != '\0')) { + // Prevent collision mesh from being generated extra times. + if (Triangles.size() == 0) { + Editor::GenerateCollisionMesh(this, (Gfx*)LOAD_ASSET_RAW(Model), 1.0f); + } + } + } +} // Virtual functions to be overridden by derived classes void AActor::Tick() { } @@ -20,15 +42,14 @@ void AActor::Draw(Camera *camera) { ApplyMatrixTransformations(mtx, *(FVector*)Pos, *(IRotator*)Rot, Scale); if (render_set_position(mtx, 0) != 0) { - gSPDisplayList(gDisplayListHead++, Model); + gSPDisplayList(gDisplayListHead++, (Gfx*)Model); } } } void AActor::Collision(Player* player, AActor* actor) {} void AActor::VehicleCollision(s32 playerId, Player* player){} void AActor::Destroy() { - // Set uuid to zero. - memset(uuid, 0, sizeof(uuid)); + bPendingDestroy = true; } bool AActor::IsMod() { return false; } void AActor::SetLocation(FVector pos) { @@ -39,3 +60,41 @@ void AActor::SetLocation(FVector pos) { FVector AActor::GetLocation() const { return FVector(Pos[0], Pos[1], Pos[2]); } + +IRotator AActor::GetRotation() const { + IRotator rot; + rot.Set(Rot[0], Rot[1], Rot[2]); + return rot; +} + +FVector AActor::GetScale() const { + return Scale; +} + +void AActor::SetSpawnParams(SpawnParams& params) { + params.Name = ResourceName; + params.Location = SpawnPos; + params.Rotation = SpawnRot; + params.Scale = SpawnScale; + params.Speed = Speed; +} + +void AActor::Translate(FVector pos) { + SpawnPos = pos; + Pos[0] = pos.x; + Pos[1] = pos.y; + Pos[2] = pos.z; +} + +void AActor::Rotate(IRotator rot) { + SpawnRot = rot; + Rot[0] = rot.pitch; + Rot[1] = rot.yaw; + Rot[2] = rot.roll; +} + +void AActor::SetScale(FVector scale) { + SpawnScale = scale; + Scale = scale; +} + diff --git a/src/engine/Actor.h b/src/engine/Actor.h index fb0a9f89e..0cbd294cd 100644 --- a/src/engine/Actor.h +++ b/src/engine/Actor.h @@ -1,7 +1,8 @@ #pragma once #include <libultraship.h> -#include "CoreMath.h" +#include "engine/SpawnParams.h" +#include "engine/editor/EditorMath.h" extern "C" { #include "macros.h" @@ -9,10 +10,8 @@ extern "C" { #include "camera.h" #include "common_structs.h" - class AActor { public: - /* 0x00 */ s16 Type = 0; /* 0x02 */ s16 Flags; /* 0x04 */ s16 Unk_04; @@ -24,25 +23,51 @@ public: /* 0x18 */ Vec3f Pos; /* 0x24 */ Vec3f Velocity = {0, 0, 0}; /* 0x30 */ Collision Unk30; + /* 0x */ const char* Model = ""; uint8_t uuid[16]; const char* Name = ""; + const char* ResourceName = ""; + FVector SpawnPos = {0.0f, 0.0f, 0.0f}; + IRotator SpawnRot = {0, 0, 0}; + FVector SpawnScale = {1.0f, 1.0f, 1.0f}; + FVector Scale = {1, 1, 1}; + float Speed = 0.0f; + std::vector<Triangle> Triangles; - Gfx* Model = NULL; + bool bPendingDestroy = false; virtual ~AActor() = default; // Virtual destructor for proper cleanup in derived classes explicit AActor(); + explicit AActor(SpawnParams params); + /** + * Make sure you call this in derived classes! + * Usage: + * MyActor::SetSpawnParams(SetSpawnParams& params) { + * AActor::SetSpawnParams(params); // Calls default implementation + * } + */ + virtual void SetSpawnParams(SpawnParams& params); + virtual void BeginPlay(); virtual void Tick(); virtual void Draw(Camera*); virtual void Collision(Player* player, AActor* actor); virtual void VehicleCollision(s32 playerId, Player* player); void SetLocation(FVector pos); - FVector GetLocation() const; virtual void Destroy(); virtual bool IsMod(); + + /** Editor functions **/ + FVector GetLocation() const; + IRotator GetRotation() const; + FVector GetScale() const; + void Translate(FVector pos); + void Rotate(IRotator rot); + void SetScale(FVector scale); + virtual void DrawEditorProperties() { DrawDefaultEditorProperties(); }; }; -}
\ No newline at end of file +} diff --git a/src/engine/AllActors.h b/src/engine/AllActors.h index f2b228b57..e408e86b8 100644 --- a/src/engine/AllActors.h +++ b/src/engine/AllActors.h @@ -1,5 +1,7 @@ #pragma once +#include "actors/Banana.h" +#include "actors/FallingRock.h" #include "actors/MarioSign.h" #include "actors/WarioSign.h" #include "actors/Cloud.h" @@ -8,6 +10,7 @@ #include "actors/Starship.h" #include "actors/Ship.h" #include "actors/Tree.h" +#include "actors/Text.h" #include "vehicles/Train.h" #include "vehicles/Boat.h" #include "vehicles/Bus.h" diff --git a/src/engine/CoreMath.h b/src/engine/CoreMath.h index 2c232aab9..626b498ae 100644 --- a/src/engine/CoreMath.h +++ b/src/engine/CoreMath.h @@ -1,6 +1,10 @@ #ifndef CORE_MATH_H #define CORE_MATH_H +#ifdef __cplusplus +#include <nlohmann/json.hpp> +#endif + #include <libultraship.h> /** @@ -10,6 +14,14 @@ * */ +struct RGBA8 { + uint8_t r, g, b, a; +#ifdef __cplusplus + NLOHMANN_DEFINE_TYPE_INTRUSIVE(RGBA8, r, g, b, a) +#endif +}; + + /** * * Applies pos, rot, and scale @@ -63,6 +75,7 @@ struct FVector { FVector() : x(0), y(0), z(0) {} FVector(float x, float y, float z) : x(x), y(y), z(z) {} + NLOHMANN_DEFINE_TYPE_INTRUSIVE(FVector, x, y, z) #endif // __cplusplus }; @@ -94,6 +107,7 @@ struct FVector2D { FVector2D() : x(0), z(0) {} FVector2D(float x, float z) : x(x), z(z) {} + NLOHMANN_DEFINE_TYPE_INTRUSIVE(FVector2D, x, z) #endif // __cplusplus }; @@ -118,12 +132,14 @@ typedef struct IVector2D { /** * This struct immediately converts float pitch/yaw/roll in degrees to n64 int16_t binary angles 0-0xFFFF == 0-360 degrees * ToDegrees() Receive an FRotator of float degrees back. - * Set() Set an n64 int16_t binary angles 0-0xFFFF + * Set() to update an IRotator using n64 int16_t binary angles 0-0xFFFF (ex. IRotator.Set(0, 0x4000, 0) for Y 90 degrees) */ struct IRotator { uint16_t pitch, yaw, roll; #ifdef __cplusplus + NLOHMANN_DEFINE_TYPE_INTRUSIVE(IRotator, pitch, yaw, roll) + IRotator& operator=(const IRotator& other) { pitch = other.pitch; yaw = other.yaw; @@ -158,7 +174,7 @@ struct IRotator { /** * Use IRotator unless you want to do some math in degrees. - * Always use ToBinary() or Rotator when sending into matrices or apply translation functions + * Always use ToBinary() or IRotator when sending into matrices or apply translation functions * Convert from IRotator to FRotator float degrees by doing FRotator(myIRotator); */ struct FRotator { @@ -196,9 +212,10 @@ struct FRotator { * Usage: IPathSpan(point1, point2) --> IPathSpan(40, 65) */ struct IPathSpan { - int Start, End; + int32_t Start, End; #ifdef __cplusplus + NLOHMANN_DEFINE_TYPE_INTRUSIVE(IPathSpan, Start, End) // Default Constructor IPathSpan() : Start(0), End(0) {} diff --git a/src/engine/GarbageCollector.cpp b/src/engine/GarbageCollector.cpp index 9832aa29d..e7f2d9975 100644 --- a/src/engine/GarbageCollector.cpp +++ b/src/engine/GarbageCollector.cpp @@ -2,21 +2,30 @@ #include "World.h" void RunGarbageCollector() { - //CleanActors(); + CleanActors(); CleanObjects(); CleanStaticMeshActors(); } void CleanActors() { - // for (auto actor = gWorldInstance.Actors.begin(); actor != gWorldInstance.Actors.end();) { - // OObject* act = *actor; // Get a mutable copy - // if (act->PendingDestroy) { - // delete act; - // actor = gWorldInstance.Objects.erase(actor); // Remove from container - // continue; - // } - // actor++; - // } + for (auto actor = gWorldInstance.Actors.begin(); actor != gWorldInstance.Actors.end();) { + AActor* act = *actor; // Get a mutable copy + if (act->bPendingDestroy) { + if (act->IsMod()) { // C++ actor + delete act; + actor = gWorldInstance.Actors.erase(actor); // Remove from container + } else { // Old C actor + act->Flags = 0; + act->Type = 0; + act->Name = ""; + act->ResourceName = ""; + actor++; // Manually advance the iterator since no deletion happens here + } + gNumActors -= 1; + continue; + } + actor++; + } } void CleanStaticMeshActors() { diff --git a/src/engine/HM_Intro.cpp b/src/engine/HM_Intro.cpp index 51d3d2bbd..1e05d1639 100644 --- a/src/engine/HM_Intro.cpp +++ b/src/engine/HM_Intro.cpp @@ -65,8 +65,6 @@ void HarbourMastersIntro::HM_InitIntro() { 0x7F, 0x30, 0x80, 0x60, 20, 10, 0x49, 0x49, 0x49 ); - - //gEditor.AddObject("lus", &lusPos, &lusRot, &lusScale, nullptr, 1, Editor::GameObject::CollisionType::BOUNDING_BOX, 10, &DespawnValue, -1); } void HarbourMastersIntro::HM_TickIntro() { diff --git a/src/engine/Matrix.cpp b/src/engine/Matrix.cpp index 954b0fd90..44a9a1799 100644 --- a/src/engine/Matrix.cpp +++ b/src/engine/Matrix.cpp @@ -119,6 +119,56 @@ void ApplyMatrixTransformations(Mat4 mtx, FVector pos, IRotator rot, FVector sca mtx[3][3] = 1.0f; } +/* + * Spherical billboarding + * Rotates the object to face the camera + * Rotates on all three axis + */ +void ApplySphericalBillBoard(Mat4 mat, FVector pos, FVector scale, s32 cameraIndex) { + Mtx* lookAt = GetLookAtMatrix(cameraIndex); + Mat4 lookAtF; + guMtxL2F((float(*)[4])&lookAtF, lookAt); + + // Camera Right + mat[0][0] = lookAtF[0][0]; + mat[1][0] = lookAtF[0][1]; + mat[2][0] = lookAtF[0][2]; + mat[3][0] = 0; + + // Camera Up + mat[0][1] = lookAtF[1][0]; + mat[1][1] = lookAtF[1][1]; + mat[2][1] = lookAtF[1][2]; + mat[3][1] = 0; + + // Camera Forward + mat[0][2] = lookAtF[2][0]; + mat[1][2] = lookAtF[2][1]; + mat[2][2] = lookAtF[2][2]; + mat[3][2] = 0; + + mat[0][3] = 0; + mat[1][3] = 0; + mat[2][3] = 0; + mat[3][3] = 1; + + // Set position + mat[3][0] = pos.x; + mat[3][1] = pos.y; + mat[3][2] = pos.z; + + // Apply scaling + mat[0][0] *= scale.x; + mat[1][0] *= scale.x; + mat[2][0] *= scale.x; + mat[0][1] *= scale.y; + mat[1][1] *= scale.y; + mat[2][1] *= scale.y; + mat[0][2] *= scale.z; + mat[1][2] *= scale.z; + mat[2][2] *= scale.z; +} + void AddLocalRotation(Mat4 mat, IRotator rot) { f32 sin_pitch = sins(rot.pitch); f32 cos_pitch = coss(rot.pitch); @@ -141,7 +191,6 @@ void AddLocalRotation(Mat4 mat, IRotator rot) { mat[2][2] = (cos_pitch * cos_yaw); } - // API extern "C" { diff --git a/src/engine/Matrix.h b/src/engine/Matrix.h index ede94b5b0..c58f41343 100644 --- a/src/engine/Matrix.h +++ b/src/engine/Matrix.h @@ -9,6 +9,7 @@ #ifdef __cplusplus extern "C" { void ApplyMatrixTransformations(Mat4 mtx, FVector pos, IRotator rot, FVector scale); +void ApplySphericalBillBoard(Mat4 mat, FVector pos, FVector scale, s32 cameraIndex); void AddLocalRotation(Mat4 mat, IRotator rot); #endif void ClearMatrixPools(void); diff --git a/src/engine/RegisteredActors.cpp b/src/engine/RegisteredActors.cpp new file mode 100644 index 000000000..7e7f0a11e --- /dev/null +++ b/src/engine/RegisteredActors.cpp @@ -0,0 +1,372 @@ +#include "SpawnParams.h" +#include "engine/CoreMath.h" +#include "Registry.h" +#include "engine/World.h" + +#include "AllActors.h" + +extern "C" { +#include "common_structs.h" +#include "actors.h" +#include "actor_types.h" +} + +void RegisterGameActors() { + RegisterActor("mk:item_box", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + spawn_item_box(pos); + } + ); + + RegisterActor("mk:fake_item_box", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + spawn_fake_item_box(pos); + } + ); + + RegisterActor("mk:thwomp", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OThwomp(params)); + } + ); + + RegisterActor("mk:snowman", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OSnowman(params)); + } + ); + + RegisterActor("mk:hot_air_balloon", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OHotAirBalloon(params)); + } + ); + + RegisterActor("mk:hedgehog", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OHedgehog(params)); + } + ); + + RegisterActor("mk:grand_prix_balloons", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OGrandPrixBalloons(params)); + } + ); + + RegisterActor("mk:flagpole", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OFlagpole(params)); + } + ); + + RegisterActor("mk:crab", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OCrab(params)); + } + ); + + RegisterActor("mk:cheep_cheep", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OCheepCheep(params)); + } + ); + + RegisterActor("mk:bomb_kart", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OBombKart(params)); + } + ); + + RegisterActor("mk:bat", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OBat(params)); + } + ); + + RegisterActor("mk:boos", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OBoos(params)); + } + ); + + RegisterActor("mk:trophy", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OTrophy(params)); + } + ); + + RegisterActor("mk:trash_bin", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OTrashBin(params)); + } + ); + + RegisterActor("mk:seagull", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OSeagull(params)); + } + ); + + RegisterActor("mk:chain_chomp", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OChainChomp()); + } + ); + + RegisterActor("mk:podium", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OPodium(params)); + } + ); + + RegisterActor("mk:penguin", + [](const SpawnParams& params) { + gWorldInstance.AddObject(new OPenguin(params)); + } + ); + + RegisterActor("mk:banana", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new ABanana(params)); + } + ); + + RegisterActor("mk:mario_sign", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new AMarioSign(params)); + } + ); + + RegisterActor("mk:wario_sign", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new AWarioSign(params)); + } + ); + + RegisterActor("mk:falling_rock", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new AFallingRock(params)); + } + ); + + RegisterActor("mk:yoshi_egg", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_YOSHI_EGG); + } + ); + + RegisterActor("mk:piranha_plant", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_PIRANHA_PLANT); + } + ); + + RegisterActor("mk:tree_mario_raceway", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_TREE_MARIO_RACEWAY); + } + ); + + RegisterActor("mk:tree_yoshi_valley", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_TREE_YOSHI_VALLEY); + } + ); + + RegisterActor("mk:tree_royal_raceway", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_TREE_ROYAL_RACEWAY); + } + ); + + RegisterActor("mk:tree_moo_moo_farm", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_TREE_MOO_MOO_FARM); + } + ); + + RegisterActor("mk:palm_tree", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_PALM_TREE); + } + ); + + RegisterActor("mk:unknown_0x1a", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_TREE_LUIGI_RACEWAY); + } + ); + + RegisterActor("mk:unknown_0x1b", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_UNKNOWN_0x1B); + } + ); + + RegisterActor("mk:tree_peach_castle", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_TREE_PEACH_CASTLE); + } + ); + + RegisterActor("mk:tree_frappe_snowland", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_TREE_FRAPPE_SNOWLAND); + } + ); + + RegisterActor("mk:cactus1_kalamari_desert", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_CACTUS1_KALAMARI_DESERT); + } + ); + + RegisterActor("mk:cactus2_kalamari_desert", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_CACTUS2_KALAMARI_DESERT); + } + ); + + RegisterActor("mk:cactus3_kalamari_desert", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_CACTUS3_KALAMARI_DESERT); + } + ); + + RegisterActor("mk:bush_bowsers_castle", + [](const SpawnParams& params) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + Vec3f pos = { loc.x, loc.y, loc.z }; + Vec3s rot = {0, 0, 0}; + Vec3f vel = {0, 0, 0}; + add_actor_to_empty_slot(pos, rot, vel, ACTOR_BUSH_BOWSERS_CASTLE); + } + ); + + RegisterActor("mk:train", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new ATrain(params)); + } + ); + + RegisterActor("mk:paddle_boat", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new ABoat(params)); + } + ); + + RegisterActor("mk:car", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new ACar(params)); + } + ); + + RegisterActor("mk:truck", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new ATankerTruck(params)); + } + ); + + RegisterActor("mk:tanker_truck", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new ATankerTruck(params)); + } + ); + + RegisterActor("mk:bus", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new ATankerTruck(params)); + } + ); + + RegisterActor("hm:spaghetti_ship", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new ASpaghettiShip(params)); + } + ); + + RegisterActor("hm:ship", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new AShip(params)); + } + ); + + RegisterActor("hm:starship", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new AStarship(params)); + } + ); + + RegisterActor("hm:cloud", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new ACloud(params)); + } + ); + + RegisterActor("hm:text", + [](const SpawnParams& params) { + gWorldInstance.AddActor(new AText(params)); + } + ); +} diff --git a/src/engine/RegisteredActors.h b/src/engine/RegisteredActors.h new file mode 100644 index 000000000..6315bef5a --- /dev/null +++ b/src/engine/RegisteredActors.h @@ -0,0 +1,3 @@ +#pragma once + +void RegisterGameActors(); diff --git a/src/engine/Registry.cpp b/src/engine/Registry.cpp index 0ea05c9d5..9a68264d8 100644 --- a/src/engine/Registry.cpp +++ b/src/engine/Registry.cpp @@ -1,59 +1,32 @@ -// #include <vector> -// #include <functional> -// #include <iostream> -// #include <map> - -// #include "Registry.h" -// #include "Course.h" -// #include "port/Game.h" - -// template <class T> Registry<T>::Registry() { -// } - -// template <class T> void Registry<T>::Add(std::string id, std::function<T*()> fn) { -// // need to handle duplicate registration -// ContentVault[id] = fn; -// } - -// template <class T> -// void Registry<T>::AddArgs(std::string id) { -// ArgsVault[id] = [](Args&&... args) -> T* { -// return new T(std::forward<Args>(args)...); -// }; -// } - -// template <class T> -// T* Registry<T>::Get(std::string id) { -// auto it = ContentVault.find(id); -// if (it != ContentVault.end()) { -// return it->second(); -// } -// return nullptr; -// } - -// // Available Registries -// Registry<Course> Courses; -// Registry<Cup> Cups; -// Registry<AActor> Actors; - -// void AddCourse(std::string id, Course* course) { -// Courses.Add(id, [course]() { return course; }); -// course->Id = id; -// } - -// void AddCup(std::string id, Cup* cup) { -// Cups.Add(id, [cup]() { return cup; }); -// //cup->Id = id; -// } - -// void AddActor(std::string id, AActor* actor) { -// Actors.Add(id, [actor]() { return actor; }); -// } - -// void AddStockContent() { -// AddActor("mk:banana", new AMarioSign({0, 0, 0})); -// } - -// template class Registry<Course>; -// template class Registry<Cup>; -// template class Registry<AActor>; +#include <functional> +#include <unordered_map> +#include <string> + +#include "Registry.h" +#include "engine/CoreMath.h" + +extern "C" { +#include "actors.h" +#include "actor_types.h" +} + +std::unordered_map<std::string, ActorRegistryEntry> gActorRegistry; + +void RegisterActor(const std::string& name, + std::function<void(const SpawnParams&)> spawnFunc) +{ + gActorRegistry[name] = { spawnFunc }; +} + +void Registry_SpawnActor(SpawnParams& params) { + auto it = gActorRegistry.find(params.Name); + if (it != gActorRegistry.end() && it->second.spawnFunc) { + printf("[Registry] Spawned %s\n", params.Name.c_str()); + it->second.spawnFunc(params); + } +} + +// @arg name Must be a resource name such as mk:car +bool Registry_Find(const std::string& name) { + return gActorRegistry.find(name) != gActorRegistry.end(); +} diff --git a/src/engine/Registry.h b/src/engine/Registry.h index f55818dc3..c6ee95be2 100644 --- a/src/engine/Registry.h +++ b/src/engine/Registry.h @@ -1,33 +1,14 @@ -// #pragma once +#pragma once -// #include "Course.h" -// #include "Cup.h" -// #include <unordered_map> -// #include "AllActors.h" -// #include "Actor.h" -// #include "objects/Object.h" +#include <libultraship.h> +#include "SpawnParams.h" -// class AActor; // <-- Forward delare +struct ActorRegistryEntry { + std::function<void(const SpawnParams&)> spawnFunc; +}; -// template <class T> class Registry { -// public: -// Registry(); -// void Add(std::string name, std::function<T*()>); - -// template <typename... Args> -// void AddArgs(std::string id); -// T* Get(std::string id); -// private: -// std::unordered_map<std::string, std::function<T*()>> ContentVault; -// std::map<std::string, std::function<T*(Args&&...)>> ArgsVault; -// }; +extern std::unordered_map<std::string, ActorRegistryEntry> gActorRegistry; -// void AddCourse(std::string id, Course* course); -// void AddCup(std::string id, Cup* cup); -// void AddActor(std::string id, AActor* actor); - -// void AddStockContent(); - -// extern Registry<Course> Courses; -// extern Registry<Cup> Cups; -// extern Registry<AActor> Actors; +void Registry_SpawnActor(SpawnParams& params); +void RegisterActor(const std::string& name, + std::function<void(const SpawnParams&)> spawnFunc); diff --git a/src/engine/Rulesets.cpp b/src/engine/Rulesets.cpp index acdbfde2a..23ea0a6cf 100644 --- a/src/engine/Rulesets.cpp +++ b/src/engine/Rulesets.cpp @@ -1,6 +1,8 @@ #include "Rulesets.h"
#include "objects/Thwomp.h"
#include "objects/Trophy.h"
+#include "objects/BombKart.h"
+#include "actors/Text.h"
extern "C" {
#include "code_800029B0.h"
@@ -29,7 +31,7 @@ void Rulesets::PostInit() { for (auto object : gWorldInstance.Objects) {
if (OThwomp* thwomp = dynamic_cast<OThwomp*>(object)) {
gObjectList[thwomp->_objectIndex].unk_0D5 = OThwomp::States::JAILED; // Sets all the thwomp behaviour flags to marty
- thwomp->State = OThwomp::States::JAILED;
+ thwomp->Behaviour = OThwomp::States::JAILED;
}
}
}
@@ -37,12 +39,37 @@ void Rulesets::PostInit() { if (CVarGetInteger("gAllBombKartsChase", false) == true) {
for (auto object : gWorldInstance.Objects) {
if (OBombKart* kart = dynamic_cast<OBombKart*>(object)) {
- kart->State = OBombKart::States::CHASE;
+ kart->Behaviour = OBombKart::States::CHASE;
}
}
}
if (CVarGetInteger("gGoFish", false) == true) {
- gWorldInstance.AddObject(new OTrophy(FVector(0,0,0), OTrophy::TrophyType::GOLD, OTrophy::Behaviour::GO_FISH));
+ OTrophy::Spawn(FVector(0,0,0), OTrophy::TrophyType::GOLD, OTrophy::Behaviour::GO_FISH);
+ }
+
+ if (CVarGetInteger("gPlayerNames", false) == true) {
+
+ std::string playerNames[NUM_PLAYERS] = {
+ "Player 1", "Player 2", "Player 3",
+ "Player 4", "Player 5", "Player 6",
+ "Player 7", "Player 8"
+ };
+
+ for (size_t i = 0; i < NUM_PLAYERS; i++) {
+ // text, pos, scale, mode, playerIndex
+ AText* text = AText::Spawn(playerNames[i], FVector(0, 0, 0), FVector(0.15f, 0.15f, 0.15f), AText::TextMode::FOLLOW_PLAYER, i);
+ text->ScaleX = 1.0f;
+ text->Scale.x = 0.15f;
+ text->Scale.y = 0.15f;
+ text->Scale.z = 0.15f;
+ text->Animate = false; // Cycle between colours similar to grand prix title text
+
+ // White
+ for (size_t j = 0; j < 4; j++) {
+ text->TextColour[j] = {255, 255, 255, 255};
+ }
+
+ }
}
}
diff --git a/src/engine/SpawnParams.h b/src/engine/SpawnParams.h new file mode 100644 index 000000000..1b87ea03a --- /dev/null +++ b/src/engine/SpawnParams.h @@ -0,0 +1,132 @@ +#pragma once + +#include <vector> +#include <optional> +#include <string> +#include <nlohmann/json.hpp> +#include "CoreMath.h" + +extern "C" { +#include "common_structs.h" +} + +// Helper function to handle std::optional deserialization +template <typename T> +void get_optional_to(const nlohmann::json& j, const char* key, std::optional<T>& opt_val) { + if (j.contains(key) && !j.at(key).is_null()) { + opt_val = j.at(key).get<T>(); + } +} + +// Helper function to handle std::optional serialization +template <typename T> +void set_optional_from(nlohmann::json& j, const char* key, const std::optional<T>& opt_val) { + if (opt_val.has_value()) { + j[key] = opt_val.value(); + } +} + +// Used to save and load all game actors to the scene file +struct SpawnParams { + std::string Name; // Must use format mk:actor_name for stock game, mymodname:myactorname for mods + std::optional<int16_t> Type; // OObject type (ex. Emperor penguin, sliding penguin) or literal actor type for AActors + std::optional<int16_t> Behaviour; + std::optional<std::string> Skin; + + std::optional<FVector> Location; + std::optional<IRotator> Rotation; // int16_t + std::optional<FVector> Scale; + std::optional<FVector> Velocity; // Used by some AActors + std::optional<FVector2D> PatrolStart; // OCrab + std::optional<FVector2D> PatrolEnd; // OCrab & Hedgehog + std::optional<IPathSpan> PathSpan; // Cheep Cheep + + // Thwomps + std::optional<int16_t> PrimAlpha; // Thwomp + std::optional<uint16_t> BoundingBoxSize; + + // Boos + std::optional<uint32_t> Count; // vehicles + std::optional<IPathSpan> LeftExitSpan; // Disable boo + std::optional<IPathSpan> TriggerSpan; // Activate boos + std::optional<IPathSpan> RightExitSpan; // Disable boo + + + // Vehicles + std::optional<uint32_t> PathIndex; // 0-3 Place vehicle this path + std::optional<uint32_t> PathPoint; // Path point index + std::optional<bool> Bool; // train tender + std::optional<bool> Bool2; + std::optional<float> Speed; // Train + std::optional<float> SpeedB; // cars, trucks, buses, etc. + std::optional<FVector> FVec2; + + std::optional<RGBA8> Colour; + std::optional<RGBA8> Colour2; + std::optional<RGBA8> Colour3; + std::optional<RGBA8> Colour4; + + void from_json(const nlohmann::json& j) { + j.at("Name").get_to(Name); + get_optional_to(j, "Type", Type); + get_optional_to(j, "Behaviour", Behaviour); + get_optional_to(j, "Skin", Skin); + get_optional_to(j, "Location", Location); + get_optional_to(j, "Rotation", Rotation); + get_optional_to(j, "Scale", Scale); + get_optional_to(j, "Velocity", Velocity); + get_optional_to(j, "PatrolStart", PatrolStart); + get_optional_to(j, "PatrolEnd", PatrolEnd); + get_optional_to(j, "PathSpan", PathSpan); + get_optional_to(j, "PrimAlpha", PrimAlpha); + get_optional_to(j, "BoundingBoxSize", BoundingBoxSize); + get_optional_to(j, "Count", Count); + get_optional_to(j, "LeftExitSpan", LeftExitSpan); + get_optional_to(j, "TriggerSpan", TriggerSpan); + get_optional_to(j, "RightExitSpan", RightExitSpan); + get_optional_to(j, "PathIndex", PathIndex); + get_optional_to(j, "PathPoint", PathPoint); + get_optional_to(j, "Bool", Bool); + get_optional_to(j, "Bool2", Bool2); + get_optional_to(j, "Speed", Speed); + get_optional_to(j, "SpeedB", SpeedB); + get_optional_to(j, "FVec2", FVec2); + get_optional_to(j, "Colour", Colour); + get_optional_to(j, "Colour2", Colour2); + get_optional_to(j, "Colour3", Colour3); + get_optional_to(j, "Colour4", Colour4); + } + + nlohmann::json to_json() const { + nlohmann::json j; + j["Name"] = Name; + set_optional_from(j, "Type", Type); + set_optional_from(j, "Behaviour", Behaviour); + set_optional_from(j, "Skin", Skin); + set_optional_from(j, "Location", Location); + set_optional_from(j, "Rotation", Rotation); + set_optional_from(j, "Scale", Scale); + set_optional_from(j, "Velocity", Velocity); + set_optional_from(j, "PatrolStart", PatrolStart); + set_optional_from(j, "PatrolEnd", PatrolEnd); + set_optional_from(j, "PathSpan", PathSpan); + set_optional_from(j, "PrimAlpha", PrimAlpha); + set_optional_from(j, "BoundingBoxSize", BoundingBoxSize); + set_optional_from(j, "Count", Count); + set_optional_from(j, "LeftExitSpan", LeftExitSpan); + set_optional_from(j, "TriggerSpan", TriggerSpan); + set_optional_from(j, "RightExitSpan", RightExitSpan); + set_optional_from(j, "PathIndex", PathIndex); + set_optional_from(j, "PathPoint", PathPoint); + set_optional_from(j, "Bool", Bool); + set_optional_from(j, "Bool2", Bool2); + set_optional_from(j, "Speed", Speed); + set_optional_from(j, "SpeedB", SpeedB); + set_optional_from(j, "FVec2", FVec2); + set_optional_from(j, "Colour", Colour); + set_optional_from(j, "Colour2", Colour2); + set_optional_from(j, "Colour3", Colour3); + set_optional_from(j, "Colour4", Colour4); + return j; + } +}; diff --git a/src/engine/StaticMeshActor.cpp b/src/engine/StaticMeshActor.cpp index 2e5fa1119..2ebad5b0c 100644 --- a/src/engine/StaticMeshActor.cpp +++ b/src/engine/StaticMeshActor.cpp @@ -9,6 +9,8 @@ extern "C" { } StaticMeshActor::StaticMeshActor(std::string name, FVector pos, IRotator rot, FVector scale, std::string model, int32_t* collision) : Name(name), Pos(pos), Rot(rot), Scale(scale), Model(""), Collision(collision) { + Name = "StaticMesh"; + ResourceName = "hm:static_mesh"; } diff --git a/src/engine/StaticMeshActor.h b/src/engine/StaticMeshActor.h index 4007aa2c1..f745a63f1 100644 --- a/src/engine/StaticMeshActor.h +++ b/src/engine/StaticMeshActor.h @@ -9,6 +9,7 @@ class StaticMeshActor { public: std::string Name; + std::string ResourceName; FVector Pos; IRotator Rot; FVector Scale; diff --git a/src/engine/World.cpp b/src/engine/World.cpp index 100b1275d..0430edf6d 100644 --- a/src/engine/World.cpp +++ b/src/engine/World.cpp @@ -126,14 +126,7 @@ void World::PreviousCourse() { AActor* World::AddActor(AActor* actor) { Actors.push_back(actor); - - if (actor->Model != NULL) { - gEditor.AddObject(actor->Name, (FVector*) &actor->Pos, (IRotator*)&actor->Rot, &actor->Scale, - (Gfx*) LOAD_ASSET_RAW(actor->Model), 1.0f, Editor::GameObject::CollisionType::VTX_INTERSECT, - 0.0f, (int32_t*) &actor->Type, 0); - } else { - gEditor.AddObject(actor->Name, (FVector*) &actor->Pos, (IRotator*)&actor->Rot, &actor->Scale, nullptr, 1.0f, Editor::GameObject::CollisionType::VTX_INTERSECT, 0.0f, (int32_t*)&actor->Type, 0); - } + actor->BeginPlay(); return Actors.back(); } @@ -146,12 +139,9 @@ struct Actor* World::AddBaseActor() { return reinterpret_cast<struct Actor*>(reinterpret_cast<char*>(Actors.back()) + sizeof(void*)); } -void World::AddEditorObject(Actor* actor, const char* name) { - if (actor->model != NULL) { - gEditor.AddObject(name, (FVector*) &actor->pos, (IRotator*)&actor->rot, nullptr, (Gfx*)LOAD_ASSET_RAW(actor->model), 1.0f, Editor::GameObject::CollisionType::VTX_INTERSECT, 0.0f, (int32_t*)&actor->type, 0); - } else { - gEditor.AddObject(name, (FVector*) &actor->pos, (IRotator*)&actor->rot, nullptr, nullptr, 1.0f, Editor::GameObject::CollisionType::VTX_INTERSECT, 0.0f, (int32_t*)&actor->type, 0); - } +void World::ActorBeginPlay(Actor* actor) { + AActor* act = ConvertActorToAActor(actor); + act->BeginPlay(); } /** @@ -189,8 +179,6 @@ void World::TickActors() { StaticMeshActor* World::AddStaticMeshActor(std::string name, FVector pos, IRotator rot, FVector scale, std::string model, int32_t* collision) { StaticMeshActors.push_back(new StaticMeshActor(name, pos, rot, scale, model, collision)); auto actor = StaticMeshActors.back(); - auto gameObj = gEditor.AddObject(actor->Name.c_str(), &actor->Pos, &actor->Rot, &actor->Scale, (Gfx*) LOAD_ASSET_RAW(actor->Model.c_str()), 1.0f, - Editor::GameObject::CollisionType::VTX_INTERSECT, 0.0f, (int32_t*) &actor->bPendingDestroy, (int32_t) true); return actor; } @@ -200,29 +188,14 @@ void World::DrawStaticMeshActors() { } } -void World::DeleteStaticMeshActors() { - for (auto it = StaticMeshActors.begin(); it != StaticMeshActors.end();) { - if ((*it)->bPendingDestroy) { - delete *it; // Deallocate memory for the actor - it = StaticMeshActors.erase(it); // Remove the pointer from the vector - } else { - ++it; // Only increment the iterator if we didn't erase an element - } - } -} - OObject* World::AddObject(OObject* object) { Objects.push_back(object); - if (object->_objectIndex != -1) { - Object* cObj = &gObjectList[object->_objectIndex]; - - if (cObj->model != NULL) { - gEditor.AddObject(object->Name, (FVector*) &cObj->origin_pos[0], (IRotator*)&cObj->orientation, nullptr, (Gfx*)LOAD_ASSET_RAW(cObj->model), 1.0f, Editor::GameObject::CollisionType::VTX_INTERSECT, 0.0f, &object->_objectIndex, -1); - } else { - gEditor.AddObject(object->Name, (FVector*) &cObj->origin_pos[0], (IRotator*)&cObj->orientation, nullptr, nullptr, 1.0f, Editor::GameObject::CollisionType::VTX_INTERSECT, 0.0f, &object->_objectIndex, -1); - } - } + // This is an example of how to get the C object. + // However, nothing is being done with it, so it's been commented out. + // if (object->_objectIndex != -1) { + // Object* cObj = &gObjectList[object->_objectIndex]; + // } return Objects.back(); } @@ -267,7 +240,7 @@ void World::DrawParticles(s32 cameraId) { // Sets OObjects or AActors static member variables back to default values void World::Reset() { for (const auto& object : Objects) { - object->Reset(); + object->Reset(); // Used for OPenguin } } @@ -280,7 +253,6 @@ Object* World::GetObjectByIndex(size_t index) { } void World::ClearWorld(void) { - World::DeleteStaticMeshActors(); CM_CleanWorld(); // for (size_t i = 0; i < ARRAY_COUNT(gCollisionMesh); i++) { diff --git a/src/engine/World.h b/src/engine/World.h index bd55a5166..52a307be5 100644 --- a/src/engine/World.h +++ b/src/engine/World.h @@ -5,16 +5,8 @@ #include "engine/courses/Course.h" #include "objects/Object.h" #include "Cup.h" -#include "vehicles/Train.h" -#include "vehicles/Car.h" -#include "objects/BombKart.h" #include "PlayerBombKart.h" -#include "vehicles/Train.h" #include "TrainCrossing.h" -#include "objects/Thwomp.h" -#include "objects/Penguin.h" -#include "objects/Seagull.h" -#include "objects/Lakitu.h" #include <memory> #include <unordered_map> #include "Actor.h" @@ -33,7 +25,6 @@ class Cup; // <-- Forward declaration class OObject; class Course; class StaticMeshActor; -class AVehicle; class OBombKart; class TrainCrossing; class OLakitu; @@ -43,8 +34,8 @@ class World { typedef struct Matrix { Mtx Screen2D; // Orthogonal projection for UI, skybox, and such Mtx Ortho; - std::array<Mtx,4> Persp; - std::array<Mtx,4> LookAt; + std::array<Mtx,5> Persp; + std::array<Mtx,5> LookAt; std::array<Mtx, 8 * 4> Karts; // Eight players * four screens std::array<Mtx, 8 * 4> Shadows; // Eight players * four screens std::deque<Mtx> Hud; @@ -63,7 +54,7 @@ public: AActor* AddActor(AActor* actor); struct Actor* AddBaseActor(); - void AddEditorObject(Actor* actor, const char* name); + void ActorBeginPlay(Actor* actor); AActor* GetActor(size_t index); void TickActors(); @@ -72,7 +63,6 @@ public: void DrawStaticMeshActors(); StaticMeshActor* AddStaticMeshActor(std::string name, FVector pos, IRotator rot, FVector scale, std::string model, int32_t* collision); - void DeleteStaticMeshActors(); OObject* AddObject(OObject* object); diff --git a/src/engine/actors/Banana.cpp b/src/engine/actors/Banana.cpp index 6c7f837d1..d338d332e 100644 --- a/src/engine/actors/Banana.cpp +++ b/src/engine/actors/Banana.cpp @@ -10,19 +10,22 @@ void update_actor_banana(struct BananaActor*); void render_actor_banana(Camera*, float[4][4], struct BananaActor*); } -ABanana::ABanana(uint16_t playerId, const float pos[3], const s16 rot[3], const float velocity[3]) { +ABanana::ABanana(const SpawnParams& params) : AActor(params) { Name = "Banana"; - // Initialize the BananaActor's position, rotation, and velocity - std::copy(pos, pos + 3, Pos); - //std::copy(rot, rot + 3, this->a.rot); - std::copy(velocity, velocity + 3, Velocity); + ResourceName = "mk:banana"; + + FVector pos = params.Location.value_or(FVector(0, 0, 0)); + Pos[0] = pos.x; Pos[1] = pos.y; Pos[2] = pos.z; + + FVector vel = params.Velocity.value_or(FVector(0, 0, 0)); + Velocity[0] = vel.x; Velocity[1] = vel.y; Velocity[2] = vel.z; Type = 6; // ACTOR_BANANA Flags = -0x8000; Unk_04 = 20; State = HELD_BANANA; - PlayerId = playerId; + PlayerId = 0; // Don't remember why this is here //this->a.unk_08 = 0.0f; Flags |= 0x4000 | 0x1000; diff --git a/src/engine/actors/Banana.h b/src/engine/actors/Banana.h index d8c55e9cb..266cbf476 100644 --- a/src/engine/actors/Banana.h +++ b/src/engine/actors/Banana.h @@ -9,7 +9,7 @@ public: uint16_t PlayerId; // Constructor - ABanana(uint16_t playerId, const float pos[3], const s16 rot[3], const float velocity[3]); + ABanana(const SpawnParams& params); virtual ~ABanana() override = default; // Virtual functions to be overridden by derived classes diff --git a/src/engine/actors/BowserStatue.cpp b/src/engine/actors/BowserStatue.cpp index c680c4c25..af9036fc3 100644 --- a/src/engine/actors/BowserStatue.cpp +++ b/src/engine/actors/BowserStatue.cpp @@ -14,6 +14,7 @@ Gfx gBowserStatueGfx[162]; ABowserStatue::ABowserStatue(FVector pos, ABowserStatue::Behaviour behaviour) { Name = "Bowser Statue"; + ResourceName = "mk:bowser_statue"; Pos = pos; ABowserStatue::Behaviour _behaviour = behaviour; } diff --git a/src/engine/actors/BowserStatue.h b/src/engine/actors/BowserStatue.h index 869f6910f..51d58c0f1 100644 --- a/src/engine/actors/BowserStatue.h +++ b/src/engine/actors/BowserStatue.h @@ -13,6 +13,8 @@ extern "C" { extern Vtx gBowserStatueVtx[717]; extern Gfx gBowserStatueGfx[162]; +// The data for this actor is generated and cut out from the actual track geography +// That generator is currently commented out. So this actor is not usable atm. class ABowserStatue : public AActor { public: enum Behaviour { diff --git a/src/engine/actors/Cloud.cpp b/src/engine/actors/Cloud.cpp index 63139f89d..34f6466c9 100644 --- a/src/engine/actors/Cloud.cpp +++ b/src/engine/actors/Cloud.cpp @@ -2,7 +2,8 @@ #include "Cloud.h" #include "engine/Actor.h" -#include "World.h" +#include "engine/World.h" +#include "port/Game.h" extern "C" { #include "macros.h" @@ -10,24 +11,48 @@ extern "C" { #include "math_util.h" #include "actor_types.h" #include "actors.h" +#include "other_textures.h" extern f32 gKartHopInitialVelocityTable[]; extern f32 gKartGravityTable[]; } -ACloud::ACloud(FVector pos) { - Name = "Cloud"; - Pos[0] = pos.x; - Pos[1] = pos.y; - Pos[2] = pos.z; - Rot[0] = 0; - Rot[1] = 0; - Rot[2] = 0; +ACloud::ACloud(const SpawnParams& params) : AActor(params) { + Name = "Cloud"; + ResourceName = "hm:cloud"; + FVector pos = params.Location.value_or(FVector(0, 0, 0)); + Pos[0] = pos.x; + Pos[1] = pos.y; + Pos[2] = pos.z; + + Rot[0] = 0; + Rot[1] = 0; + Rot[2] = 0; + + TimerLength = params.Type.value_or(500); // How long the effect lasts + Hop = params.Speed.value_or(3.0f); // How long the effect lasts + Gravity = params.SpeedB.value_or(200.0f); // How long the effect lasts // Flags = -0x8000 | 0x4000; BoundingBoxSize = 2.0f; } +void ACloud::SetSpawnParams(SpawnParams& params) { + AActor::SetSpawnParams(params); + params.Name = ResourceName; + params.Type = TimerLength; + params.Speed = Hop; + params.SpeedB = Gravity; +} + +extern Gfx cloud_mesh[]; +void ACloud::BeginPlay() { + // Prevent collision mesh from being generated extra times. + if (Triangles.size() == 0) { + Editor::GenerateCollisionMesh(this, (Gfx*)cloud_mesh, 1.0f); + } +} + void ACloud::Tick() { Rot[1] += 0x200; @@ -35,7 +60,7 @@ void ACloud::Tick() { Timer++; // Increment timer } - if (Timer > 500) { // Time has expired, reset the actor and player + if (Timer > TimerLength) { // Time has expired, reset the actor and player PickedUp = false; if (_player) { gKartHopInitialVelocityTable[_player->characterId] = OldHop; // reset back to normal @@ -46,8 +71,6 @@ void ACloud::Tick() { } } -extern Gfx cloud_mesh[]; - void ACloud::Draw(Camera* camera) { Mat4 mtx; @@ -71,7 +94,9 @@ void ACloud::Collision(Player* player, AActor* actor) { OldHop = gKartHopInitialVelocityTable[player->characterId]; OldGravity = gKartGravityTable[player->characterId]; + // Hop height gKartHopInitialVelocityTable[player->characterId] = Hop; + // How strong gravity is gKartGravityTable[player->characterId] = Gravity; } } @@ -133,9 +158,53 @@ Gfx mat_cloud_cutout[] = { Gfx cloud_mesh[] = { // gsSPClearGeometryMode(G_LIGHTING), // gsSPVertex(cloud_mesh_vtx_cull + 0, 8, 0), - gsSPSetGeometryMode(G_LIGHTING), + //gsSPSetGeometryMode(G_LIGHTING), // gsSPCullDisplayList(0, 7), gsSPDisplayList(mat_cloud_cutout), gsSPDisplayList(cloud_mesh_tri_0), gsSPEndDisplayList(), }; + +void ACloud::DrawEditorProperties() { + ImGui::Text("Location"); + ImGui::SameLine(); + FVector location = FVector(Pos[0], Pos[1], Pos[2]); + if (ImGui::DragFloat3("##Location", (float*)&location)) { + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPos")) { + FVector location = FVector(0, 0, 0); + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::Text("Effect Timer"); + ImGui::SameLine(); + int32_t type = static_cast<int32_t>(TimerLength); + if (ImGui::InputInt("##Type", &type)) { + TimerLength = static_cast<uint32_t>(type); + } + + ImGui::Text("Hop"); + ImGui::SameLine(); + + if (ImGui::DragFloat("##Speed", &Hop, 0.01f)) { + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeed")) { + Hop = 0.0f; + } + + ImGui::Text("Gravity"); + ImGui::Text("Higher value = stronger gravity"); + + if (ImGui::DragFloat("##SpeedB", &Gravity, 1.0f)) { + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeedB")) { + Gravity = 0.0f; + } +}
\ No newline at end of file diff --git a/src/engine/actors/Cloud.h b/src/engine/actors/Cloud.h index 4805c08b9..fb334b680 100644 --- a/src/engine/actors/Cloud.h +++ b/src/engine/actors/Cloud.h @@ -3,6 +3,7 @@ #include <libultraship.h> #include "engine/Actor.h" #include "CoreMath.h" +#include "engine/World.h" extern "C" { #include "macros.h" @@ -13,27 +14,38 @@ extern "C" { class ACloud : public AActor { public: - - - // Constructor - ACloud(FVector pos); + ACloud(const SpawnParams& params); virtual ~ACloud() override = default; - // Virtual functions to be overridden by derived classes + // This is simply a helper function to keep Spawning code clean + static inline ACloud* Spawn(FVector pos, uint16_t time, f32 hop, f32 gravity) { + SpawnParams params = { + .Name = "hm:cloud", + .Type = time, // How long the effect is active + .Location = pos, + .Speed = hop, // How high you hop + .SpeedB = gravity, // How much gravity is effected + }; + return static_cast<ACloud*>(gWorldInstance.AddActor(new ACloud(params))); + } + virtual void Tick() override; virtual void Draw(Camera*) override; + virtual void BeginPlay() override; + virtual void SetSpawnParams(SpawnParams& params) override; + virtual void DrawEditorProperties() override; virtual void Collision(Player* player, AActor* actor) override; virtual bool IsMod() override; bool PickedUp = false; + uint32_t TimerLength = 500; uint32_t Timer = 0; - + Player* _player = NULL; - + f32 Hop = 3.0f; f32 Gravity = 200.0f; - f32 OldHop = 0; f32 OldGravity = 0; diff --git a/src/engine/actors/FallingRock.cpp b/src/engine/actors/FallingRock.cpp new file mode 100644 index 000000000..80e968294 --- /dev/null +++ b/src/engine/actors/FallingRock.cpp @@ -0,0 +1,231 @@ +#include "FallingRock.h" + +#include <libultra/gbi.h> +#include "CoreMath.h" +#include <assets/choco_mountain_data.h> +#include "port/interpolation/FrameInterpolation.h" +#include "port/Game.h" + +extern "C" { +#include "common_structs.h" +#include "math_util.h" +#include "main.h" +#include "actor_types.h" +#include "code_800029B0.h" +#include "collision.h" +#include "code_800029B0.h" +#include "external.h" +} + +size_t AFallingRock::_count = 0; + +AFallingRock::AFallingRock(SpawnParams params) : AActor(params) { + Type = ACTOR_FALLING_ROCK; + Name = "Falling Rock"; + ResourceName = "mk:falling_rock"; + + FVector pos = params.Location.value_or(FVector(0, 0, 0)); + TimerLength = params.Behaviour.value_or(80); + Pos[0] = pos.x * gCourseDirection; + Pos[1] = pos.y + 10.0f; + Pos[2] = pos.z; + State = _count; + func_802AAAAC(&Unk30); + + Flags = -0x8000; + Flags |= 0x4000; + BoundingBoxSize = 10.0f; + Model = d_course_choco_mountain_dl_falling_rock; + + _count += 1; +} + +void AFallingRock::SetSpawnParams(SpawnParams& params) { + AActor::SetSpawnParams(params); + params.Behaviour = TimerLength; +} + +void AFallingRock::Reset() { + RespawnTimer = TimerLength; + FVector pos = SpawnPos; + Pos[0] = (f32) pos.x * gCourseDirection; + Pos[1] = (f32) pos.y + 10.0f; + Pos[2] = (f32) pos.z; + vec3f_set(Velocity, 0, 0, 0); + vec3s_set(Rot, 0, 0, 0); +} + +bool AFallingRock::IsMod() { + return true; +} + +/** + * @brief Updates the falling rock actor. + * Actor used in Choco Mountain. + * + * @param rock + */ +void AFallingRock::Tick() { + Vec3f unkVec; + f32 pad0; + f32 pad1; + + if (RespawnTimer != 0) { + RespawnTimer -= 1; + return; + } + if (Pos[1] < CM_GetWaterLevel(Pos, NULL)) { + AFallingRock::Reset(); + } + Rot[0] += (s16) ((Velocity[2] * 5461.0f) / 20.0f); + Rot[2] += (s16) ((Velocity[0] * 5461.0f) / 20.0f); + Velocity[1] -= 0.1; + if (Velocity[1] < (-2.0f)) { + Velocity[1] = -2.0f; + } + Pos[0] += Velocity[0]; + Pos[1] += Velocity[1]; + Pos[2] += Velocity[2]; + pad1 = Velocity[1]; + check_bounding_collision(&Unk30, 10.0f, Pos[0], Pos[1], Pos[2]); + pad0 = Unk30.surfaceDistance[2]; + if (pad0 < 0.0f) { + unkVec[0] = -Unk30.orientationVector[0]; + unkVec[1] = -Unk30.orientationVector[1]; + unkVec[2] = -Unk30.orientationVector[2]; + Pos[0] += unkVec[0] * Unk30.surfaceDistance[2]; + Pos[1] += unkVec[1] * Unk30.surfaceDistance[2]; + Pos[2] += unkVec[2] * Unk30.surfaceDistance[2]; + adjust_pos_orthogonally(unkVec, pad0, Velocity, 2.0f); + Velocity[1] = -1.2f * pad1; + func_800C98B8(Pos, Velocity, SOUND_ARG_LOAD(0x19, 0x00, 0x80, 0x0F)); + } + pad0 = Unk30.surfaceDistance[0]; + if (pad0 < 0.0f) { + unkVec[1] = -Unk30.unk48[1]; + if (unkVec[1] == 0.0f) { + Velocity[1] *= -1.2f; + return; + } else { + unkVec[0] = -Unk30.unk48[0]; + unkVec[2] = -Unk30.unk48[2]; + Pos[0] += unkVec[0] * Unk30.surfaceDistance[0]; + Pos[1] += unkVec[1] * Unk30.surfaceDistance[0]; + Pos[2] += unkVec[2] * Unk30.surfaceDistance[0]; + adjust_pos_orthogonally(unkVec, pad0, Velocity, 2.0f); + Velocity[1] = -1.2f * pad1; + func_800C98B8(Pos, Velocity, SOUND_ARG_LOAD(0x19, 0x00, 0x80, 0x0F)); + } + } + pad0 = Unk30.surfaceDistance[1]; + if (pad0 < 0.0f) { + unkVec[1] = -Unk30.unk54[1]; + if (unkVec[1] == 0.0f) { + Velocity[1] *= -1.2f; + } else { + unkVec[0] = -Unk30.unk54[0]; + unkVec[2] = -Unk30.unk54[2]; + Pos[0] += unkVec[0] * Unk30.surfaceDistance[1]; + Pos[1] += unkVec[1] * Unk30.surfaceDistance[1]; + Pos[2] += unkVec[2] * Unk30.surfaceDistance[1]; + pad1 = Velocity[1]; + adjust_pos_orthogonally(unkVec, pad0, Velocity, 2.0f); + Velocity[1] = -1.2f * pad1; + func_800C98B8(Pos, Velocity, SOUND_ARG_LOAD(0x19, 0x00, 0x80, 0x0F)); + } + } +} + + + +/** + * @brief Renders the falling rock actor. + * Actor used in Choco Mountain. + * + * @param camera + * @param rock + */ +void AFallingRock::Draw(Camera* camera) { + Vec3s sp98; + Vec3f sp8C; + Mat4 mtx; + f32 height; + UNUSED s32 pad[4]; + + if (RespawnTimer != 0) { + return; + } + + height = is_within_render_distance(camera->pos, Pos, camera->rot[1], 400.0f, gCameraZoom[camera - camera1], + 4000000.0f); + + if (CVarGetInteger("gNoCulling", 0) == 1) { + height = CLAMP(height, 0.0f, 250000.0f); + } + + if (height < 0.0f) { + return; + } + + if (height < 250000.0f) { + + if (Unk30.unk34 == 1) { + sp8C[0] = Pos[0]; + sp8C[2] = Pos[2]; + height = calculate_surface_height(sp8C[0], Pos[1], sp8C[2], Unk30.meshIndexZX); + sp98[0] = 0; + sp98[1] = 0; + sp98[2] = 0; + sp8C[1] = height + 2.0f; + + FrameInterpolation_RecordOpenChild("rock_shadow", (uintptr_t) this); + mtxf_pos_rotation_xyz(mtx, sp8C, sp98); + if (render_set_position(mtx, 0) == 0) { + FrameInterpolation_RecordCloseChild(); + return; + } + gSPDisplayList(gDisplayListHead++, (Gfx*)d_course_choco_mountain_dl_6F88); + FrameInterpolation_RecordCloseChild(); + } + } + + // @port: Tag the transform. + FrameInterpolation_RecordOpenChild("rock", (uintptr_t) this); + + mtxf_pos_rotation_xyz(mtx, Pos, Rot); + if (render_set_position(mtx, 0) == 0) { + // @port Pop the transform id. + FrameInterpolation_RecordCloseChild(); + return; + } + gSPDisplayList(gDisplayListHead++, (Gfx*)d_course_choco_mountain_dl_falling_rock); + + // @port Pop the transform id. + FrameInterpolation_RecordCloseChild(); +} + +void AFallingRock::DrawEditorProperties() { + ImGui::Text("Location"); + ImGui::SameLine(); + FVector location = SpawnPos; + if (ImGui::DragFloat3("##Location", (float*)&location)) { + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPos")) { + FVector location = FVector(0, 0, 0); + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::Text("Respawn Timer"); + ImGui::SameLine(); + + int32_t behaviour = static_cast<int32_t>(TimerLength); + + if (ImGui::InputInt("##Behaviour", &behaviour)) { + TimerLength = static_cast<uint32_t>(behaviour); + } +} diff --git a/src/engine/actors/FallingRock.h b/src/engine/actors/FallingRock.h new file mode 100644 index 000000000..ffb8444e2 --- /dev/null +++ b/src/engine/actors/FallingRock.h @@ -0,0 +1,50 @@ +#pragma once + +#include <libultraship.h> +#include "engine/Actor.h" +#include "CoreMath.h" +#include "engine/SpawnParams.h" +#include "engine/CoreMath.h" +#include "engine/World.h" + +class World; +extern World gWorldInstance; + +extern "C" { +#include "common_structs.h" +} + +// Falls from the sky bouncing off of geography until it goes through water. +// Then after a brief period of time, respawns. +class AFallingRock : public AActor { +public: + + explicit AFallingRock(SpawnParams params); + ~AFallingRock() { + _count -= 1; + }; + + // This is simply a helper function to keep Spawning code clean + // @arg respawnTimer default game used 60, 120, 180 as the timer. Time until respawn after reaching the bottom? + static inline AFallingRock* Spawn(FVector pos, int16_t respawnTimer) { + SpawnParams params = { + .Name = "mk:falling_rock", + .Behaviour = respawnTimer, + .Location = pos, + }; + return static_cast<AFallingRock*>(gWorldInstance.AddActor(new AFallingRock(params))); + } + + int16_t TimerLength = 80; + + virtual void SetSpawnParams(SpawnParams& params) override; + virtual bool IsMod() override; + virtual void Tick() override; + virtual void Draw(Camera*) override; + virtual void DrawEditorProperties() override; + void Reset(); + + private: + uint32_t RespawnTimer = 0; + static size_t _count; +}; diff --git a/src/engine/actors/Finishline.cpp b/src/engine/actors/Finishline.cpp index 6a82f03f0..4164b5f3b 100644 --- a/src/engine/actors/Finishline.cpp +++ b/src/engine/actors/Finishline.cpp @@ -21,30 +21,41 @@ extern f32 gKartGravityTable[]; size_t AFinishline::_count = 0; -AFinishline::AFinishline(std::optional<FVector> pos) { +AFinishline::AFinishline(const SpawnParams& params) : AActor(params) { Name = "Finishline"; + ResourceName = "mk:finishline"; - if (pos.has_value()) { + if (params.Location.has_value()) { + FVector pos = params.Location.value_or(FVector(0, 0, 0)); // Set spawn point to the provided position - Pos[0] = D_8015F8D0[0] = pos.value().x; - Pos[1] = D_8015F8D0[1] = pos.value().y - 15; - Pos[2] = D_8015F8D0[2] = pos.value().z; + Pos[0] = D_8015F8D0[0] = pos.x; + Pos[1] = D_8015F8D0[1] = pos.y - 15; + Pos[2] = D_8015F8D0[2] = pos.z; } else { // Set spawn point to the tracks first path point. - Pos[0] = D_8015F8D0[0] = gCurrentTrackPath->posX; - Pos[1] = D_8015F8D0[1] = (f32) (gCurrentTrackPath->posY - 15); - Pos[2] = D_8015F8D0[2] = gCurrentTrackPath->posZ; + Pos[0] = D_8015F8D0[0] = gCurrentTrackPath->x; + Pos[1] = D_8015F8D0[1] = (f32) (gCurrentTrackPath->y - 15); + Pos[2] = D_8015F8D0[2] = gCurrentTrackPath->z; } - Rot[0] = 0; - Rot[1] = 0; - Rot[2] = 0; + IRotator rot = params.Rotation.value_or(IRotator(0, 0, 0)); + + Rot[0] = rot.pitch; + Rot[1] = rot.yaw; + Rot[2] = rot.roll; Flags = -0x8000 | 0x4000; BoundingBoxSize = 0.0f; } +void AFinishline::BeginPlay() { + // Prevent collision mesh from being generated extra times. + if (Triangles.size() == 0) { + Editor::GenerateCollisionMesh(this, (Gfx*)LOAD_ASSET_RAW(D_0D001B90), 1.0f); + } +} + void AFinishline::Tick() { } diff --git a/src/engine/actors/Finishline.h b/src/engine/actors/Finishline.h index 3c0e84de8..ec99b637f 100644 --- a/src/engine/actors/Finishline.h +++ b/src/engine/actors/Finishline.h @@ -3,6 +3,7 @@ #include <libultraship.h> #include "CoreMath.h" #include "engine/Actor.h" +#include "engine/World.h" extern "C" { #include "macros.h" @@ -17,13 +18,31 @@ public: * Default behaviour places the finishline at the first waypoint. * @arg pos, optional. Sets a custom position */ - AFinishline(std::optional<FVector> pos); + AFinishline(const SpawnParams& params); virtual ~AFinishline() override = default; + // This is simply a helper function to keep Spawning code clean + static inline AFinishline* Spawn(FVector pos, IRotator rot) { + SpawnParams params = { + .Name = "mk:finishline", + .Location = pos, + .Rotation = rot, + }; + return static_cast<AFinishline*>(gWorldInstance.AddActor(new AFinishline(params))); + } + + static inline AFinishline* Spawn() { + SpawnParams params = { + .Name = "mk:finishline", + }; + return static_cast<AFinishline*>(gWorldInstance.AddActor(new AFinishline(params))); + } + // Virtual functions to be overridden by derived classes virtual void Tick() override; virtual void Draw(Camera*) override; + virtual void BeginPlay() override; virtual void Collision(Player* player, AActor* actor) override; virtual bool IsMod() override; diff --git a/src/engine/actors/MarioSign.cpp b/src/engine/actors/MarioSign.cpp index f39e4f69b..b5fc51860 100644 --- a/src/engine/actors/MarioSign.cpp +++ b/src/engine/actors/MarioSign.cpp @@ -2,33 +2,60 @@ #include <libultra/gbi.h> #include <assets/mario_raceway_data.h> +#include "CoreMath.h" extern "C" { #include "common_structs.h" #include "math_util.h" #include "main.h" #include "actor_types.h" +#include "code_800029B0.h" +#include "collision.h" } -AMarioSign::AMarioSign(FVector pos) { +AMarioSign::AMarioSign(const SpawnParams& params) : AActor(params) { Type = ACTOR_MARIO_SIGN; Name = "Mario Sign"; - Pos[0] = pos.x; + ResourceName = "mk:mario_sign"; + Model = d_course_mario_raceway_dl_sign; + + Speed = params.Speed.value_or(182); + + FVector pos = params.Location.value_or(FVector(0, 0, 0)); + Pos[0] = pos.x * gCourseDirection; Pos[1] = pos.y; Pos[2] = pos.z; + + IRotator rot = params.Rotation.value_or(IRotator(0, 0, 0)); + Rot[0] = rot.pitch; + Rot[1] = rot.yaw; + Rot[2] = rot.roll; + + Scale = params.Scale.value_or(FVector(1.0f, 1.0f, 1.0f)); + + func_802AAAAC(&Unk30); + Flags = -0x8000; Flags |= 0x4000; } +bool AMarioSign::IsMod() { + return true; +} + +void AMarioSign::SetSpawnParams(SpawnParams& params) { + AActor::SetSpawnParams(params); +} + void AMarioSign::Tick() { if ((Flags & 0x800) == 0) { if ((Flags & 0x400) != 0) { Pos[1] += 4.0f; if (Pos[1] > 800.0f) { Flags |= 0x800; - Rot[1] += 1820; + Rot[1] += Speed * 10; // Originally 1820 } } else { - Rot[1] += 182; + Rot[1] += Speed; // Originally 182 } } } @@ -49,6 +76,7 @@ void AMarioSign::Draw(Camera *camera) { gSPSetGeometryMode(gDisplayListHead++, G_SHADING_SMOOTH); gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING); mtxf_pos_rotation_xyz(sp40, Pos, Rot); + if (render_set_position(sp40, 0) != 0) { gSPDisplayList(gDisplayListHead++, (Gfx*)d_course_mario_raceway_dl_sign); } diff --git a/src/engine/actors/MarioSign.h b/src/engine/actors/MarioSign.h index 099894dc1..f8864ad76 100644 --- a/src/engine/actors/MarioSign.h +++ b/src/engine/actors/MarioSign.h @@ -3,6 +3,10 @@ #include <libultraship.h> #include "engine/Actor.h" #include "CoreMath.h" +#include "engine/World.h" + +class World; +extern World gWorldInstance; extern "C" { #include "common_structs.h" @@ -12,8 +16,23 @@ class AMarioSign : public AActor { public: virtual ~AMarioSign() = default; - explicit AMarioSign(FVector pos); + explicit AMarioSign(const SpawnParams& params); + + // This is simply a helper function to keep Spawning code clean + static inline AMarioSign* Spawn(FVector pos, IRotator rot, FVector velocity, FVector scale) { + SpawnParams params = { + .Name = "mk:mario_sign", + .Location = pos, + .Rotation = rot, + .Scale = scale, + .Velocity = velocity, + .Speed = 182, + }; + return static_cast<AMarioSign*>(gWorldInstance.AddActor(new AMarioSign(params))); + } + virtual bool IsMod() override; + virtual void SetSpawnParams(SpawnParams& params) override; virtual void Tick() override; virtual void Draw(Camera*) override; }; diff --git a/src/engine/actors/Ship.cpp b/src/engine/actors/Ship.cpp index 5af3553e2..8cbf0d8d6 100644 --- a/src/engine/actors/Ship.cpp +++ b/src/engine/actors/Ship.cpp @@ -2,6 +2,7 @@ #include <libultra/gbi.h> #include "CoreMath.h" +#include "port/Game.h" #include "Matrix.h" extern "C" { @@ -13,29 +14,48 @@ extern "C" { #include "courses/harbour/ship3_model.h" } -AShip::AShip(FVector pos, AShip::Skin skin) { - Spawn = pos; - Spawn.y += 10; +AShip::AShip(const SpawnParams& params) : AActor(params) { + ResourceName = "hm:ship"; + FVector pos = params.Location.value_or(FVector(0, 0, 0)); + //Spawn.y += 10; Pos[0] = pos.x; Pos[1] = pos.y; Pos[2] = pos.z; Scale = FVector(0.4, 0.4, 0.4); - - switch(skin) { - case GHOSTSHIP: + + SpawnSkin = static_cast<AShip::Skin>(params.Type.value_or(0)); + switch(SpawnSkin) { + case Skin::GHOSTSHIP: Name = "Ghostship"; _skin = ghostship_Plane_mesh; break; - case SHIP2: + case Skin::SHIP2: Name = "Ship_1"; _skin = ship2_SoH_mesh; break; - case SHIP3: + case Skin::SHIP3: Name = "Ship_2"; _skin = ship3_2Ship_mesh; break; } - Model = _skin; + Model = (const char*)_skin; +} + +void AShip::SetSpawnParams(SpawnParams& params) { + AActor::SetSpawnParams(params); + params.Name = ResourceName; + params.Type = static_cast<int16_t>(SpawnSkin); + params.Location = SpawnPos; + params.Rotation = SpawnRot; + params.Scale = SpawnScale; + params.Speed = Speed; +} + +void AShip::BeginPlay() { + // Prevent collision mesh from being generated extra times. + if (Triangles.size() == 0) { + Editor::GenerateCollisionMesh(this, (Gfx*)_skin, Scale.y); + } } void AShip::Tick() { @@ -54,3 +74,90 @@ void AShip::Tick() { } bool AShip::IsMod() { return true; } + +void AShip::DrawEditorProperties() { + ImGui::Text("Ship Type"); + ImGui::SameLine(); + + int32_t type = static_cast<int32_t>(SpawnSkin); + const char* items[] = { "Ghostship", "Ship 2", "Ship 3" }; + + if (ImGui::Combo("##Type", &type, items, IM_ARRAYSIZE(items))) { + SpawnSkin = static_cast<AShip::Skin>(type); + + switch(SpawnSkin) { + case Skin::GHOSTSHIP: + Name = "Ghostship"; + _skin = ghostship_Plane_mesh; + break; + case Skin::SHIP2: + Name = "Ship_1"; + _skin = ship2_SoH_mesh; + break; + case Skin::SHIP3: + Name = "Ship_2"; + _skin = ship3_2Ship_mesh; + break; + } + Model = (const char*)_skin; + } + + ImGui::Text("Location"); + ImGui::SameLine(); + FVector location = FVector(Pos[0], Pos[1], Pos[2]); + if (ImGui::DragFloat3("##Location", (float*)&location)) { + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPos")) { + FVector location = FVector(0, 0, 0); + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::Text("Rotation"); + ImGui::SameLine(); + + IRotator objRot = GetRotation(); + + // Convert to temporary int values (to prevent writing 32bit values to 16bit variables) + int rot[3] = { + objRot.pitch, + objRot.yaw, + objRot.roll + }; + + if (ImGui::DragInt3("##Rotation", rot, 5.0f)) { + for (size_t i = 0; i < 3; i++) { + // Wrap around 0–65535 + rot[i] = (rot[i] % 65536 + 65536) % 65536; + } + IRotator newRot; + newRot.Set( + static_cast<uint16_t>(rot[0]), + static_cast<uint16_t>(rot[1]), + static_cast<uint16_t>(rot[2]) + ); + Rotate(newRot); + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetRot")) { + IRotator rot = IRotator(0, 0, 0); + Rotate(rot); + } + + FVector scale = GetScale(); + ImGui::Text("Scale "); + ImGui::SameLine(); + + ImGui::DragFloat3("##Scale", (float*)&scale, 0.1f); + SetScale(scale); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetScale")) { + FVector scale = FVector(0.4f, 0.4f, 0.4f); + SetScale(scale); + } +} diff --git a/src/engine/actors/Ship.h b/src/engine/actors/Ship.h index fe5743922..c1f082c78 100644 --- a/src/engine/actors/Ship.h +++ b/src/engine/actors/Ship.h @@ -2,8 +2,9 @@ #include <libultraship.h> #include <libultra/gbi.h> -#include "engine/Actor.h" #include "CoreMath.h" +#include "engine/Actor.h" +#include "engine/World.h" extern "C" { #include "common_structs.h" @@ -19,16 +20,28 @@ public: SHIP3, }; - explicit AShip(FVector pos, AShip::Skin); + explicit AShip(const SpawnParams& params); virtual ~AShip() = default; + // This is simply a helper function to keep Spawning code clean + static inline AShip* Spawn(FVector pos, IRotator rot, FVector scale, int16_t skin) { + SpawnParams params = { + .Name = "hm:ship", + .Type = skin, // which ship model to use + .Location = pos, + .Rotation = rot, + .Scale = scale, + }; + return static_cast<AShip*>(gWorldInstance.AddActor(new AShip(params))); + } + + AShip::Skin SpawnSkin = Skin::GHOSTSHIP; + + virtual void SetSpawnParams(SpawnParams& params) override; virtual void Tick() override; + virtual void BeginPlay() override; virtual bool IsMod() override; - - FVector Spawn; - //FVector Pos; - ///IRotator Rot = {0, 0, 0}; - //FVector Scale = {0.4, 0.4, 0.4}; + virtual void DrawEditorProperties() override; private: Gfx* _skin; }; diff --git a/src/engine/actors/SpaghettiShip.cpp b/src/engine/actors/SpaghettiShip.cpp index b09ada319..63ed048ec 100644 --- a/src/engine/actors/SpaghettiShip.cpp +++ b/src/engine/actors/SpaghettiShip.cpp @@ -10,14 +10,22 @@ extern "C" { #include "courses/harbour/ship_model.h" } -ASpaghettiShip::ASpaghettiShip(FVector pos) { +ASpaghettiShip::ASpaghettiShip(const SpawnParams& params) : AActor(params) { Name = "Spaghetti Ship"; + ResourceName = "hm:spaghetti_ship"; + BoundingBoxSize = 3.0f; + + FVector pos = params.Location.value_or(FVector(0, 0, 0)); Pos[0] = pos.x; Pos[1] = pos.y; Pos[2] = pos.z; - Spawn = pos; - Spawn.y += 10; - Scale = {0.4, 0.4, 0.4}; + + Scale = params.Scale.value_or(FVector(0.4f, 0.4f, 0.4f)); + + IRotator rot = params.Rotation.value_or(IRotator(0, 0, 0)); + Rot[0] = rot.pitch; + Rot[1] = rot.yaw; + Rot[2] = rot.roll; } void ASpaghettiShip::Tick() { @@ -28,8 +36,9 @@ void ASpaghettiShip::Tick() { angle += speed; // Increment the angle to move in a circle // Update the position based on a circular path - Pos[0] = Spawn.x + radius * cosf(angle); - Pos[2] = Spawn.z + radius * sinf(angle); + FVector spawn = SpawnPos; + Pos[0] = spawn.x + radius * cosf(angle); + Pos[2] = spawn.z + radius * sinf(angle); // Rotate to face forward along the circle Rot[1] = -static_cast<int16_t>(angle * (32768.0f / M_PI / 2.0f)); diff --git a/src/engine/actors/SpaghettiShip.h b/src/engine/actors/SpaghettiShip.h index 70bd047d7..fef2e05a5 100644 --- a/src/engine/actors/SpaghettiShip.h +++ b/src/engine/actors/SpaghettiShip.h @@ -2,8 +2,9 @@ #include <libultraship.h> #include <libultra/gbi.h> -#include "engine/Actor.h" #include "CoreMath.h" +#include "engine/Actor.h" +#include "engine/World.h" extern "C" { #include "common_structs.h" @@ -12,13 +13,23 @@ extern "C" { class ASpaghettiShip : public AActor { public: - explicit ASpaghettiShip(FVector pos); + explicit ASpaghettiShip(const SpawnParams& params); virtual ~ASpaghettiShip() = default; + // This is simply a helper function to keep Spawning code clean + static inline ASpaghettiShip* Spawn(FVector pos, IRotator rot, FVector scale) { + SpawnParams params = { + .Name = "hm:spaghetti_ship", + .Location = pos, + .Rotation = rot, + .Scale = scale, + }; + return static_cast<ASpaghettiShip*>(gWorldInstance.AddActor(new ASpaghettiShip(params))); + } + virtual void Tick() override; virtual void Draw(Camera*) override; virtual bool IsMod() override; - FVector Spawn; IRotator WheelRot = {0, 0, 0}; }; diff --git a/src/engine/actors/Starship.cpp b/src/engine/actors/Starship.cpp index e104d0eb6..7723ac2d5 100644 --- a/src/engine/actors/Starship.cpp +++ b/src/engine/actors/Starship.cpp @@ -2,6 +2,7 @@ #include <libultra/gbi.h> #include "Matrix.h" +#include "port/Game.h" extern "C" { #include "common_structs.h" @@ -10,26 +11,44 @@ extern "C" { #include "courses/harbour/starship_model.h" } -AStarship::AStarship(FVector pos) { +AStarship::AStarship(const SpawnParams& params) : AActor(params) { Name = "Starship"; - Spawn = pos; + ResourceName = "hm:starship"; + FVector pos = params.Location.value_or(FVector(0, 0, 0)); SetLocation(pos); - Scale = FVector(1.5, 1.5, 1.5); - Model = starship_Cube_mesh; + IRotator rot = params.Rotation.value_or(IRotator(0, 0, 0)); + Rot[0] = rot.pitch; + Rot[1] = rot.yaw; + Rot[2] = rot.roll; + Scale = params.Scale.value_or(FVector(0, 0, 0)); + Speed = params.Speed.value_or(0.01f); + SpeedB = params.SpeedB.value_or(150.0f); + Model = (const char*)starship_Cube_mesh; +} + +void AStarship::SetSpawnParams(SpawnParams& params) { + AActor::SetSpawnParams(params); + params.SpeedB = SpeedB; +} + +void AStarship::BeginPlay() { + // Prevent collision mesh from being generated extra times. + if (Triangles.size() == 0) { + Editor::GenerateCollisionMesh(this, (Gfx*)Model, 1.0f); + } } void AStarship::Tick() { static float angle = 0.0f; - float radius = 150.0f; - float speed = 0.01f; - angle += speed; + angle += Speed; // Move relative to the initial position FVector pos = GetLocation(); - pos.x = Spawn.x + radius * cosf(angle); - pos.z = Spawn.z + radius * sinf(angle); + FVector spawn = SpawnPos; + pos.x = spawn.x + SpeedB * cosf(angle); + pos.z = spawn.z + SpeedB * sinf(angle); SetLocation(pos); // Keep y from changing (or adjust it if necessary) @@ -40,3 +59,88 @@ void AStarship::Tick() { } bool AStarship::IsMod() { return true; } + +void AStarship::DrawEditorProperties() { + ImGui::Text("Location"); + ImGui::SameLine(); + FVector location = FVector(Pos[0], Pos[1], Pos[2]); + if (ImGui::DragFloat3("##Location", (float*)&location)) { + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPos")) { + FVector location = FVector(0, 0, 0); + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::Text("Rotation"); + ImGui::SameLine(); + + IRotator objRot = GetRotation(); + + // Convert to temporary int values (to prevent writing 32bit values to 16bit variables) + int rot[3] = { + objRot.pitch, + objRot.yaw, + objRot.roll + }; + + if (ImGui::DragInt3("##Rotation", rot, 5.0f)) { + for (size_t i = 0; i < 3; i++) { + // Wrap around 0–65535 + rot[i] = (rot[i] % 65536 + 65536) % 65536; + } + IRotator newRot; + newRot.Set( + static_cast<uint16_t>(rot[0]), + static_cast<uint16_t>(rot[1]), + static_cast<uint16_t>(rot[2]) + ); + Rotate(newRot); + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetRot")) { + IRotator rot = IRotator(0, 0, 0); + Rotate(rot); + } + + FVector scale = GetScale(); + ImGui::Text("Scale "); + ImGui::SameLine(); + + ImGui::DragFloat3("##Scale", (float*)&scale, 0.1f); + SetScale(scale); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetScale")) { + FVector scale = FVector(0.4f, 0.4f, 0.4f); + SetScale(scale); + } + + ImGui::Text("Speed"); + ImGui::SameLine(); + + float speed = Speed; + if (ImGui::DragFloat("##Speed", &speed, 0.01f)) { + Speed = speed; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeed")) { + Speed = 0.0f; + } + + ImGui::Text("Radius"); + ImGui::SameLine(); + + float speed2 = SpeedB; + if (ImGui::DragFloat("##SpeedB", &speed2, 5.0f)) { + SpeedB = speed2; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeedB")) { + SpeedB = 0.0f; + } +} diff --git a/src/engine/actors/Starship.h b/src/engine/actors/Starship.h index 71b898358..c76c10c68 100644 --- a/src/engine/actors/Starship.h +++ b/src/engine/actors/Starship.h @@ -2,8 +2,9 @@ #include <libultraship.h> #include <libultra/gbi.h> -#include "engine/Actor.h" #include "CoreMath.h" +#include "engine/Actor.h" +#include "engine/World.h" extern "C" { #include "common_structs.h" @@ -12,11 +13,27 @@ extern "C" { class AStarship : public AActor { public: - explicit AStarship(FVector pos); + explicit AStarship(const SpawnParams& params); virtual ~AStarship() = default; + // This is simply a helper function to keep Spawning code clean + static inline AStarship* Spawn(FVector pos, IRotator rot, FVector scale, f32 speed, f32 radius) { + SpawnParams params = { + .Name = "hm:starship", + .Location = pos, + .Rotation = rot, + .Scale = scale, + .Speed = speed, + .SpeedB = radius, + }; + return static_cast<AStarship*>(gWorldInstance.AddActor(new AStarship(params))); + } + + float SpeedB; + + virtual void SetSpawnParams(SpawnParams& params) override; virtual void Tick() override; virtual bool IsMod() override; - - FVector Spawn; + virtual void BeginPlay() override; + virtual void DrawEditorProperties() override; }; diff --git a/src/engine/actors/Text.cpp b/src/engine/actors/Text.cpp new file mode 100644 index 000000000..2d2fab0b0 --- /dev/null +++ b/src/engine/actors/Text.cpp @@ -0,0 +1,588 @@ +#include <libultraship.h> +#include <libultra/gbi.h> +#include <vector> + +#include "Text.h" + +#include "port/interpolation/FrameInterpolation.h" +#include "engine/Matrix.h" +#include "engine/editor/EditorMath.h" + +extern "C" { +#include "defines.h" +#include "main.h" +#include "menu_items.h" +#include "assets/data_segment2.h" +#include "render_player.h" +#include "math_util.h" +#include "assets/texture_data_2.h" +#include "render_objects.h" +#include "common_structs.h" +#include "code_80005FD0.h" +} + +AText::AText(const SpawnParams& params) : AActor(params) { + Name = "Text"; + ResourceName = "hm:text"; + + SpawnPos = params.Location.value_or(FVector(0.0f, 100.0f, 0.0f)); + Pos[0] = SpawnPos.x; + Pos[1] = SpawnPos.y; + Pos[2] = SpawnPos.z; + + SpawnRot = params.Rotation.value_or(IRotator(0, 0, 0)); + Rot[0] = SpawnRot.pitch; + Rot[1] = SpawnRot.yaw; + Rot[2] = SpawnRot.roll; + + SpawnScale = params.Scale.value_or(FVector(1.0f, 1.0f, 1.0f)); + Scale = SpawnScale; + + ScaleX = params.FVec2.value_or(FVector{1.0f, 0.0f, 0.0f}).x; + + Mode = static_cast<TextMode>(params.Type.value_or(0)); // STATIONARY + + Animate = params.Bool.value_or(false); + FaceCamera = params.Bool2.value_or(true); + + WidthOffset = params.Speed.value_or(0.0f); + HeightOffset = params.SpeedB.value_or(8.0f); + + FVector options = params.Velocity.value_or(FVector{1.0f, 14000.0f, 0.0f}); + LetterSpacing = options.x; + Far = options.y; + Close = options.z; + + TextColour[0] = params.Colour.value_or(RGBA8{255, 255, 255, 255}); + TextColour[1] = params.Colour2.value_or(RGBA8{255, 255, 255, 255}); + TextColour[2] = params.Colour3.value_or(RGBA8{255, 255, 255, 255}); + TextColour[3] = params.Colour4.value_or(RGBA8{255, 255, 255, 255}); + + PlayerIndex = static_cast<uint32_t>(params.Behaviour.value_or(0)); + if (PlayerIndex < 0 || PlayerIndex >= NUM_PLAYERS) { + PlayerIndex = 0; + } + + Text = ValidateString(params.Skin.value_or("Harbour Masters")); + AText::Print3D((char*)Text.c_str(), 0, CENTER_TEXT_MODE_2); +} + +/** + * Filters out bad characters (allows a-z, A-Z, 0-9, space) + * Returns "Blank Text" for blank input + * Returns "Invalid" if no valid input found + * Limits str to 20 characters + * + * The font does support some symbols and other language characters + * But these need to be checked thoroughly before white-listing. + */ +std::string AText::ValidateString(const std::string_view& s) { + if (s.empty()) { return "Blank Text"; } + + Text.clear(); + + for (char c : s) { + if (std::isalpha(static_cast<unsigned char>(c)) || + std::isdigit(static_cast<unsigned char>(c)) || + c == ' ') + { + Text += c; + if (Text.size() >= 20) { + break; + }; + } + } + + // No valid characters found + if (Text.empty()) { + return "Invalid"; + } + return Text; +} + +/* + * Most changes during runtime require a refresh because the text is generated statically + * with the intention of this code being somewhat performant + */ +void AText::Refresh() { + AText::TextureList.clear(); + AText::Print3D((char*)Text.c_str(), 0, CENTER_TEXT_MODE_2); +} + +bool AText::IsMod() { return true; } + +void AText::SetSpawnParams(SpawnParams& params) { + AActor::SetSpawnParams(params); + params.Name = ResourceName; + params.Type = static_cast<int16_t>(Mode); + params.Behaviour = PlayerIndex; + params.Skin = Text; + + params.Colour = TextColour[0]; + params.Colour2 = TextColour[1]; + params.Colour3 = TextColour[2]; + params.Colour4 = TextColour[3]; + + params.Speed = WidthOffset; + params.SpeedB = HeightOffset; + + params.Velocity = {LetterSpacing, Far, Close}; + params.FVec2 = {ScaleX, 0.0f, 0.0f}; + + params.Bool = Animate; + params.Bool2 = FaceCamera; +} + +void AText::Tick() { + switch(Mode) { + case STATIONARY: + break; // Do nothing + case FOLLOW_PLAYER: + AText::FollowPlayer(); + break; + } +} + +void AText::FollowPlayer() { + Pos[0] = gPlayers[PlayerIndex].pos[0] + WidthOffset; + Pos[1] = gPlayers[PlayerIndex].pos[1] + HeightOffset; + Pos[2] = gPlayers[PlayerIndex].pos[2]; + + // Animate text if player is in first place + // if (gGPCurrentRaceRankByPlayerId[PlayerIndex] == 0) { + // Animate = true; + // } else { + // Animate = false; + // } +} + +void AText::Draw(Camera* camera) { + switch(Mode) { + case STATIONARY: + break; // Do nothing + case FOLLOW_PLAYER: + if (PlayerIndex == camera->playerId) { + return; // Do not draw the local players own name + } + if ((gPlayers[PlayerIndex].effects & BOO_EFFECT) == BOO_EFFECT) { + FadeState = FADE_OUT; + AText::DrawText3D(camera); + return; // Skip expensive calculations below + } + break; + } + + f32 distance = is_within_render_distance(camera->pos, (float*)&Pos[0], camera->rot[1], Close, + gCameraZoom[camera - camera1], Far); + + if (distance == -1.0f) { + Dist = DistanceProps::TOO_FAR; + } else if (distance < Close) { + Dist = DistanceProps::TOO_CLOSE; + } else { + Dist = DistanceProps::ACTIVE; + } + + if (Dist != PrevState) { + PrevState = Dist; + if ((distance == -1.0f) || (distance < Close)) { + FadeState = FADE_OUT; + } else { + FadeState = FADE_IN; + } + } + + AText::DrawText3D(camera); +} + +/** + * These have been refactored for efficiency purposes. + * The new method uses 1 matrix to display the whole string + * And then setting vertex data is done during the setup/constructor phase, + * instead of during rendering + * This requires a refresh if the data ever changes + * + * This method is more efficient because the original version does all this work + * during the render phase. Now it's done during the actor spawn phase with the exception + * if any changes are made at runtime. + */ +void AText::Print3D(char* text, s32 tracking, s32 mode) { + char* temp_string = text; + s32 stringWidth = 0; + s32 glyphIndex; + s32 sp60; + + s32 column = 0; + s32 row = 0; + + if (text == NULL) { + // @port if invalid text is loaded it will skip rendering it. + return; + } + + while (*temp_string != '\0') { + glyphIndex = char_to_glyph_index(temp_string); + if (glyphIndex >= 0) { + stringWidth += ((gGlyphDisplayWidth[glyphIndex] + tracking) * ScaleX); + } else if ((glyphIndex != -2) && (glyphIndex == -1)) { + stringWidth += ((tracking + 7) * ScaleX); + } else { + return; + } + if (glyphIndex >= 0x30) { + temp_string += 2; + } else { + temp_string += 1; + } + } + + switch (mode) { + case LEFT_TEXT: + //! FAKE: + do { + } while (0); + case RIGHT_TEXT: + column -= stringWidth; + break; + case CENTER_TEXT_MODE_1: + case CENTER_TEXT_MODE_2: + column -= stringWidth / 2; + break; + default: + break; + } + + if (mode < 3) { + sp60 = 1; + } else { + sp60 = 2; + } + + while (*text != '\0') { + glyphIndex = char_to_glyph_index(text); + if (glyphIndex >= 0) { + AText::PrintLetter3D(gGlyphTextureLUT[glyphIndex], + column, row, sp60); + column = column + (s32) ((gGlyphDisplayWidth[glyphIndex] + tracking) * ScaleX); + } else if ((glyphIndex != -2) && (glyphIndex == -1)) { + column = column + (s32) ((tracking + 7) * ScaleX); + } else { + return; + } + if (glyphIndex >= 0x30) { + text += 2; + } else { + text += 1; + } + } + SetupVtx(); // position each letter +} + +void AText::PrintLetter3D(MenuTexture* glyphTexture, f32 column, f32 row, s32 mode) { + s32 var_v0; + u8* temp_v0_2; + f32 thing0; + f32 thing1; + MenuTexture* texture; + + texture = glyphTexture; + while (texture->textureData != NULL) { + if (texture->textureData != 0) { + f32 col = texture->dX + column; + + TextureList.emplace_back(CharacterList{ + (const char*) texture->textureData, + col, + texture->dY + row, + texture->width, + texture->height, + mode, + }); + } + texture++; + } +} + +void AText::SetupVtx() { + for (CharacterList& character : TextureList) { + + Vtx* vtxPtr; + switch (character.width) { + default: + vtxPtr = (Vtx*)&AText::myVtx[4]; + break; + case 16: + vtxPtr = (Vtx*)&AText::myVtx[4]; + break; + case 26: + vtxPtr = (Vtx*)&AText::myVtx[0]; + break; + case 30: + vtxPtr = (Vtx*)&AText::myVtx[8]; + break; + } + // printf("col %f width %d span %d\n", character.column, character.width, character.span); + // memcpy the vtx data into the unique vtx data for this letter + Vtx* vtxSrc = (Vtx*)vtxPtr; + memcpy(&character.vtx, vtxSrc, sizeof(Vtx) * 4); + + for (size_t i = 0; i < 4; i++) { + // Set the location for this letter (beside the previous letter) center the text over the anchor point + float span = (character.column * LetterSpacing); + character.vtx[i].v.ob[0] += (s16)span; + + // Set the colour for this letter + character.vtx[i].v.cn[0] = TextColour[i].r; + character.vtx[i].v.cn[1] = TextColour[i].g; + character.vtx[i].v.cn[2] = TextColour[i].b; + character.vtx[i].v.cn[3] = TextColour[i].a; + } + } +} + +void AText::DrawText3D(Camera* camera) { // Based on func_80095BD0 + Mat4 mtx; + + if (FaceCamera) { + ApplySphericalBillBoard(mtx, *(FVector*)Pos, Scale, camera->cameraId); + } else { + ApplyMatrixTransformations(mtx, *(FVector*)Pos, *(IRotator*)Rot, Scale); + } + + AddObjectMatrix(mtx, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + + FrameInterpolation_RecordOpenChild("actor_text", TAG_LETTER(this)); + gSPDisplayList(gDisplayListHead++, (Gfx*)D_020077A8); + switch (1) { + case 1: + gSPDisplayList(gDisplayListHead++, (Gfx*)D_020077F8); + break; + case 2: + gSPDisplayList(gDisplayListHead++, (Gfx*)D_02007818); + break; + } + + for (CharacterList& tex : TextureList) { + //printf("tex texture %p width %d height %d mode %d col %f\n", tex.Texture, tex.width, tex.height, tex.mode, tex.column); + gDPLoadTextureTile_4b(gDisplayListHead++, (Gfx*)tex.Texture, G_IM_FMT_I, tex.width, 0, 0, 0, tex.width, tex.height + 2, 0, + G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, + G_TX_NOLOD); + + // gSPClearGeometryMode(gDisplayListHead++, G_ZBUFFER); + + if (Animate) { + AnimateColour(tex.vtx); + } + + switch(FadeState) { + case FADE_IN: + AText::FadeIn(tex.vtx); + break; + case FADE_OUT: + AText::FadeOut(tex.vtx); + break; + } + + gSPVertex(gDisplayListHead++, (uintptr_t)tex.vtx, 4, 0); + gSP2Triangles(gDisplayListHead++, 0, 2, 1, 0, 0, 3, 2, 0); + + // gSPSetGeometryMode(gDisplayListHead++, G_ZBUFFER); + } + + gSPDisplayList(gDisplayListHead++, (Gfx*)D_020077D8); + FrameInterpolation_RecordCloseChild(); +} + +void AText::AnimateColour(Vtx* vtx) { + u8 r = (sin(gGlobalTimer * 0.1f) * 0.5f + 0.5f) * 200; + u8 g = (sin(gGlobalTimer * 0.1f + 2.0f) * 0.5f + 0.5f) * 200; + u8 b = (sin(gGlobalTimer * 0.1f + 4.0f) * 0.5f + 0.5f) * 200; + + for (size_t i = 0; i < 4; i++) { + vtx[i].v.cn[0] = r; + vtx[i].v.cn[1] = g; + vtx[i].v.cn[2] = b; + } +} + +#define fadeSpeed 16 +void AText::FadeIn(Vtx* vtx) { + uint8_t alpha = vtx[0].v.cn[3]; + if (alpha + fadeSpeed > 255) { + alpha = 255; + FadeState = NO_FADE; + } else { + alpha += fadeSpeed; + } + + // Apply alpha to all 4 vertices + for (size_t i = 0; i < 4; i++) { + vtx[i].v.cn[3] = alpha; + } +} + +void AText::FadeOut(Vtx* vtx) { + uint8_t alpha = vtx[0].v.cn[3]; + if (alpha < fadeSpeed) { + alpha = 0; + FadeState = NO_FADE; + } else { + alpha -= fadeSpeed; + } + + // Apply alpha to all 4 vertices + for (size_t i = 0; i < 4; i++) { + vtx[i].v.cn[3] = alpha; + } +} +#undef fadeSpeed + +void AText::DrawEditorProperties() { + bool updated = false; + ImGui::Text("Text"); + ImGui::SameLine(); + + char text[21] = ""; + strncpy(text, Text.c_str(), sizeof(text)); + ImGui::InputText("Enter text", text, IM_ARRAYSIZE(text)); + + Text = std::string(text); + + ImGui::Text("Mode"); + ImGui::SameLine(); + + int32_t mode = static_cast<int32_t>(Mode); + const char* items[] = { "STATIONARY", "FOLLOW PLAYER" }; + + if (ImGui::Combo("##Type", &mode, items, IM_ARRAYSIZE(items))) { + Mode = static_cast<TextMode>(mode); + updated = true; + } + + ImGui::DragFloat("Far Render Dist", &Far); + ImGui::DragFloat("Close Render Dist", &Close); + + ImGui::Checkbox("Face Camera", &FaceCamera); + if (!FaceCamera) { + ImGui::Text("Rotation"); + ImGui::SameLine(); + + IRotator objRot = GetRotation(); + + // Convert to temporary int values (to prevent writing 32bit values to 16bit variables) + int rot[3] = { + objRot.pitch, + objRot.yaw, + objRot.roll + }; + + if (ImGui::DragInt3("##Rotation", rot, 5.0f)) { + for (size_t i = 0; i < 3; i++) { + // Wrap around 0–65535 + rot[i] = (rot[i] % 65536 + 65536) % 65536; + } + IRotator newRot; + newRot.Set( + static_cast<uint16_t>(rot[0]), + static_cast<uint16_t>(rot[1]), + static_cast<uint16_t>(rot[2]) + ); + Rotate(newRot); + } + } + + switch(mode) { + case STATIONARY: { + ImGui::Text("Location"); + ImGui::SameLine(); + FVector location = GetLocation(); + if (ImGui::DragFloat3("##Location", (float*)&location)) { + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + break; + } + case FOLLOW_PLAYER: + // Allow setting PlayerIndex + int32_t playerIdx = PlayerIndex + 1; + + // Draw the input box (ImGui input limited between 1 and 8) + ImGui::SetNextItemWidth(100); + if (ImGui::InputInt("Follow Player", &playerIdx)) { + // Clamp display value to [1, 8] + if (playerIdx < 1) playerIdx = 1; + if (playerIdx > 8) playerIdx = 8; + + // Update the internal value (0–7) + PlayerIndex = playerIdx - 1; + } + + ImGui::DragFloat("Width Offset", &WidthOffset); + ImGui::DragFloat("Height Offset", &HeightOffset); + break; + } + + DrawColourEditor(&updated); + + ImGui::Text("Transform Settings"); + ImGui::Separator(); + + ImGui::SetNextItemWidth(100); + ImGui::DragFloat("Scale X", &ScaleX, 0.1f, -5.0f, 5.0f, "%.2f"); + + ImGui::SetNextItemWidth(100); + if (ImGui::DragFloat("Letter Spacing", &LetterSpacing, 0.1f, 0.0f, 5.0f, "%.2f")) { + updated = true; + } + + if (updated) { + Refresh(); + } +} + +void AText::DrawColourEditor(bool* updated) { + ImGui::Checkbox("Use single colour", &SingleColour); + + if (SingleColour) + { + // Convert 8bit colours to float + ImVec4 colour( + TextColour[0].r / 255.0f, + TextColour[0].g / 255.0f, + TextColour[0].b / 255.0f, + TextColour[0].a / 255.0f + ); + + // Single color input + ImGui::ColorEdit4("Colour", (float*)&colour); + // Apply same color to all vertices + for (int i = 0; i < 4; i++) { + TextColour[i].r = FloatToU8(colour.x); + TextColour[i].g = FloatToU8(colour.y); + TextColour[i].b = FloatToU8(colour.z); + TextColour[i].a = FloatToU8(colour.w); + } + *updated = true; + + } else { + // Separate color pickers for each vertex + for (int i = 0; i < 4; i++) + { + ImVec4 colour2( + TextColour[i].r / 255.0f, + TextColour[i].g / 255.0f, + TextColour[i].b / 255.0f, + TextColour[i].a / 255.0f + ); + char label[32]; + snprintf(label, sizeof(label), "Vtx %d Colour", i); + if (ImGui::ColorEdit4(label, (float*)&colour2)) { + TextColour[i].r = FloatToU8(colour2.x); + TextColour[i].g = FloatToU8(colour2.y); + TextColour[i].b = FloatToU8(colour2.z); + TextColour[i].a = FloatToU8(colour2.w); + *updated = true; + } + } + } +}
\ No newline at end of file diff --git a/src/engine/actors/Text.h b/src/engine/actors/Text.h new file mode 100644 index 000000000..7a9ac7407 --- /dev/null +++ b/src/engine/actors/Text.h @@ -0,0 +1,149 @@ +#pragma once + +#include <libultraship.h> +#include "engine/Actor.h" +#include "src/textures.h" +#include "engine/CoreMath.h" +#include "port/Game.h" + +class AText : public AActor { +public: + enum TextMode : int16_t { + STATIONARY, + FOLLOW_PLAYER + }; + + enum FadeMode : int16_t { + NO_FADE, + FADE_IN, + FADE_OUT + }; + + enum DistanceProps : int16_t { + TOO_CLOSE, + ACTIVE, + TOO_FAR + }; + + DistanceProps Dist = ACTIVE; + DistanceProps PrevState = ACTIVE; + FadeMode FadeState = NO_FADE; + + struct CharacterList { + const char* Texture; + f32 column; + f32 row; + u32 width; + u32 height; + s32 mode; + Vtx vtx[4]; + }; + + std::vector<CharacterList> TextureList; + + std::string Text; // The text to be displayed + TextMode Mode; + uint32_t PlayerIndex; + float WidthOffset = 0.0f; + float HeightOffset = 8.0f; // Place text above player + f32 ScaleX = 1.0f; + f32 LetterSpacing = 1.0f; + f32 Far = 14000.0f; + f32 Close = 350.0f; + + bool FaceCamera = true; + bool Animate = false; + bool SingleColour = true; // Only used for imGUI to show more colour options + // 1 colour for each of the 4 vtx + // This allows setting each vtx colour individually + RGBA8 TextColour[4]; + + // Constructor + AText(const SpawnParams& params); + virtual ~AText() override = default; + + /** + * This is simply a helper function to keep Spawning code clean + * Main parameters usage: + * + * PlayerIndex is only used if textMode is FOLLOW_PLAYER + * + * Other available options: + * + * TextColour<RGBA8> - Colour of the text (1 colour for each vertex) + * ScaleX<float> - Font scale Y + * LetterSpacing<float> - Space between letters + * HeightOffset<float> - Height above player when in FOLLOW_PLAYER mode + * WidthOffset<float> - Left/right offset of the text when in FOLLOW_PLAYER mode + * Far<float> - Skip rendering if camera is too far away + * Close<float> - Skip rendering if too close to the camera + * Animate<bool> - Cycle colours similar to the grand prix title text + * + * Other options usage: + * + * AText* text = AText::Spawn("Hello World", FVector(0, 0, 0), etc.); + * text->Animate = true; + * text->Far = 14000.0f; + * text->ScaleX = 0.1f; // Recommend in the range of 0-2.0f. Default 1.0f + * text->LetterSpacing = 1.0f; // Default 1.0f + * + * For one single colour, set all vertices to the same colour. + * text->TextColour[0] = {255, 0, 0, 255}; // Red + * text->TextColour[1] = {0, 255, 0, 255}; // Green + * text->TextColour[2] = {0, 0, 255, 255}; // Blue + * text->TextColour[3] = {255, 255, 0, 255}; // Yellow + * + * The above will result in a gradient between red, green, blue, and yellow + * For transparency {0, 0, 0, 100} <-- alpha value of 100 will render semi-transparent black text. + * + */ + static inline AText* Spawn(std::string text, FVector pos, FVector scale, AText::TextMode textMode, int16_t playerIndex) { + SpawnParams params = { + .Name = "hm:text", + .Type = static_cast<int16_t>(textMode), + .Behaviour = static_cast<int16_t>(playerIndex), + .Skin = text, + .Location = pos, + .Scale = scale, + }; + return static_cast<AText*>(gWorldInstance.AddActor(new AText(params))); + } + + // Virtual functions to be overridden by derived classes + virtual void Tick() override; + virtual void Draw(Camera* camera) override; + virtual void SetSpawnParams(SpawnParams& params) override; + virtual bool IsMod() override; + virtual void DrawEditorProperties() override; + void DrawColourEditor(bool* updated); + void FollowPlayer(); + + std::string ValidateString(const std::string_view& text); + void Refresh(); + void Print3D(char* text, s32 tracking, s32 mode); + void PrintLetter3D(MenuTexture* glyphTexture, f32 column, f32 row, s32 mode); + void SetupVtx(); + void DrawText3D(Camera* camera); // Based on func_80095BD0 + void AnimateColour(Vtx* vtx); // Animate the vtx colours + void FadeIn(Vtx* vtx); + void FadeOut(Vtx* vtx); + + inline uint8_t FloatToU8(float v) { + return (uint8_t)(v * 255.0f); + } + + Vtx myVtx[12] = { // D_02007BB8 + {{{ 0, 16, 0}, 0, { 0, 0}, {0xff, 0xff, 0xff, 0xff}}}, + {{{26, 16, 0}, 0, {1600, 0}, {0xff, 0xff, 0xff, 0xff}}}, + {{{26, 0, 0}, 0, {1600, 960}, {0xff, 0xff, 0xff, 0xff}}}, + {{{ 0, 0, 0}, 0, { 0, 960}, {0xff, 0xff, 0xff, 0xff}}}, + {{{ 0, 16, 0}, 0, { 0, 0}, {0xff, 0xff, 0xff, 0xff}}}, + {{{16, 16, 0}, 0, {960, 0}, {0xff, 0xff, 0xff, 0xff}}}, + {{{16, 0, 0}, 0, {960, 960}, {0xff, 0xff, 0xff, 0xff}}}, + {{{ 0, 0, 0}, 0, { 0, 960}, {0xff, 0xff, 0xff, 0xff}}}, + {{{ 0, 32, 0}, 0, { 0, 0}, {0xff, 0xff, 0xff, 0xff}}}, + {{{30, 32, 0}, 0, {1856, 0}, {0xff, 0xff, 0xff, 0xff}}}, + {{{30, 0, 0}, 0, {1856, 1984}, {0xff, 0xff, 0xff, 0xff}}}, + {{{ 0, 0, 0}, 0, { 0, 1984}, {0xff, 0xff, 0xff, 0xff}}}, + }; +}; diff --git a/src/engine/actors/Tree.cpp b/src/engine/actors/Tree.cpp index faffd0798..69c3db5b8 100644 --- a/src/engine/actors/Tree.cpp +++ b/src/engine/actors/Tree.cpp @@ -11,6 +11,7 @@ extern "C" { ATree::ATree(Vec3f pos, Gfx* displaylist, f32 drawDistance, f32 minDrawDistance, const char* tlut = nullptr) { Name = "Tree"; + ResourceName = "mk:tree"; Pos[0] = pos[0]; Pos[1] = pos[1]; Pos[2] = pos[2]; diff --git a/src/engine/actors/WarioSign.cpp b/src/engine/actors/WarioSign.cpp index 0e83e114f..66cffcb4b 100644 --- a/src/engine/actors/WarioSign.cpp +++ b/src/engine/actors/WarioSign.cpp @@ -8,18 +8,40 @@ extern "C" { #include "math_util.h" #include "main.h" #include "actor_types.h" +#include "code_800029B0.h" +#include "collision.h" } -AWarioSign::AWarioSign(FVector pos) { +AWarioSign::AWarioSign(const SpawnParams& params) : AActor(params) { Type = ACTOR_WARIO_SIGN; Name = "Wario Sign"; - Pos[0] = pos.x; + ResourceName = "mk:wario_sign"; + Model = d_course_wario_stadium_dl_sign; + + Speed = params.Speed.value_or(182); + + FVector pos = params.Location.value_or(FVector(0, 0, 0)); + Pos[0] = pos.x * gCourseDirection; Pos[1] = pos.y; Pos[2] = pos.z; + + IRotator rot = params.Rotation.value_or(IRotator(0, 0, 0)); + Rot[0] = rot.pitch; + Rot[1] = rot.yaw; + Rot[2] = rot.roll; + + Scale = params.Scale.value_or(FVector(1.0f, 1.0f, 1.0f)); + + func_802AAAAC(&Unk30); + Flags = -0x8000; +} + +bool AWarioSign::IsMod() { + return true; } void AWarioSign::Tick() { - Rot[1] += 0xB6; + Rot[1] += Speed; } void AWarioSign::Draw(Camera *camera) { diff --git a/src/engine/actors/WarioSign.h b/src/engine/actors/WarioSign.h index 634b2132e..511d2506d 100644 --- a/src/engine/actors/WarioSign.h +++ b/src/engine/actors/WarioSign.h @@ -3,6 +3,7 @@ #include <libultraship.h> #include "engine/Actor.h" #include "CoreMath.h" +#include "engine/World.h" extern "C" { #include "common_structs.h" @@ -12,8 +13,22 @@ class AWarioSign : public AActor { public: virtual ~AWarioSign() = default; - explicit AWarioSign(FVector pos); + explicit AWarioSign(const SpawnParams& params); + // This is simply a helper function to keep Spawning code clean + static inline AWarioSign* Spawn(FVector pos, IRotator rot, FVector velocity, FVector scale) { + SpawnParams params = { + .Name = "mk:wario_sign", + .Location = pos, + .Rotation = rot, + .Scale = scale, + .Velocity = velocity, + .Speed = 182, + }; + return static_cast<AWarioSign*>(gWorldInstance.AddActor(new AWarioSign(params))); + } + + virtual bool IsMod() override; virtual void Tick() override; virtual void Draw(Camera*) override; }; diff --git a/src/engine/courses/BansheeBoardwalk.cpp b/src/engine/courses/BansheeBoardwalk.cpp index 205fe24c1..b9634cd60 100644 --- a/src/engine/courses/BansheeBoardwalk.cpp +++ b/src/engine/courses/BansheeBoardwalk.cpp @@ -164,8 +164,8 @@ void BansheeBoardwalk::LoadTextures() { void BansheeBoardwalk::BeginPlay() { spawn_all_item_boxes((struct ActorSpawnData*)LOAD_ASSET_RAW(d_course_banshee_boardwalk_item_box_spawns)); - gWorldInstance.AddObject(new OCheepCheep(FVector(xOrientation * -1650.0, -200.0f, -1650.0f), - OCheepCheep::CheepType::RACE, IPathSpan(160, 170))); + OCheepCheep::Spawn(FVector(xOrientation * -1650.0, -200.0f, -1650.0f), + OCheepCheep::Behaviour::RACE, IPathSpan(160, 170)); OTrashBin::Behaviour bhv; if (gModeSelection == TIME_TRIALS) { @@ -175,27 +175,25 @@ void BansheeBoardwalk::BeginPlay() { } if (gIsMirrorMode) { - gWorldInstance.AddObject(new OTrashBin(FVector(1765.0f, 45.0f, 195.0f), IRotator(0, 180, 0), 1.0f, bhv)); + OTrashBin::Spawn(FVector(1765.0f, 45.0f, 195.0f), IRotator(0, 180, 0), 1.0f, bhv); } else { - gWorldInstance.AddObject(new OTrashBin(FVector(-1765.0f, 45.0f, 70.0f), IRotator(0, 0, 0), 1.0f, bhv)); + OTrashBin::Spawn(FVector(-1765.0f, 45.0f, 70.0f), IRotator(0, 0, 0), 1.0f, bhv); } if ((gGamestate != CREDITS_SEQUENCE) && (gModeSelection != TIME_TRIALS)) { - gWorldInstance.AddObject(new OBat(FVector(0,0,0), IRotator(0, 0, 90))); - gWorldInstance.AddObject(new OBoos(5, IPathSpan(180, 190), IPathSpan(200, 210), IPathSpan(280, 290))); - gWorldInstance.AddObject(new OBoos(5, IPathSpan(490, 500), IPathSpan(510, 520), IPathSpan(620, 630))); + OBat::Spawn(FVector(0,0,0), IRotator(0, 0, 90)); + OBoos::Spawn(5, IPathSpan(180, 190), IPathSpan(200, 210), IPathSpan(280, 290)); + OBoos::Spawn(5, IPathSpan(490, 500), IPathSpan(510, 520), IPathSpan(620, 630)); } if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][110], 110, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][190], 190, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][250], 250, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][475], 475, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][610], 610, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 110, 3, 0.8333333f); + OBombKart::Spawn(0, 190, 1, 0.8333333f); + OBombKart::Spawn(0, 250, 3, 0.8333333f); + OBombKart::Spawn(0, 475, 1, 0.8333333f); + OBombKart::Spawn(0, 610, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } } diff --git a/src/engine/courses/BigDonut.cpp b/src/engine/courses/BigDonut.cpp index ed2d1c7fc..1c8fd5b76 100644 --- a/src/engine/courses/BigDonut.cpp +++ b/src/engine/courses/BigDonut.cpp @@ -132,15 +132,13 @@ void BigDonut::BeginPlay() { spawn_all_item_boxes((ActorSpawnData*) LOAD_ASSET_RAW(d_course_big_donut_item_box_spawns)); if (gModeSelection == VERSUS) { - FVector pos = {0, 0, 0}; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][20], 20, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][40], 40, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][60], 60, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][80], 80, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][100], 100, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][120], 120, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][140], 140, 0, 1.0f)); + OBombKart::Spawn(0, 20, 0, 1.0f); + OBombKart::Spawn(0, 40, 0, 1.0f); + OBombKart::Spawn(0, 60, 0, 1.0f); + OBombKart::Spawn(0, 80, 0, 1.0f); + OBombKart::Spawn(0, 100, 0, 1.0f); + OBombKart::Spawn(0, 120, 0, 1.0); + OBombKart::Spawn(0, 140, 0, 1.0f); } } diff --git a/src/engine/courses/BlockFort.cpp b/src/engine/courses/BlockFort.cpp index ae1b9bfb9..0ec9cb555 100644 --- a/src/engine/courses/BlockFort.cpp +++ b/src/engine/courses/BlockFort.cpp @@ -127,15 +127,13 @@ void BlockFort::BeginPlay() { spawn_all_item_boxes((ActorSpawnData*) LOAD_ASSET_RAW(d_course_block_fort_item_box_spawns)); if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][20], 20, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][40], 40, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][60], 60, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][80], 80, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][100], 100, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][120], 120, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][140], 140, 0, 1.0f)); + OBombKart::Spawn(0, 20, 0, 1.0f); + OBombKart::Spawn(0, 40, 0, 1.0f); + OBombKart::Spawn(0, 60, 0, 1.0f); + OBombKart::Spawn(0, 80, 0, 1.0f); + OBombKart::Spawn(0, 100, 0, 1.0f); + OBombKart::Spawn(0, 120, 0, 1.0f); + OBombKart::Spawn(0, 140, 0, 1.0f); } } diff --git a/src/engine/courses/BowsersCastle.cpp b/src/engine/courses/BowsersCastle.cpp index 47f26cf9f..9258683ce 100644 --- a/src/engine/courses/BowsersCastle.cpp +++ b/src/engine/courses/BowsersCastle.cpp @@ -4,7 +4,8 @@ #include <memory> #include "BowsersCastle.h" -#include "World.h" +#include "engine/World.h" +#include "engine/courses/Course.h" #include "engine/actors/Finishline.h" #include "engine/objects/BombKart.h" #include "engine/objects/Thwomp.h" @@ -169,54 +170,52 @@ void BowsersCastle::BeginPlay() { switch (gCCSelection) { case CC_100: case CC_EXTRA: - gWorldInstance.AddObject(new OThwomp(0x0320, 0xf92a, 0xC000, 1.0f, OThwomp::States::STATIONARY, 0)); - gWorldInstance.AddObject(new OThwomp(0x044c, 0xf92a, 0xC000, 1.0f, OThwomp::States::STATIONARY, 1)); - gWorldInstance.AddObject(new OThwomp(0x02bc, 0xf95c, 0xC000, 1.0f, OThwomp::States::MOVE_AND_ROTATE, 0)); - gWorldInstance.AddObject(new OThwomp(0x04b0, 0xf8f8, 0xC000, 1.0f, OThwomp::States::MOVE_AND_ROTATE, 1)); - gWorldInstance.AddObject(new OThwomp(0x04b0, 0xf5ba, 0xC000, 1.0f, OThwomp::States::MOVE_FAR, 0)); - gWorldInstance.AddObject(new OThwomp(0x04b0, 0xf592, 0xC000, 1.0f, OThwomp::States::MOVE_FAR, 1)); - gWorldInstance.AddObject(new OThwomp(0x091a, 0xf5bf, 0xC000, 1.0f, OThwomp::States::STATIONARY_FAST, 0)); - gWorldInstance.AddObject(new OThwomp(0x091a, 0xf597, 0xC000, 1.0f, OThwomp::States::STATIONARY_FAST, 1)); - gWorldInstance.AddObject(new OThwomp(0x0596, 0xf92f, 0xC000, 1.5f, OThwomp::States::JAILED, 0)); - gWorldInstance.AddObject(new OThwomp(0x082a, 0xf9f2, 0x4000, 1.0f, OThwomp::States::SLIDE, 0)); - gWorldInstance.AddObject(new OThwomp(0x073a, 0xf9f2, 0x4000, 1.0f, OThwomp::States::SLIDE, 1)); + OThwomp::Spawn(0x0320, 0xf92a, 0xC000, 1.0f, OThwomp::States::STATIONARY, 0); + OThwomp::Spawn(0x044c, 0xf92a, 0xC000, 1.0f, OThwomp::States::STATIONARY, 1); + OThwomp::Spawn(0x02bc, 0xf95c, 0xC000, 1.0f, OThwomp::States::MOVE_AND_ROTATE, 0); + OThwomp::Spawn(0x04b0, 0xf8f8, 0xC000, 1.0f, OThwomp::States::MOVE_AND_ROTATE, 1); + OThwomp::Spawn(0x04b0, 0xf5ba, 0xC000, 1.0f, OThwomp::States::MOVE_FAR, 0); + OThwomp::Spawn(0x04b0, 0xf592, 0xC000, 1.0f, OThwomp::States::MOVE_FAR, 1); + OThwomp::Spawn(0x091a, 0xf5bf, 0xC000, 1.0f, OThwomp::States::STATIONARY_FAST, 0); + OThwomp::Spawn(0x091a, 0xf597, 0xC000, 1.0f, OThwomp::States::STATIONARY_FAST, 1); + OThwomp::Spawn(0x0596, 0xf92f, 0xC000, 1.5f, OThwomp::States::JAILED, 0); + OThwomp::Spawn(0x082a, 0xf9f2, 0x4000, 1.0f, OThwomp::States::SLIDE, 0); + OThwomp::Spawn(0x073a, 0xf9f2, 0x4000, 1.0f, OThwomp::States::SLIDE, 1); break; case CC_50: - gWorldInstance.AddObject(new OThwomp(0x3B6, 0xF92A, 0xC000, 1.0f, OThwomp::States::STATIONARY, 0)); - gWorldInstance.AddObject(new OThwomp(0x0352, 0xf95c, 0xC000, 1.0f, OThwomp::States::MOVE_AND_ROTATE, 0)); - gWorldInstance.AddObject(new OThwomp(0x04b0, 0xf5ba, 0xC000, 1.0f, OThwomp::States::MOVE_FAR, 0)); - gWorldInstance.AddObject(new OThwomp(0x04b0, 0xf592, 0xC000, 1.0f, OThwomp::States::MOVE_FAR, 1)); - gWorldInstance.AddObject(new OThwomp(0x091a, 0xf5b0, 0xC000, 1.0f, OThwomp::States::STATIONARY_FAST, 0)); - gWorldInstance.AddObject(new OThwomp(0x0596, 0xf92f, 0xC000, 1.5f, OThwomp::States::JAILED, 0)); - gWorldInstance.AddObject(new OThwomp(0x082a, 0xf9f2, 0x4000, 1.0f, OThwomp::States::SLIDE , 0)); - gWorldInstance.AddObject(new OThwomp(0x073a, 0xf9f2, 0x4000, 1.0f, OThwomp::States::SLIDE, 1)); + OThwomp::Spawn(0x3B6, 0xF92A, 0xC000, 1.0f, OThwomp::States::STATIONARY, 0); + OThwomp::Spawn(0x0352, 0xf95c, 0xC000, 1.0f, OThwomp::States::MOVE_AND_ROTATE, 0); + OThwomp::Spawn(0x04b0, 0xf5ba, 0xC000, 1.0f, OThwomp::States::MOVE_FAR, 0); + OThwomp::Spawn(0x04b0, 0xf592, 0xC000, 1.0f, OThwomp::States::MOVE_FAR, 1); + OThwomp::Spawn(0x091a, 0xf5b0, 0xC000, 1.0f, OThwomp::States::STATIONARY_FAST, 0); + OThwomp::Spawn(0x0596, 0xf92f, 0xC000, 1.5f, OThwomp::States::JAILED, 0); + OThwomp::Spawn(0x082a, 0xf9f2, 0x4000, 1.0f, OThwomp::States::SLIDE , 0); + OThwomp::Spawn(0x073a, 0xf9f2, 0x4000, 1.0f, OThwomp::States::SLIDE, 1); break; case CC_150: - gWorldInstance.AddObject(new OThwomp(0x0320, 0xf92a, 0xC000, 1.0f, OThwomp::States::STATIONARY, 0)); - gWorldInstance.AddObject(new OThwomp(0x044c, 0xf92a, 0xC000, 1.0f, OThwomp::States::STATIONARY, 1)); - gWorldInstance.AddObject(new OThwomp(0x02bc, 0xf95c, 0xC000, 1.0f, OThwomp::States::MOVE_AND_ROTATE, 0)); - gWorldInstance.AddObject(new OThwomp(0x04b0, 0xf8f8, 0xC000, 1.0f, OThwomp::States::MOVE_AND_ROTATE, 1)); - gWorldInstance.AddObject(new OThwomp(0x04b0, 0xf5ba, 0xC000, 1.0f, OThwomp::States::MOVE_FAR, 0)); - gWorldInstance.AddObject(new OThwomp(0x04b0, 0xf592, 0xC000, 1.0f, OThwomp::States::MOVE_FAR, 1)); - gWorldInstance.AddObject(new OThwomp(0x091a, 0xf5c9, 0xC000, 1.0f, OThwomp::States::STATIONARY_FAST, 0)); - gWorldInstance.AddObject(new OThwomp(0x091a, 0xf5ab, 0xC000, 1.0f, OThwomp::States::STATIONARY_FAST, 1)); - gWorldInstance.AddObject(new OThwomp(0x091a, 0xf58d, 0xC000, 1.0f, OThwomp::States::STATIONARY_FAST, 2)); - gWorldInstance.AddObject(new OThwomp(0x0596, 0xf92f, 0xC000, 1.5f, OThwomp::States::JAILED, 0)); - gWorldInstance.AddObject(new OThwomp(0x082a, 0xf9f2, 0x4000, 1.0f, OThwomp::States::SLIDE, 0)); - gWorldInstance.AddObject(new OThwomp(0x073a, 0xf9f2, 0x4000, 1.0f, OThwomp::States::SLIDE, 1)); + OThwomp::Spawn(0x0320, 0xf92a, 0xC000, 1.0f, OThwomp::States::STATIONARY, 0); + OThwomp::Spawn(0x044c, 0xf92a, 0xC000, 1.0f, OThwomp::States::STATIONARY, 1); + OThwomp::Spawn(0x02bc, 0xf95c, 0xC000, 1.0f, OThwomp::States::MOVE_AND_ROTATE, 0); + OThwomp::Spawn(0x04b0, 0xf8f8, 0xC000, 1.0f, OThwomp::States::MOVE_AND_ROTATE, 1); + OThwomp::Spawn(0x04b0, 0xf5ba, 0xC000, 1.0f, OThwomp::States::MOVE_FAR, 0); + OThwomp::Spawn(0x04b0, 0xf592, 0xC000, 1.0f, OThwomp::States::MOVE_FAR, 1); + OThwomp::Spawn(0x091a, 0xf5c9, 0xC000, 1.0f, OThwomp::States::STATIONARY_FAST, 0); + OThwomp::Spawn(0x091a, 0xf5ab, 0xC000, 1.0f, OThwomp::States::STATIONARY_FAST, 1); + OThwomp::Spawn(0x091a, 0xf58d, 0xC000, 1.0f, OThwomp::States::STATIONARY_FAST, 2); + OThwomp::Spawn(0x0596, 0xf92f, 0xC000, 1.5f, OThwomp::States::JAILED, 0); + OThwomp::Spawn(0x082a, 0xf9f2, 0x4000, 1.0f, OThwomp::States::SLIDE, 0); + OThwomp::Spawn(0x073a, 0xf9f2, 0x4000, 1.0f, OThwomp::States::SLIDE, 1); break; } if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][50], 50, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][150], 150, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][200], 200, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][260], 260, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][435], 435, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 50, 3, 0.8333333f); + OBombKart::Spawn(0, 150, 1, 0.8333333f); + OBombKart::Spawn(0, 200, 3, 0.8333333f); + OBombKart::Spawn(0, 260, 1, 0.8333333f); + OBombKart::Spawn(0, 435, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } } diff --git a/src/engine/courses/ChocoMountain.cpp b/src/engine/courses/ChocoMountain.cpp index 6152d9911..8fa1c1d19 100644 --- a/src/engine/courses/ChocoMountain.cpp +++ b/src/engine/courses/ChocoMountain.cpp @@ -8,6 +8,7 @@ #include "engine/objects/BombKart.h" #include "choco_mountain_data.h" #include "engine/actors/Finishline.h" +#include "engine/actors/FallingRock.h" extern "C" { #include "main.h" @@ -177,18 +178,18 @@ void ChocoMountain::LoadTextures() { void ChocoMountain::BeginPlay() { spawn_all_item_boxes((struct ActorSpawnData*)LOAD_ASSET_RAW(d_course_choco_mountain_item_box_spawns)); - spawn_falling_rocks((struct ActorSpawnData*)LOAD_ASSET_RAW((const char*)d_course_choco_mountain_falling_rock_spawns)); + AFallingRock::Spawn(FVector(2019, 156, 164), 60); + AFallingRock::Spawn(FVector(2018, 155, 379), 120); + AFallingRock::Spawn(FVector(1996, 146, 505), 180); if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][140], 140, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][165], 165, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][330], 330, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][550], 550, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][595], 595, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 140, 3, 0.8333333f); + OBombKart::Spawn(0, 165, 1, 0.8333333f); + OBombKart::Spawn(0, 330, 3, 0.8333333f); + OBombKart::Spawn(0, 550, 1, 0.8333333f); + OBombKart::Spawn(0, 595, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } } diff --git a/src/engine/courses/Course.cpp b/src/engine/courses/Course.cpp index aa87ac9b3..3ad991f0a 100644 --- a/src/engine/courses/Course.cpp +++ b/src/engine/courses/Course.cpp @@ -6,6 +6,8 @@ #include "port/Game.h" #include "port/resource/type/TrackPathPointData.h" #include "port/resource/type/TrackSections.h" +#include "engine/editor/SceneManager.h" +#include "Registry.h" extern "C" { #include "main.h" @@ -24,6 +26,7 @@ extern "C" { #include "collision.h" #include "actors.h" #include "math_util.h" +#include "code_80005FD0.h" extern StaffGhost* d_mario_raceway_staff_ghost; } @@ -99,6 +102,7 @@ Course::Course() { Props.PathTable2[1] = NULL; Props.PathTable2[2] = NULL; Props.PathTable2[3] = NULL; + Props.PathTable2[4] = NULL; Props.Clouds = NULL; Props.CloudList = NULL; @@ -115,6 +119,7 @@ void Course::Load(Vtx* vtx, Gfx* gfx) { void Course::LoadO2R(std::string trackPath) { if (!trackPath.empty()) { + SceneFilePtr = (trackPath + "/scene.json"); TrackSectionsPtr = (trackPath + "/data_track_sections"); std::string path_file = (trackPath + "/data_paths").c_str(); @@ -125,22 +130,19 @@ void Course::LoadO2R(std::string trackPath) { auto& paths = res->PathList; size_t i = 0; + u16* ptr = &Props.PathSizes.unk0; for (auto& path : paths) { - if (i == 0) { - Props.PathSizes.unk0 = path.size(); - Props.PathTable[0] = (TrackPathPoint*) path.data(); - Props.PathTable[1] = NULL; - Props.PathTable[2] = NULL; - Props.PathTable[3] = NULL; - Props.PathTable2[0] = (TrackPathPoint*) path.data(); - Props.PathTable2[1] = NULL; - Props.PathTable2[2] = NULL; - Props.PathTable2[3] = NULL; + if (i >= ARRAY_COUNT(Props.PathTable2)) { + printf("[Course.cpp] The game can only import 5 paths. Found more than 5. Skipping the rest\n"); + break; // Only 5 paths allowed. 4 track, 1 vehicle } + ptr[i] = path.size(); + Props.PathTable2[i] = (TrackPathPoint*) path.data(); i += 1; } } + gVehiclePathSize = Props.PathSizes.unk0; // This is likely incorrect. } else { printf("Course.cpp: LoadO2R: trackPath str is empty\n"); @@ -149,6 +151,10 @@ void Course::LoadO2R(std::string trackPath) { // Load stock and o2r tracks void Course::Load() { + // Re-load scenefile in-case changes were made in the editor + if (!SceneFilePtr.empty()) { + Editor::LoadLevel(this, SceneFilePtr); + } // Load from O2R if (!TrackSectionsPtr.empty()) { @@ -219,6 +225,7 @@ void Course::Load() { // C++ version of parse_course_displaylists() void Course::ParseCourseSections(TrackSectionsO2R* sections, size_t size) { + printf("\n[Track] Generating Collision Meshes...\n"); for (size_t i = 0; i < (size / sizeof(TrackSectionsO2R)); i++) { if (sections[i].flags & 0x8000) { D_8015F59C = 1; // single-sided wall @@ -235,10 +242,11 @@ void Course::ParseCourseSections(TrackSectionsO2R* sections, size_t size) { } else { D_8015F5A4 = 0; } - printf("LOADING DL %s\n", sections[i].addr.c_str()); + printf(" %s\n", sections[i].addr.c_str()); generate_collision_mesh((Gfx*) LOAD_ASSET_RAW(sections[i].addr.c_str()), sections[i].surfaceType, sections[i].sectionId); } + printf("[Track] Collision Mesh Generation Complete!\n\n"); } void Course::TestPath() { @@ -251,9 +259,9 @@ void Course::TestPath() { Vec3f vel = { 0, 0, 0 }; for (size_t i = 0; i < gPathCountByPathIndex[0]; i++) { - x = gTrackPaths[0][i].posX; - y = gTrackPaths[0][i].posY; - z = gTrackPaths[0][i].posZ; + x = gTrackPaths[0][i].x; + y = gTrackPaths[0][i].y; + z = gTrackPaths[0][i].z; if (((x & 0xFFFF) == 0x8000) && ((y & 0xFFFF) == 0x8000) && ((z & 0xFFFF) == 0x8000)) { break; @@ -289,7 +297,21 @@ void Course::LoadTextures() { } void Course::BeginPlay() { + printf("[Track] BeginPlay\n"); TestPath(); + this->SpawnActors(); +} + +// Spawns actors from SpawnParams set by the scene file in SceneManager.cpp +void Course::SpawnActors() { + for (const auto& actor : SpawnList) { + auto it = gActorRegistry.find(actor.Name); + if (it != gActorRegistry.end() && it->second.spawnFunc) { + it->second.spawnFunc(actor); + } else { + printf("Actor not found %s\n", actor.Name.c_str()); + } + } } void Course::InitClouds() { diff --git a/src/engine/courses/Course.h b/src/engine/courses/Course.h index 2370d916c..80e61fdbd 100644 --- a/src/engine/courses/Course.h +++ b/src/engine/courses/Course.h @@ -1,10 +1,13 @@ #ifndef ENGINE_COURSE_H #define ENGINE_COURSE_H -#include <libultraship.h> +#include <libultraship/libultraship.h> #include "CoreMath.h" #ifdef __cplusplus +#include "engine/SpawnParams.h" +#include <optional> +#include <nlohmann/json.hpp> #include "engine/objects/Lakitu.h" #include "port/resource/type/TrackSections.h" extern "C" { @@ -81,8 +84,8 @@ typedef struct Properties { Vec4f NormalTargetSpeed; Vec4f D_0D0096B8; Vec4f OffTrackTargetSpeed; - TrackPathPoint* PathTable[4]; - TrackPathPoint* PathTable2[4]; + TrackPathPoint* PathTable[4]; // Only used for podium ceremony + TrackPathPoint* PathTable2[5]; // The fifth entry is for vehicles uint8_t* CloudTexture; CloudData *Clouds; CloudData *CloudList; @@ -286,6 +289,7 @@ typedef struct Properties { #ifdef __cplusplus + class World; // <-- Forward declare class Course { @@ -304,8 +308,14 @@ public: const course_texture* textures = nullptr; bool bSpawnFinishline = true; std::optional<FVector> FinishlineSpawnPoint; + + // O2R Loading + std::shared_ptr<Ship::Archive> RootArchive; + std::string SceneFilePtr; std::string TrackSectionsPtr; + bool bIsMod = false; + std::vector<SpawnParams> SpawnList; virtual ~Course() = default; @@ -322,6 +332,7 @@ public: * Actor spawning should go here. */ virtual void BeginPlay(); + void SpawnActors(); virtual void TestPath(); virtual void InitClouds(); virtual void UpdateClouds(s32, Camera*); diff --git a/src/engine/courses/DKJungle.cpp b/src/engine/courses/DKJungle.cpp index 7be091ad3..261f4b672 100644 --- a/src/engine/courses/DKJungle.cpp +++ b/src/engine/courses/DKJungle.cpp @@ -205,18 +205,16 @@ void DKJungle::BeginPlay() { // The original game only ran vehicle logic every second frame. // Thus the speed gets divided by two to set speed to match properly - gWorldInstance.AddActor(new ABoat((0.6666666f)/4, 0)); + ABoat::Spawn((0.6666666f)/4, 0, 0, ABoat::SpawnMode::POINT); if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][50], 50, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][100], 100, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][150], 150, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][190], 190, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][250], 250, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 50, 3, 0.8333333f); + OBombKart::Spawn(0, 100, 1, 0.8333333f); + OBombKart::Spawn(0, 150, 3, 0.8333333f); + OBombKart::Spawn(0, 190, 1, 0.8333333f); + OBombKart::Spawn(0, 250, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } } } diff --git a/src/engine/courses/DoubleDeck.cpp b/src/engine/courses/DoubleDeck.cpp index 5331d4bc6..e92bc22d8 100644 --- a/src/engine/courses/DoubleDeck.cpp +++ b/src/engine/courses/DoubleDeck.cpp @@ -129,15 +129,13 @@ void DoubleDeck::BeginPlay() { spawn_all_item_boxes((ActorSpawnData*)LOAD_ASSET_RAW(d_course_double_deck_item_box_spawns)); if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][20], 20, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][40], 40, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][60], 60, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][80], 80, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][100], 100, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][120], 120, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][140], 140, 0, 1.0f)); + OBombKart::Spawn(0, 20, 0, 1.0f); + OBombKart::Spawn(0, 40, 0, 1.0f); + OBombKart::Spawn(0, 60, 0, 1.0f); + OBombKart::Spawn(0, 80, 0, 1.0f); + OBombKart::Spawn(0, 100, 0, 1.0f); + OBombKart::Spawn(0, 120, 0, 1.0f); + OBombKart::Spawn(0, 140, 0, 1.0f); } } diff --git a/src/engine/courses/FrappeSnowland.cpp b/src/engine/courses/FrappeSnowland.cpp index c4185b165..1be4bf04a 100644 --- a/src/engine/courses/FrappeSnowland.cpp +++ b/src/engine/courses/FrappeSnowland.cpp @@ -145,37 +145,35 @@ void FrappeSnowland::BeginPlay() { spawn_all_item_boxes((struct ActorSpawnData*)LOAD_ASSET_RAW(d_course_frappe_snowland_item_box_spawns)); if (gGamestate != CREDITS_SEQUENCE) { - gWorldInstance.AddObject(new OSnowman(FVector(697, 0, -1684))); - gWorldInstance.AddObject(new OSnowman(FVector(82, 0, -2245))); - gWorldInstance.AddObject(new OSnowman(FVector(27, 5, -2067))); - gWorldInstance.AddObject(new OSnowman(FVector(-656, 0, -1735))); - gWorldInstance.AddObject(new OSnowman(FVector(-1497, 0, -83))); - gWorldInstance.AddObject(new OSnowman(FVector(-1643, 0, -25))); - gWorldInstance.AddObject(new OSnowman(FVector(-1547, 0, -20))); - gWorldInstance.AddObject(new OSnowman(FVector(-1445, 0, -10))); - gWorldInstance.AddObject(new OSnowman(FVector(-1502, 0, 61))); - gWorldInstance.AddObject(new OSnowman(FVector(-1429, 0, 79))); - gWorldInstance.AddObject(new OSnowman(FVector(-1586, 0, 71))); - gWorldInstance.AddObject(new OSnowman(FVector(-1471, 0, 157))); - gWorldInstance.AddObject(new OSnowman(FVector(-1539, 0, 175))); - gWorldInstance.AddObject(new OSnowman(FVector(-1484, 0, 303))); - gWorldInstance.AddObject(new OSnowman(FVector(-1442, 0, 358))); - gWorldInstance.AddObject(new OSnowman(FVector(-1510, 0, 426))); - gWorldInstance.AddObject(new OSnowman(FVector(-665, 0, 830))); - gWorldInstance.AddObject(new OSnowman(FVector(-701, 3, 853))); - gWorldInstance.AddObject(new OSnowman(FVector(-602, 0, 929))); + OSnowman::Spawn(FVector(697, 0, -1684)); + OSnowman::Spawn(FVector(82, 0, -2245)); + OSnowman::Spawn(FVector(27, 5, -2067)); + OSnowman::Spawn(FVector(-656, 0, -1735)); + OSnowman::Spawn(FVector(-1497, 0, -83)); + OSnowman::Spawn(FVector(-1643, 0, -25)); + OSnowman::Spawn(FVector(-1547, 0, -20)); + OSnowman::Spawn(FVector(-1445, 0, -10)); + OSnowman::Spawn(FVector(-1502, 0, 61)); + OSnowman::Spawn(FVector(-1429, 0, 79)); + OSnowman::Spawn(FVector(-1586, 0, 71)); + OSnowman::Spawn(FVector(-1471, 0, 157)); + OSnowman::Spawn(FVector(-1539, 0, 175)); + OSnowman::Spawn(FVector(-1484, 0, 303)); + OSnowman::Spawn(FVector(-1442, 0, 358)); + OSnowman::Spawn(FVector(-1510, 0, 426)); + OSnowman::Spawn(FVector(-665, 0, 830)); + OSnowman::Spawn(FVector(-701, 3, 853)); + OSnowman::Spawn(FVector(-602, 0, 929)); } if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][50], 50, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][100], 100, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][150], 150, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][290], 290, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][350], 350, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 50, 3, 0.8333333f); + OBombKart::Spawn(0, 100, 1, 0.8333333f); + OBombKart::Spawn(0, 150, 3, 0.8333333f); + OBombKart::Spawn(0, 290, 1, 0.8333333f); + OBombKart::Spawn(0, 350, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } } diff --git a/src/engine/courses/Harbour.cpp b/src/engine/courses/Harbour.cpp index d1f455b93..db81d065b 100644 --- a/src/engine/courses/Harbour.cpp +++ b/src/engine/courses/Harbour.cpp @@ -620,75 +620,8 @@ void Harbour::Load() { void Harbour::LoadTextures() { dma_textures(gTextureTrees1, 0x0000035BU, 0x00000800U); // 0x03009000 - D_802BA058 = dma_textures(gTexturePiranhaPlant1, 0x000003E8U, 0x00000800U); // 0x03009800 - dma_textures(gTexturePiranhaPlant2, 0x000003E8U, 0x00000800U); // 0x0300A000 - dma_textures(gTexturePiranhaPlant3, 0x000003E8U, 0x00000800U); // 0x0300A800 - dma_textures(gTexturePiranhaPlant4, 0x000003E8U, 0x00000800U); // 0x0300B000 - dma_textures(gTexturePiranhaPlant5, 0x000003E8U, 0x00000800U); // 0x0300B800 - dma_textures(gTexturePiranhaPlant6, 0x000003E8U, 0x00000800U); // 0x0300C000 - dma_textures(gTexturePiranhaPlant7, 0x000003E8U, 0x00000800U); // 0x0300C800 - dma_textures(gTexturePiranhaPlant8, 0x000003E8U, 0x00000800U); // 0x0300D000 - dma_textures(gTexturePiranhaPlant9, 0x000003E8U, 0x00000800U); // 0x0300D800 } -Path2D harbour_path2D[] = { - { 0, 0}, - { 0, -100}, - { 0, -200}, - { 0, -300}, - { 0, -400}, - { 0, -500}, - { 0, -600}, - { 0, -700}, - { 0, -800}, - { 0, -900}, - { 0, -1000}, - { 0, -1096}, // Main point 1 - { 100, -1090}, - { 200, -1085}, - { 300, -1080}, - { 400, -1075}, - { 500, -1072}, // Curve begins to smooth here - { 600, -1068}, - { 700, -1065}, - { 800, -1063}, - { 900, -1061}, - { 984, -1060}, // Main point 2 - { 990, -900}, - { 995, -800}, - { 997, -700}, - { 998, -600}, - { 999, -500}, - { 999, -400}, - { 999, -300}, - { 999, -200}, - { 999, -100}, - { 999, 0}, - { 999, 100}, - { 999, 200}, - { 999, 300}, - { 999, 400}, - { 999, 500}, - { 999, 600}, - { 999, 700}, - { 999, 800}, - { 999, 900}, - { 999, 940}, // Main point 3 - { 900, 945}, - { 800, 945}, - { 700, 947}, - { 600, 948}, - { 500, 949}, - { 400, 949}, - { 300, 949}, - { 200, 950}, - { 100, 950}, - { 0, 950}, // Main point 4 - - // End of path - { -32768, -32768 } // Terminator -}; - void Harbour::BeginPlay() { struct ActorSpawnData itemboxes[] = { { 200, 1500, 200 , 0}, @@ -742,22 +675,8 @@ void Harbour::BeginPlay() { // gWorldInstance.AddObject(new OBoos(10, IPathSpan(0, 5), IPathSpan(18, 23), IPathSpan(25, 50))); - // gVehicle2DPathPoint = harbour_path2D; - //gVehicle2DPathLength = 53; - // D_80162EB0 = spawn_actor_on_surface(harbour_path2D[0].x, 2000.0f, harbour_path2D[0].z); - - - // DEBUG ONLY TO VISUALIZE PATH - // for (size_t i = 0; i < ARRAY_COUNT(harbour_path); i++) { - // if (i % 10 == 1) { - // f32 height = spawn_actor_on_surface(harbour_path[i].posX, 2000.0f, harbour_path[i].posZ); - // Vec3f itemPos = {harbour_path[i].posX, height, harbour_path[i].posZ}; - // add_actor_to_empty_slot(itemPos, rot, vel, ACTOR_ITEM_BOX); - // } - // } //gWorldInstance.AddActor(new AShip(FVector(-1694, -111, 1451), AShip::Skin::GHOSTSHIP)); //gWorldInstance.AddActor(new AShip(FVector(2811, -83, 966), AShip::Skin::SHIP2)); - //gWorldInstance.AddObject(new OGrandPrixBalloons(FVector(16, -136, -34))); } void Harbour::WhatDoesThisDo(Player* player, int8_t playerId) { diff --git a/src/engine/courses/KalimariDesert.cpp b/src/engine/courses/KalimariDesert.cpp index 3ecb3f03d..4886174f1 100644 --- a/src/engine/courses/KalimariDesert.cpp +++ b/src/engine/courses/KalimariDesert.cpp @@ -4,7 +4,7 @@ #include <memory> #include "KalimariDesert.h" -#include "World.h" +#include "engine/World.h" #include "engine/actors/Finishline.h" #include "engine/objects/BombKart.h" #include "kalimari_desert_data.h" @@ -198,7 +198,9 @@ void KalimariDesert::BeginPlay() { // Spawn two trains for (size_t i = 0; i < _numTrains; ++i) { - uint32_t waypoint = CalculateWaypointDistribution(i, _numTrains, gVehicle2DPathLength, centerWaypoint); + // outputs 160 for train 1 and 392 for train 2. + // If using more trains, it wraps the value to always output a valid waypoint. + uint32_t waypoint = CalculateWaypointDistribution(i, _numTrains, gVehiclePathSize, centerWaypoint); if (CVarGetInteger("gMultiplayerNoFeatureCuts", 0) == false) { // Multiplayer modes have no tender and no carriages @@ -214,19 +216,17 @@ void KalimariDesert::BeginPlay() { } } - gWorldInstance.AddActor(new ATrain(_tender, _numCarriages, 2.5f, waypoint)); + ATrain::Spawn(_tender, _numCarriages, 2.5f, 0, waypoint, ATrain::SpawnMode::POINT); } if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][50], 50, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][138], 138, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][280], 280, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][404], 404, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][510], 510, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 50, 3, 0.8333333f); + OBombKart::Spawn(0, 138, 1, 0.8333333f); + OBombKart::Spawn(0, 280, 3, 0.8333333f); + OBombKart::Spawn(0, 404, 1, 0.8333333f); + OBombKart::Spawn(0, 510, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } } } diff --git a/src/engine/courses/KoopaTroopaBeach.cpp b/src/engine/courses/KoopaTroopaBeach.cpp index e80676d41..ca1c19f9e 100644 --- a/src/engine/courses/KoopaTroopaBeach.cpp +++ b/src/engine/courses/KoopaTroopaBeach.cpp @@ -9,6 +9,7 @@ #include "engine/actors/Finishline.h" #include "engine/objects/BombKart.h" #include "engine/objects/Crab.h" +#include "engine/objects/Seagull.h" #include "assets/koopa_troopa_beach_data.h" extern "C" { @@ -164,42 +165,40 @@ void KoopaTroopaBeach::BeginPlay() { spawn_palm_trees((struct ActorSpawnData*)LOAD_ASSET_RAW(d_course_koopa_troopa_beach_tree_spawn)); if (gGamestate != CREDITS_SEQUENCE) { - gWorldInstance.AddObject(new OCrab(FVector2D(-1809, 625), FVector2D(-1666, 594))); - gWorldInstance.AddObject(new OCrab(FVector2D(-1852, 757), FVector2D(-1620, 740))); - gWorldInstance.AddObject(new OCrab(FVector2D(-1478, 1842), FVector2D(-1453, 1833))); - gWorldInstance.AddObject(new OCrab(FVector2D(-1418, 1967), FVector2D(-1455, 1962))); - gWorldInstance.AddObject(new OCrab(FVector2D(-1472, 2112), FVector2D(-1417, 2100))); - gWorldInstance.AddObject(new OCrab(FVector2D(-1389, 2152), FVector2D(-1335, 2136))); - gWorldInstance.AddObject(new OCrab(FVector2D(218, 693), FVector2D(69, 696))); - gWorldInstance.AddObject(new OCrab(FVector2D(235, 528), FVector2D(24, 501))); - gWorldInstance.AddObject(new OCrab(FVector2D(268, 406), FVector2D(101, 394))); - gWorldInstance.AddObject(new OCrab(FVector2D(223, 318), FVector2D(86, 308))); + OCrab::Spawn(FVector2D(-1809, 625), FVector2D(-1666, 594)); + OCrab::Spawn(FVector2D(-1852, 757), FVector2D(-1620, 740)); + OCrab::Spawn(FVector2D(-1478, 1842), FVector2D(-1453, 1833)); + OCrab::Spawn(FVector2D(-1418, 1967), FVector2D(-1455, 1962)); + OCrab::Spawn(FVector2D(-1472, 2112), FVector2D(-1417, 2100)); + OCrab::Spawn(FVector2D(-1389, 2152), FVector2D(-1335, 2136)); + OCrab::Spawn(FVector2D(218, 693), FVector2D(69, 696)); + OCrab::Spawn(FVector2D(235, 528), FVector2D(24, 501)); + OCrab::Spawn(FVector2D(268, 406), FVector2D(101, 394)); + OCrab::Spawn(FVector2D(223, 318), FVector2D(86, 308)); } if (gGamestate == CREDITS_SEQUENCE) { for (size_t i = 0; i < NUM_SEAGULLS; i++) { - gWorldInstance.AddObject(new OSeagull(FVector(-360.0f, 60.0f, -1300.0f))); + OSeagull::Spawn(FVector(-360.0f, 60.0f, -1300.0f)); } } else { // Normal gameplay for (size_t i = 0; i < 4; i++) { - gWorldInstance.AddObject(new OSeagull(FVector(-985.0f, 15.0f, 1200.0f))); + OSeagull::Spawn(FVector(-985.0f, 15.0f, 1200.0f)); } for (size_t i = 0; i < 6; i++) { - gWorldInstance.AddObject(new OSeagull(FVector(328.0f, 20.0f, 2541.0f))); + OSeagull::Spawn(FVector(328.0f, 20.0f, 2541.0f)); } } if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][60], 60, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][120], 120, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][200], 200, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][280], 280, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][435], 435, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 60, 1, 0.8333333f); + OBombKart::Spawn(0, 120, 1, 0.8333333f); + OBombKart::Spawn(0, 200, 3, 0.8333333f); + OBombKart::Spawn(0, 280, 1, 0.8333333f); + OBombKart::Spawn(0, 435, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } } diff --git a/src/engine/courses/LuigiRaceway.cpp b/src/engine/courses/LuigiRaceway.cpp index 39b17a81d..cb2fd3e14 100644 --- a/src/engine/courses/LuigiRaceway.cpp +++ b/src/engine/courses/LuigiRaceway.cpp @@ -182,22 +182,20 @@ void LuigiRaceway::BeginPlay() { spawn_all_item_boxes((struct ActorSpawnData*) LOAD_ASSET_RAW(d_course_luigi_raceway_item_box_spawns)); if (gGamestate == CREDITS_SEQUENCE) { - gWorldInstance.AddObject(new OHotAirBalloon(FVector(-1250.0f, 0.0f, 1110.0f))); + OHotAirBalloon::Spawn(FVector(-1250.0f, 0.0f, 1110.0f)); } else { // Normal gameplay - gWorldInstance.AddObject(new OHotAirBalloon(FVector(-176.0, 0.0f, -2323.0f))); - gWorldInstance.AddObject(new OGrandPrixBalloons(FVector(-140, -44, -215))); + OHotAirBalloon::Spawn(FVector(-176.0, 0.0f, -2323.0f)); + OGrandPrixBalloons::Spawn(FVector(-140, -44, -215)); } if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][50], 50, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][200], 200, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][305], 305, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][440], 440, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][515], 515, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 50, 1, 0.8333333f); + OBombKart::Spawn(0, 200, 3, 0.8333333f); + OBombKart::Spawn(0, 305, 1, 0.8333333f); + OBombKart::Spawn(0, 440, 3, 0.8333333f); + OBombKart::Spawn(0, 515, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } } diff --git a/src/engine/courses/MarioRaceway.cpp b/src/engine/courses/MarioRaceway.cpp index 867c2734a..2afba1e9f 100644 --- a/src/engine/courses/MarioRaceway.cpp +++ b/src/engine/courses/MarioRaceway.cpp @@ -6,6 +6,7 @@ #include "MarioRaceway.h" #include "World.h" #include "engine/actors/Finishline.h" +#include "engine/actors/MarioSign.h" #include "engine/objects/Object.h" #include "engine/objects/BombKart.h" #include "engine/objects/GrandPrixBalloons.h" @@ -177,15 +178,6 @@ void MarioRaceway::Load() { void MarioRaceway::LoadTextures() { dma_textures(gTextureTrees1, 0x0000035BU, 0x00000800U); // 0x03009000 - D_802BA058 = dma_textures(gTexturePiranhaPlant1, 0x000003E8U, 0x00000800U); // 0x03009800 - dma_textures(gTexturePiranhaPlant2, 0x000003E8U, 0x00000800U); // 0x0300A000 - dma_textures(gTexturePiranhaPlant3, 0x000003E8U, 0x00000800U); // 0x0300A800 - dma_textures(gTexturePiranhaPlant4, 0x000003E8U, 0x00000800U); // 0x0300B000 - dma_textures(gTexturePiranhaPlant5, 0x000003E8U, 0x00000800U); // 0x0300B800 - dma_textures(gTexturePiranhaPlant6, 0x000003E8U, 0x00000800U); // 0x0300C000 - dma_textures(gTexturePiranhaPlant7, 0x000003E8U, 0x00000800U); // 0x0300C800 - dma_textures(gTexturePiranhaPlant8, 0x000003E8U, 0x00000800U); // 0x0300D000 - dma_textures(gTexturePiranhaPlant9, 0x000003E8U, 0x00000800U); // 0x0300D800 } void MarioRaceway::BeginPlay() { @@ -197,26 +189,22 @@ void MarioRaceway::BeginPlay() { spawn_foliage((struct ActorSpawnData*)LOAD_ASSET_RAW(d_course_mario_raceway_tree_spawns)); spawn_piranha_plants((struct ActorSpawnData*)LOAD_ASSET_RAW(d_course_mario_raceway_piranha_plant_spawns)); spawn_all_item_boxes((struct ActorSpawnData*)LOAD_ASSET_RAW(d_course_mario_raceway_item_box_spawns)); - vec3f_set(position, 150.0f, 40.0f, -1300.0f); - position[0] *= gCourseDirection; - add_actor_to_empty_slot(position, rotation, velocity, ACTOR_MARIO_SIGN); - vec3f_set(position, 2520.0f, 0.0f, 1240.0f); - position[0] *= gCourseDirection; - add_actor_to_empty_slot(position, rotation, velocity, ACTOR_MARIO_SIGN); + + AMarioSign::Spawn(FVector(150.0f, 40.0f, -1300.0f), IRotator(0, 0, 0), FVector(0, 0, 0), FVector(1.0f, 1.0f, 1.0f)); + AMarioSign::Spawn(FVector(2520.0f, 0.0f, 1240.0f), IRotator(0, 0, 0), FVector(0, 0, 0), FVector(1.0f, 1.0f, 1.0f)); if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][40], 40, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][100], 100, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][265], 265, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][285], 285, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][420], 420, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 40, 3, 0.8333333f); + OBombKart::Spawn(0, 100, 3, 0.8333333f); + OBombKart::Spawn(0, 265, 3, 0.8333333f); + OBombKart::Spawn(0, 285, 1, 0.8333333f); + OBombKart::Spawn(0, 420, 1, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } if (gGamestate != CREDITS_SEQUENCE) { - gWorldInstance.AddObject(new OGrandPrixBalloons(FVector(0, 5, -240))); + OGrandPrixBalloons::Spawn(FVector(0, 5, -240)); } } diff --git a/src/engine/courses/MooMooFarm.cpp b/src/engine/courses/MooMooFarm.cpp index f3c4dc1bc..5166442d4 100644 --- a/src/engine/courses/MooMooFarm.cpp +++ b/src/engine/courses/MooMooFarm.cpp @@ -323,15 +323,13 @@ void MooMooFarm::BeginPlay() { } if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][50], 50, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][140], 140, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][225], 225, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][316], 316, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][434], 434, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 50, 3, 0.8333333f); + OBombKart::Spawn(0, 140, 3, 0.8333333f); + OBombKart::Spawn(0, 225, 3, 0.8333333f); + OBombKart::Spawn(0, 316, 3, 0.8333333f); + OBombKart::Spawn(0, 434, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } } diff --git a/src/engine/courses/PodiumCeremony.cpp b/src/engine/courses/PodiumCeremony.cpp index 9e75dec23..c41d96904 100644 --- a/src/engine/courses/PodiumCeremony.cpp +++ b/src/engine/courses/PodiumCeremony.cpp @@ -138,10 +138,10 @@ PodiumCeremony::PodiumCeremony() { Props.PathTable[2] = (TrackPathPoint*)LOAD_ASSET_RAW(podium_ceremony_path_3); Props.PathTable[3] = (TrackPathPoint*)LOAD_ASSET_RAW(podium_ceremony_path_4); - Props.PathTable2[0] = NULL; - Props.PathTable2[1] = NULL; - Props.PathTable2[2] = NULL; - Props.PathTable2[3] = NULL; + Props.PathTable2[0] = (TrackPathPoint*)LOAD_ASSET_RAW(podium_ceremony_path); + Props.PathTable2[1] = (TrackPathPoint*)LOAD_ASSET_RAW(podium_ceremony_path_2); + Props.PathTable2[2] = (TrackPathPoint*)LOAD_ASSET_RAW(podium_ceremony_path_3); + Props.PathTable2[3] = (TrackPathPoint*)LOAD_ASSET_RAW(podium_ceremony_path_4); Props.CloudTexture = (u8*) gTextureExhaust4; Props.Clouds = NULL; // no clouds @@ -175,9 +175,9 @@ void PodiumCeremony::BeginPlay() { spawn_all_item_boxes((struct ActorSpawnData*)LOAD_ASSET_RAW(d_course_royal_raceway_item_box_spawns)); spawn_piranha_plants((struct ActorSpawnData*)LOAD_ASSET_RAW(d_course_royal_raceway_piranha_plant_spawn)); - gWorldInstance.AddObject(new OCheepCheep(FVector((f32)-3202, (f32)19, (f32)-478), OCheepCheep::CheepType::PODIUM_CEREMONY, IPathSpan(0, 0))); - gWorldInstance.AddObject(new OPodium(FVector((f32)-3202, (f32)19, (f32)-478))); - + OCheepCheep::Spawn(FVector((f32)-3202, (f32)19, (f32)-478), OCheepCheep::Behaviour::PODIUM_CEREMONY, IPathSpan(0, 0)); + OPodium::Spawn(FVector((f32)-3202, (f32)19, (f32)-478)); + FVector pos = {0, 90.0f, 0}; OTrophy::TrophyType type = OTrophy::TrophyType::BRONZE; @@ -197,19 +197,18 @@ void PodiumCeremony::BeginPlay() { break; } - OTrophy* trophy = reinterpret_cast<OTrophy*>(gWorldInstance.AddObject(new OTrophy(pos, type, OTrophy::Behaviour::PODIUM_CEREMONY))); + OTrophy::Spawn(pos, type, OTrophy::Behaviour::PODIUM_CEREMONY); - FVector kart = { 0, 0, 0 }; - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[3][3], 3, OBombKart::States::PODIUM_CEREMONY, 1.25f)); - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[3][40], 40, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[3][60], 60, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[3][80], 80, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[3][100], 100, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[3][120], 120, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[3][140], 140, 0, 1.0f)); + OBombKart::Spawn(3, 3, OBombKart::States::PODIUM_CEREMONY, 1.25f); + OBombKart::Spawn(3, 40, 0, 1.0f); + OBombKart::Spawn(3, 60, 0, 1.0f); + OBombKart::Spawn(3, 80, 0, 1.0f); + OBombKart::Spawn(3, 100, 0, 1.0f); + OBombKart::Spawn(3, 120, 0, 1.0f); + OBombKart::Spawn(3, 140, 0, 1.0f); if (gGamestate != CREDITS_SEQUENCE) { - gWorldInstance.AddObject(new OGrandPrixBalloons(FVector(-64, 5, -330))); + OGrandPrixBalloons::Spawn(FVector(-64, 5, -330)); } } diff --git a/src/engine/courses/RainbowRoad.cpp b/src/engine/courses/RainbowRoad.cpp index e5ec039bd..f2f99b488 100644 --- a/src/engine/courses/RainbowRoad.cpp +++ b/src/engine/courses/RainbowRoad.cpp @@ -156,15 +156,13 @@ void RainbowRoad::BeginPlay() { } if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][50], 50, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][100], 100, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][150], 150, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][200], 200, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][250], 250, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 50, 3, 0.8333333f); + OBombKart::Spawn(0, 100, 1, 0.8333333f); + OBombKart::Spawn(0, 150, 3, 0.8333333f); + OBombKart::Spawn(0, 200, 1, 0.8333333f); + OBombKart::Spawn(0, 250, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } } diff --git a/src/engine/courses/RoyalRaceway.cpp b/src/engine/courses/RoyalRaceway.cpp index 1473cbf4f..1fc6a10b9 100644 --- a/src/engine/courses/RoyalRaceway.cpp +++ b/src/engine/courses/RoyalRaceway.cpp @@ -173,15 +173,6 @@ void RoyalRaceway::Load() { void RoyalRaceway::LoadTextures() { dma_textures(gTextureTrees3, 0x000003E8U, 0x00000800U); // 0x03009000 dma_textures(gTextureTrees7, 0x000003E8U, 0x00000800U); // 0x03009800 - D_802BA058 = dma_textures(gTexturePiranhaPlant1, 0x000003E8U, 0x00000800U); // 0x0300A000 - dma_textures(gTexturePiranhaPlant2, 0x000003E8U, 0x00000800U); // 0x0300A800 - dma_textures(gTexturePiranhaPlant3, 0x000003E8U, 0x00000800U); // 0x0300B000 - dma_textures(gTexturePiranhaPlant4, 0x000003E8U, 0x00000800U); // 0x0300B800 - dma_textures(gTexturePiranhaPlant5, 0x000003E8U, 0x00000800U); // 0x0300C000 - dma_textures(gTexturePiranhaPlant6, 0x000003E8U, 0x00000800U); // 0x0300C800 - dma_textures(gTexturePiranhaPlant7, 0x000003E8U, 0x00000800U); // 0x0300D000 - dma_textures(gTexturePiranhaPlant8, 0x000003E8U, 0x00000800U); // 0x0300D800 - dma_textures(gTexturePiranhaPlant9, 0x000003E8U, 0x00000800U); // 0x0300E000 } void RoyalRaceway::BeginPlay() { @@ -190,18 +181,16 @@ void RoyalRaceway::BeginPlay() { spawn_piranha_plants((struct ActorSpawnData*)LOAD_ASSET_RAW(d_course_royal_raceway_piranha_plant_spawn)); if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][50], 50, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][100], 100, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][296], 296, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][400], 400, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][746], 746, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 50, 3, 0.8333333f); + OBombKart::Spawn(0, 100, 3, 0.8333333f); + OBombKart::Spawn(0, 296, 3, 0.8333333f); + OBombKart::Spawn(0, 400, 1, 0.8333333f); + OBombKart::Spawn(0, 746, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } if (gGamestate != CREDITS_SEQUENCE) { - gWorldInstance.AddObject(new OGrandPrixBalloons(FVector(-64, 5, -330))); + OGrandPrixBalloons::Spawn(FVector(-64, 5, -330)); } } diff --git a/src/engine/courses/SherbetLand.cpp b/src/engine/courses/SherbetLand.cpp index adcec5186..4d72e8095 100644 --- a/src/engine/courses/SherbetLand.cpp +++ b/src/engine/courses/SherbetLand.cpp @@ -152,74 +152,44 @@ f32 SherbetLand::GetWaterLevel(FVector pos, Collision* collision) { void SherbetLand::BeginPlay() { spawn_all_item_boxes((struct ActorSpawnData*)LOAD_ASSET_RAW(d_course_sherbet_land_item_box_spawns)); - // Multiplayer does not spawn the big penguin + // Multiplayer does not spawn the big penguin... It does now! // if (gPlayerCountSelection1 == 1) { - FVector pos = {-383.0f, 2.0f, -690.0f}; - gWorldInstance.AddObject(new OPenguin(pos, 0, OPenguin::PenguinType::EMPEROR, OPenguin::Behaviour::STRUT)); + OPenguin::Spawn(FVector(-383.0f, 2.0f, -690.0f), 0, 0, 0.0f, OPenguin::PenguinType::EMPEROR, OPenguin::Behaviour::STRUT); // } - FVector pos2 = { -2960.0f, -80.0f, 1521.0f }; - auto penguin = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos2, 0x150, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE))); - auto penguin2 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos2, 0x150, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE))); - penguin->Diameter = penguin2->Diameter = 100.0f; + OPenguin::Spawn(FVector(-2960.0f, -80.0f, 1521.0f), 0x150, 0, 100.0f, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE); + OPenguin::Spawn(FVector(-2960.0f, -80.0f, 1521.0f), 0x150, 0, 100.0f, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE); - FVector pos3 = { -2490.0f, -80.0f, 1612.0f }; - auto penguin3 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos3, 0x100, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE))); - auto penguin4 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos3, 0x100, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE))); - penguin3->Diameter = penguin4->Diameter = 80.0f; + OPenguin::Spawn(FVector(-2490.0f, -80.0f, 1612.0f), 0x100, 0, 80.0f, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE); + OPenguin::Spawn(FVector(-2490.0f, -80.0f, 1612.0f), 0x100, 0, 80.0f, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE); - FVector pos4 = { -2098.0f, -80.0f, 1624.0f }; - auto penguin5 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos4, 0xFF00, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE))); - auto penguin6 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos4, 0xFF00, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE))); - penguin5->Diameter = penguin6->Diameter = 80.0f; - - - FVector pos5 = { -2080.0f, -80.0f, 1171.0f }; - auto penguin7 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos5, 0x150, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE))); - auto penguin8 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos5, 0x150, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE))); - penguin7->Diameter = penguin8->Diameter = 80.0f; + OPenguin::Spawn(FVector(-2098.0f, -80.0f, 1624.0f), 0xFF00, 0, 80.0f, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE); + OPenguin::Spawn(FVector(-2098.0f, -80.0f, 1624.0f), 0xFF00, 0, 80.0f, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE); + OPenguin::Spawn(FVector(-2080.0f, -80.0f, 1171.0f), 0x150, 0, 80.0f, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE); + OPenguin::Spawn(FVector(-2080.0f, -80.0f, 1171.0f), 0x150, 0, 80.0f, OPenguin::PenguinType::ADULT, OPenguin::Behaviour::CIRCLE); if (gGamestate == CREDITS_SEQUENCE) { - FVector pos6 = { 380.0, 0.0f, -535.0f }; - auto penguin9 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos6, 0x9000, OPenguin::PenguinType::CREDITS, OPenguin::Behaviour::SLIDE3))); - penguin9->MirrorModeAngleOffset = -0x4000; + OPenguin::Spawn(FVector(380.0f, 0.0f, -535.0f), 0x9000, -0x4000, 0.0f, OPenguin::PenguinType::CREDITS, OPenguin::Behaviour::SLIDE3); } else { - FVector pos6 = { 146.0f, 0.0f, -380.0f }; - auto penguin9 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos6, 0x9000, OPenguin::PenguinType::CHICK, OPenguin::Behaviour::SLIDE3))); - penguin9->MirrorModeAngleOffset = -0x4000; + OPenguin::Spawn(FVector(146.0f, 0.0f, -380.0f), 0x9000, -0x4000, 0.0f, OPenguin::PenguinType::CHICK, OPenguin::Behaviour::SLIDE3); } - FVector pos7 = { 380.0f, 0.0f, -766.0f }; - auto penguin10 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos7, 0x5000, OPenguin::PenguinType::CHICK, OPenguin::Behaviour::SLIDE4))); - penguin10->MirrorModeAngleOffset = 0x8000; - - FVector pos8 = { -2300.0f, 0.0f, -210.0f }; - auto penguin11 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos8, 0xC000, OPenguin::PenguinType::CHICK, OPenguin::Behaviour::SLIDE6))); - penguin11->MirrorModeAngleOffset = 0x8000; - - FVector pos9 = { -2500.0f, 0.0f, -250.0f }; - auto penguin12 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos9, 0x4000, OPenguin::PenguinType::CHICK, OPenguin::Behaviour::SLIDE6))); - penguin12->MirrorModeAngleOffset = 0x8000; - - FVector pos10 = { -535.0f, 0.0f, 875.0f }; - auto penguin13 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos10, 0x8000, OPenguin::PenguinType::CHICK, OPenguin::Behaviour::SLIDE6))); - penguin13->MirrorModeAngleOffset = -0x4000; - - FVector pos11 = { -250.0f, 0.0f, 953.0f }; - auto penguin14 = reinterpret_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(pos11, 0x9000, OPenguin::PenguinType::CHICK, OPenguin::Behaviour::SLIDE6))); - penguin14->MirrorModeAngleOffset = -0x4000; + OPenguin::Spawn(FVector(380.0f, 0.0f, -766.0f), 0x5000, 0x8000, 0.0f, OPenguin::PenguinType::CHICK, OPenguin::Behaviour::SLIDE4); + OPenguin::Spawn(FVector(-2300.0f, 0.0f, -210.0f), 0xC000, 0x8000, 0.0f, OPenguin::PenguinType::CHICK, OPenguin::Behaviour::SLIDE6); + OPenguin::Spawn(FVector(-2500.0f, 0.0f, -250.0f), 0x4000, 0x8000, 0.0f, OPenguin::PenguinType::CHICK, OPenguin::Behaviour::SLIDE6); + OPenguin::Spawn(FVector(-535.0f, 0.0f, 875.0f), 0x8000, -0x4000, 0.0f, OPenguin::PenguinType::CHICK, OPenguin::Behaviour::SLIDE6); + OPenguin::Spawn(FVector(-250.0f, 0.0f, 953.0f), 0x9000, -0x4000, 0.0f, OPenguin::PenguinType::CHICK, OPenguin::Behaviour::SLIDE6); if (gGamestate != CREDITS_SEQUENCE) { if (gModeSelection == VERSUS) { - FVector kart = { 0, 0, 0 }; - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[0][50], 50, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[0][100], 100, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[0][150], 150, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[0][200], 200, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[0][250], 250, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(kart, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 50, 3, 0.8333333f); + OBombKart::Spawn(0, 100, 1, 0.8333333f); + OBombKart::Spawn(0, 150, 3, 0.8333333f); + OBombKart::Spawn(0, 200, 1, 0.8333333f); + OBombKart::Spawn(0, 250, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } } } diff --git a/src/engine/courses/Skyscraper.cpp b/src/engine/courses/Skyscraper.cpp index 184110a2e..53232257d 100644 --- a/src/engine/courses/Skyscraper.cpp +++ b/src/engine/courses/Skyscraper.cpp @@ -153,15 +153,13 @@ void Skyscraper::BeginPlay() { spawn_all_item_boxes((ActorSpawnData*)LOAD_ASSET_RAW(d_course_skyscraper_item_box_spawns)); if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][20], 20, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][40], 40, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][60], 60, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][80], 80, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][100], 100, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][120], 120, 0, 1.0f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][140], 140, 0, 1.0f)); + OBombKart::Spawn(0, 20, 0, 1.0f); + OBombKart::Spawn(0, 40, 0, 1.0f); + OBombKart::Spawn(0, 60, 0, 1.0f); + OBombKart::Spawn(0, 80, 0, 1.0f); + OBombKart::Spawn(0, 100, 0, 1.0f); + OBombKart::Spawn(0, 120, 0, 1.0f); + OBombKart::Spawn(0, 140, 0, 1.0f); } } diff --git a/src/engine/courses/TestCourse.cpp b/src/engine/courses/TestCourse.cpp index cf9f25179..f4a6710ac 100644 --- a/src/engine/courses/TestCourse.cpp +++ b/src/engine/courses/TestCourse.cpp @@ -29,6 +29,7 @@ #include "engine/objects/Crab.h" #include "engine/objects/Boos.h" #include "engine/objects/GrandPrixBalloons.h" +#include "engine/objects/Thwomp.h" extern "C" { #include "main.h" @@ -147,75 +148,8 @@ void TestCourse::Load() { void TestCourse::LoadTextures() { dma_textures(gTextureTrees1, 0x0000035BU, 0x00000800U); // 0x03009000 - D_802BA058 = dma_textures(gTexturePiranhaPlant1, 0x000003E8U, 0x00000800U); // 0x03009800 - dma_textures(gTexturePiranhaPlant2, 0x000003E8U, 0x00000800U); // 0x0300A000 - dma_textures(gTexturePiranhaPlant3, 0x000003E8U, 0x00000800U); // 0x0300A800 - dma_textures(gTexturePiranhaPlant4, 0x000003E8U, 0x00000800U); // 0x0300B000 - dma_textures(gTexturePiranhaPlant5, 0x000003E8U, 0x00000800U); // 0x0300B800 - dma_textures(gTexturePiranhaPlant6, 0x000003E8U, 0x00000800U); // 0x0300C000 - dma_textures(gTexturePiranhaPlant7, 0x000003E8U, 0x00000800U); // 0x0300C800 - dma_textures(gTexturePiranhaPlant8, 0x000003E8U, 0x00000800U); // 0x0300D000 - dma_textures(gTexturePiranhaPlant9, 0x000003E8U, 0x00000800U); // 0x0300D800 } -Path2D test_course_path2D[] = { - { 0, 0}, - { 0, -100}, - { 0, -200}, - { 0, -300}, - { 0, -400}, - { 0, -500}, - { 0, -600}, - { 0, -700}, - { 0, -800}, - { 0, -900}, - { 0, -1000}, - { 0, -1096}, // Main point 1 - { 100, -1090}, - { 200, -1085}, - { 300, -1080}, - { 400, -1075}, - { 500, -1072}, // Curve begins to smooth here - { 600, -1068}, - { 700, -1065}, - { 800, -1063}, - { 900, -1061}, - { 984, -1060}, // Main point 2 - { 990, -900}, - { 995, -800}, - { 997, -700}, - { 998, -600}, - { 999, -500}, - { 999, -400}, - { 999, -300}, - { 999, -200}, - { 999, -100}, - { 999, 0}, - { 999, 100}, - { 999, 200}, - { 999, 300}, - { 999, 400}, - { 999, 500}, - { 999, 600}, - { 999, 700}, - { 999, 800}, - { 999, 900}, - { 999, 940}, // Main point 3 - { 900, 945}, - { 800, 945}, - { 700, 947}, - { 600, 948}, - { 500, 949}, - { 400, 949}, - { 300, 949}, - { 200, 950}, - { 100, 950}, - { 0, 950}, // Main point 4 - - // End of path - { -32768, -32768 } // Terminator -}; - void TestCourse::BeginPlay() { struct ActorSpawnData itemboxes[] = { { 200, 1500, 200 , 0}, @@ -270,7 +204,7 @@ void TestCourse::BeginPlay() { // gWorldInstance.AddActor(new OSeagull(2, pos)); // gWorldInstance.AddActor(new OSeagull(3, pos)); // gWorldInstance.AddObject(new OCheepCheep(FVector(0, 40, 0), OCheepCheep::CheepType::RACE, IPathSpan(0, 10))); - gWorldInstance.AddObject(new OTrophy(FVector(0,0,0), OTrophy::TrophyType::GOLD, OTrophy::Behaviour::GO_FISH)); + OTrophy::Spawn(FVector(0,0,0), OTrophy::TrophyType::GOLD, OTrophy::Behaviour::GO_FISH); //gWorldInstance.AddObject(new OSnowman(FVector(0, 0, 0))); //gWorldInstance.AddObject(new OTrashBin(FVector(0.0f, 0.0f, 0.0f), IRotator(0, 90, 0), 1.0f, OTrashBin::Behaviour::MUNCHING)); @@ -282,22 +216,17 @@ void TestCourse::BeginPlay() { // gWorldInstance.AddActor(new ABowserStatue(FVector(-200, 0, 0), ABowserStatue::Behaviour::CRUSH)); // gWorldInstance.AddObject(new OBoos(10, IPathSpan(0, 5), IPathSpan(18, 23), IPathSpan(25, 50))); - - gVehicle2DPathPoint = test_course_path2D; - gVehicle2DPathLength = 53; - D_80162EB0 = spawn_actor_on_surface(test_course_path2D[0].x, 2000.0f, test_course_path2D[0].z); + //OThwomp::Spawn(0, 0, 0, 1.0f, 0, 1, 7); //gWorldInstance.AddTrain(ATrain::TenderStatus::HAS_TENDER, 5, 2.5f, 0); //gWorldInstance.AddTrain(ATrain::TenderStatus::HAS_TENDER, 5, 2.5f, 8); - FVector pos2 = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos2, &gTrackPaths[0][25], 25, 4, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos2, &gTrackPaths[0][45], 45, 4, 0.8333333f)); + OBombKart::Spawn(0, 25, 4, 0.8333333f); + OBombKart::Spawn(0, 45, 4, 0.8333333f); // gWorldInstance.AddActor(new AShip(FVector(0, 0, 0), AShip::Skin::SHIP3)); -// gWorldInstance.AddObject(new OGrandPrixBalloons(FVector(0, 0, 0))); +// OGrandPrixBalloons::Spawn(FVector(0, 0, 0)); } void TestCourse::WhatDoesThisDo(Player* player, int8_t playerId) { diff --git a/src/engine/courses/ToadsTurnpike.cpp b/src/engine/courses/ToadsTurnpike.cpp index c9f8b9918..69fcc7724 100644 --- a/src/engine/courses/ToadsTurnpike.cpp +++ b/src/engine/courses/ToadsTurnpike.cpp @@ -170,7 +170,7 @@ void ToadsTurnpike::BeginPlay() { spawn_all_item_boxes((struct ActorSpawnData*)LOAD_ASSET_RAW(d_course_toads_turnpike_item_box_spawns)); if (gGamestate != CREDITS_SEQUENCE) { - uint32_t waypoint; + uint32_t pathPoint; f32 a = ((gCCSelection * 90.0) / 216.0f) + 4.583333333333333; f32 b = ((gCCSelection * 90.0) / 216.0f) + 2.9166666666666665; a /= 2; // Normally vehicle logic is only ran every 2 frames. This slows the vehicles down to match. @@ -190,34 +190,33 @@ void ToadsTurnpike::BeginPlay() { } for (size_t i = 0; i < _numTrucks; i++) { - waypoint = CalculateWaypointDistribution(i, _numTrucks, gPathCountByPathIndex[0], 0); - gWorldInstance.AddActor(new ATruck(a, b, &gTrackPaths[0][0], waypoint)); + pathPoint = CalculateWaypointDistribution(i, _numTrucks, gPathCountByPathIndex[0], 0); + ATruck::Spawn(a, b, 0, pathPoint, ATruck::SpawnMode::POINT); } for (size_t i = 0; i < _numBuses; i++) { - waypoint = CalculateWaypointDistribution(i, _numBuses, gPathCountByPathIndex[0], 75); - gWorldInstance.AddActor(new ABus(a, b, &gTrackPaths[0][0], waypoint)); + pathPoint = CalculateWaypointDistribution(i, _numBuses, gPathCountByPathIndex[0], 75); + ABus::Spawn(a, b, 0, pathPoint, ABus::SpawnMode::POINT); } for (size_t i = 0; i < _numTankerTrucks; i++) { - waypoint = CalculateWaypointDistribution(i, _numTankerTrucks, gPathCountByPathIndex[0], 50); - gWorldInstance.AddActor(new ATankerTruck(a, b, &gTrackPaths[0][0], waypoint)); + pathPoint = CalculateWaypointDistribution(i, _numTankerTrucks, gPathCountByPathIndex[0], 50); + ATankerTruck::Spawn(a, b, 0, pathPoint, ATankerTruck::SpawnMode::POINT); } for (size_t i = 0; i < _numCars; i++) { - waypoint = CalculateWaypointDistribution(i, _numCars, gPathCountByPathIndex[0], 25); - gWorldInstance.AddActor(new ACar(a, b, &gTrackPaths[0][0], waypoint)); + pathPoint = CalculateWaypointDistribution(i, _numCars, gPathCountByPathIndex[0], 25); + ACar::Spawn(a, b, 0, pathPoint, ACar::SpawnMode::POINT); } if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][50], 50, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][100], 100, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][150], 150, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][200], 200, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][250], 250, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 50, 3, 0.8333333f); + OBombKart::Spawn(0, 100, 1, 0.8333333f); + OBombKart::Spawn(0, 150, 3, 0.8333333f); + OBombKart::Spawn(0, 200, 1, 0.8333333f); + OBombKart::Spawn(0, 250, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } } } diff --git a/src/engine/courses/WarioStadium.cpp b/src/engine/courses/WarioStadium.cpp index 8046e6325..6904ad45b 100644 --- a/src/engine/courses/WarioStadium.cpp +++ b/src/engine/courses/WarioStadium.cpp @@ -176,28 +176,18 @@ void WarioStadium::LoadTextures() { void WarioStadium::BeginPlay() { spawn_all_item_boxes((struct ActorSpawnData*) LOAD_ASSET_RAW(d_course_wario_stadium_item_box_spawns)); - FVector pos = { -131.0f, 83.0f, 286.0f }; - pos.x *= gCourseDirection; - gWorldInstance.AddActor(new AWarioSign(pos)); - - FVector pos2 = { -2353.0f, 72.0f, -1608.0f }; - pos2.x *= gCourseDirection; - gWorldInstance.AddActor(new AWarioSign(pos2)); - - FVector pos3 = { -2622.0f, 79.0f, 739.0f }; - pos3.x *= gCourseDirection; - gWorldInstance.AddActor(new AWarioSign(pos3)); + AWarioSign::Spawn(FVector(-131.0f, 83.0f, 286.0f), IRotator(0, 0, 0), FVector(0, 0, 0), FVector(1.0f, 1.0f, 1.0f)); + AWarioSign::Spawn(FVector(-2353.0f, 72.0f, -1608.0f), IRotator(0, 0, 0), FVector(0, 0, 0), FVector(1.0f, 1.0f, 1.0f)); + AWarioSign::Spawn(FVector(-2622.0f, 79.0f, 739.0f), IRotator(0, 0, 0), FVector(0, 0, 0), FVector(1.0f, 1.0f, 1.0f)); if (gModeSelection == VERSUS) { - FVector pos = { 0, 0, 0 }; - - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][50], 50, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][100], 100, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][150], 150, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][200], 200, 1, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][250], 250, 3, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); - gWorldInstance.AddObject(new OBombKart(pos, &gTrackPaths[0][0], 0, 0, 0.8333333f)); + OBombKart::Spawn(0, 50, 3, 0.8333333f); + OBombKart::Spawn(0, 100, 1, 0.8333333f); + OBombKart::Spawn(0, 150, 3, 0.8333333f); + OBombKart::Spawn(0, 200, 1, 0.8333333f); + OBombKart::Spawn(0, 250, 3, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); + OBombKart::Spawn(0, 0, 0, 0.8333333f); } } diff --git a/src/engine/courses/YoshiValley.cpp b/src/engine/courses/YoshiValley.cpp index 71d782440..4576b53ca 100644 --- a/src/engine/courses/YoshiValley.cpp +++ b/src/engine/courses/YoshiValley.cpp @@ -160,26 +160,26 @@ void YoshiValley::BeginPlay() { if (gGamestate != CREDITS_SEQUENCE) { //! @bug Skip spawning in credits due to animation crash for now - gWorldInstance.AddObject(new OFlagpole(FVector(-902, 70, -1406), 0x3800)); - gWorldInstance.AddObject(new OFlagpole(FVector(-948, 70, -1533), 0x3800)); - gWorldInstance.AddObject(new OFlagpole(FVector(-2170, 0, 723), 0x400)); - gWorldInstance.AddObject(new OFlagpole(FVector(-2193, 0, 761), 0x400)); - - gWorldInstance.AddObject(new OHedgehog(FVector(-1683, -80, -88), FVector2D(-1650, -114), 9)); - gWorldInstance.AddObject(new OHedgehog(FVector(-1636, -93, -147), FVector2D(-1661, -151), 9)); - gWorldInstance.AddObject(new OHedgehog(FVector(-1628, -86, -108), FVector2D(-1666, -58), 9)); - gWorldInstance.AddObject(new OHedgehog(FVector(-1676, -69, -30), FVector2D(-1651, -26), 9)); - gWorldInstance.AddObject(new OHedgehog(FVector(-1227, -27, -989), FVector2D(-1194, -999), 26)); - gWorldInstance.AddObject(new OHedgehog(FVector(-1261, -41, -880), FVector2D(-1213, -864), 26)); - gWorldInstance.AddObject(new OHedgehog(FVector(-1342, -60, -830), FVector2D(-1249, -927), 26)); - gWorldInstance.AddObject(new OHedgehog(FVector(-1429, -78, -849), FVector2D(-1347, -866), 26)); - gWorldInstance.AddObject(new OHedgehog(FVector(-1492, -94, -774), FVector2D(-1427, -891), 26)); - gWorldInstance.AddObject(new OHedgehog(FVector(-1453, -87, -784), FVector2D(-1509, -809), 26)); - gWorldInstance.AddObject(new OHedgehog(FVector(-1488, 89, -852), FVector2D(-1464, -822), 26)); - gWorldInstance.AddObject(new OHedgehog(FVector(-1301, 47, -904), FVector2D(-1537, -854), 26)); - gWorldInstance.AddObject(new OHedgehog(FVector(-2587, 56, -259), FVector2D(-2624, -241), 28)); - gWorldInstance.AddObject(new OHedgehog(FVector(-2493, 94, -454), FVector2D(-2505, -397), 28)); - gWorldInstance.AddObject(new OHedgehog(FVector(-2477, 3, -57), FVector2D(-2539, -66), 28)); + OFlagpole::Spawn(FVector(-902, 70, -1406), 0x3800); + OFlagpole::Spawn(FVector(-948, 70, -1533), 0x3800); + OFlagpole::Spawn(FVector(-2170, 0, 723), 0x400); + OFlagpole::Spawn(FVector(-2193, 0, 761), 0x400); + + OHedgehog::Spawn(FVector(-1683, -80, -88), FVector2D(-1650, -114), 9); + OHedgehog::Spawn(FVector(-1636, -93, -147), FVector2D(-1661, -151), 9); + OHedgehog::Spawn(FVector(-1628, -86, -108), FVector2D(-1666, -58), 9); + OHedgehog::Spawn(FVector(-1676, -69, -30), FVector2D(-1651, -26), 9); + OHedgehog::Spawn(FVector(-1227, -27, -989), FVector2D(-1194, -999), 26); + OHedgehog::Spawn(FVector(-1261, -41, -880), FVector2D(-1213, -864), 26); + OHedgehog::Spawn(FVector(-1342, -60, -830), FVector2D(-1249, -927), 26); + OHedgehog::Spawn(FVector(-1429, -78, -849), FVector2D(-1347, -866), 26); + OHedgehog::Spawn(FVector(-1492, -94, -774), FVector2D(-1427, -891), 26); + OHedgehog::Spawn(FVector(-1453, -87, -784), FVector2D(-1509, -809), 26); + OHedgehog::Spawn(FVector(-1488, 89, -852), FVector2D(-1464, -822), 26); + OHedgehog::Spawn(FVector(-1301, 47, -904), FVector2D(-1537, -854), 26); + OHedgehog::Spawn(FVector(-2587, 56, -259), FVector2D(-2624, -241), 28); + OHedgehog::Spawn(FVector(-2493, 94, -454), FVector2D(-2505, -397), 28); + OHedgehog::Spawn(FVector(-2477, 3, -57), FVector2D(-2539, -66), 28); } if (gModeSelection == VERSUS) { @@ -187,20 +187,13 @@ void YoshiValley::BeginPlay() { // the original data has values here. // Note that the Y height is calculated automatically to place the kart on the surface - FVector pos = { -1533, 0, -682 }; - gWorldInstance.AddObject(new OBombKart(pos, NULL, 0, 0, 0.8333333f)); - FVector pos2 = { -1565, 0, -619 }; - gWorldInstance.AddObject(new OBombKart(pos2, NULL, 10, 0, 0.8333333f)); - FVector pos3 = { -1529, 0, -579 }; - gWorldInstance.AddObject(new OBombKart(pos3, NULL, 20, 0, 0.8333333f)); - FVector pos4 = { -1588, 0, -534 }; - gWorldInstance.AddObject(new OBombKart(pos4, NULL, 30, 0, 0.8333333f)); - FVector pos5 = { -1598, 0, -207 }; - gWorldInstance.AddObject(new OBombKart(pos5, NULL, 40, 0, 0.8333333f)); - FVector pos6 = { -1646, 0, -147 }; - gWorldInstance.AddObject(new OBombKart(pos6, NULL, 50, 0, 0.8333333f)); - FVector pos7 = { -2532, 0, -445 }; - gWorldInstance.AddObject(new OBombKart(pos7, NULL, 60, 0, 0.8333333f)); + OBombKart::Spawn(FVector(-1533, 0, -682), 0, 0.8333333f); + OBombKart::Spawn(FVector(-1565, 0, -619), 0, 0.8333333f); + OBombKart::Spawn(FVector(-1529, 0, -579), 0, 0.8333333f); + OBombKart::Spawn(FVector(-1588, 0, -534), 0, 0.8333333f); + OBombKart::Spawn(FVector(-1598, 0, -207), 0, 0.8333333f); + OBombKart::Spawn(FVector(-1646, 0, -147), 0, 0.8333333f); + OBombKart::Spawn(FVector(-2532, 0, -445), 0, 0.8333333f); } } diff --git a/src/engine/editor/Collision.cpp b/src/engine/editor/Collision.cpp index bf62d6273..261aa40d7 100644 --- a/src/engine/editor/Collision.cpp +++ b/src/engine/editor/Collision.cpp @@ -4,13 +4,17 @@ #include <libultra/gbi.h> #include "Matrix.h" +#include "engine/Actor.h" +#include "engine/objects/Object.h" +#include "engine/editor/GameObject.h" + extern "C" { #include "main.h" #include "other_textures.h" } namespace Editor { - void GenerateCollisionMesh(GameObject* object, Gfx* model, float scale) { + void GenerateCollisionMesh(std::variant<AActor*, OObject*, GameObject*> object, Gfx* model, float scale) { int8_t opcode; uintptr_t lo; uintptr_t hi; @@ -19,6 +23,12 @@ namespace Editor { Vtx* vtx = NULL; size_t i = 0; bool run = true; + + //! @attention Objects will not be clickable if editor is enabled mid-race. + if (CVarGetInteger("gEditorEnabled", false) == true) { + return; + } + while (run) { i++; lo = ptr->words.w0; @@ -64,8 +74,11 @@ namespace Editor { FVector p1 = FVector(vtx[v1].v.ob[0], vtx[v1].v.ob[1], vtx[v1].v.ob[2]); FVector p2 = FVector(vtx[v2].v.ob[0], vtx[v2].v.ob[1], vtx[v2].v.ob[2]); FVector p3 = FVector(vtx[v3].v.ob[0], vtx[v3].v.ob[1], vtx[v3].v.ob[2]); - - object->Triangles.push_back({p1, p2, p3}); + std::visit([p1, p2, p3](auto* obj) { + if (obj) { + obj->Triangles.push_back({p1, p2, p3}); + } + }, object); break; } case G_TRI1_OTR: { @@ -82,9 +95,11 @@ namespace Editor { FVector p1 = FVector(vtx[v1].v.ob[0], vtx[v1].v.ob[1], vtx[v1].v.ob[2]); FVector p2 = FVector(vtx[v2].v.ob[0], vtx[v2].v.ob[1], vtx[v2].v.ob[2]); FVector p3 = FVector(vtx[v3].v.ob[0], vtx[v3].v.ob[1], vtx[v3].v.ob[2]); - - object->Triangles.push_back({p1, p2, p3}); - + std::visit([p1, p2, p3](auto* obj) { + if (obj) { + obj->Triangles.push_back({p1, p2, p3}); + } + }, object); break; } case G_TRI2: { @@ -109,8 +124,12 @@ namespace Editor { FVector p5 = FVector(vtx[v5].v.ob[0], vtx[v5].v.ob[1], vtx[v5].v.ob[2]); FVector p6 = FVector(vtx[v6].v.ob[0], vtx[v6].v.ob[1], vtx[v6].v.ob[2]); - object->Triangles.push_back({p1, p2, p3}); - object->Triangles.push_back({p4, p5, p6}); + std::visit([p1, p2, p3, p4, p5, p6](auto* obj) { + if (obj) { + obj->Triangles.push_back({p1, p2, p3}); + obj->Triangles.push_back({p4, p5, p6}); + } + }, object); break; } case G_QUAD: { @@ -128,8 +147,12 @@ namespace Editor { FVector p3 = FVector(vtx[v3].v.ob[0], vtx[v3].v.ob[1], vtx[v3].v.ob[2]); FVector p4 = FVector(vtx[v4].v.ob[0], vtx[v4].v.ob[1], vtx[v4].v.ob[2]); - object->Triangles.push_back({p1, p2, p3}); - object->Triangles.push_back({p1, p3, p4}); + std::visit([p1, p2, p3, p4](auto* obj) { + if (obj) { + obj->Triangles.push_back({p1, p2, p3}); + obj->Triangles.push_back({p1, p3, p4}); + } + }, object); break; } case G_ENDDL: diff --git a/src/engine/editor/Collision.h b/src/engine/editor/Collision.h index ee8fef4af..d761bf04c 100644 --- a/src/engine/editor/Collision.h +++ b/src/engine/editor/Collision.h @@ -3,6 +3,8 @@ #include <libultraship/libultraship.h> #include <libultra/gbi.h> #include "GameObject.h" +#include "engine/Actor.h" +#include "engine/objects/Object.h" #include "EditorMath.h" @@ -18,6 +20,6 @@ #define EDITOR_GFX_GET_OPCODE(var) ((uint32_t) ((var) & 0xFF000000)) namespace Editor { - void GenerateCollisionMesh(GameObject* object, Gfx* model, float scale); + void GenerateCollisionMesh(std::variant<AActor*, OObject*, GameObject*> object, Gfx* model, float scale); void DebugCollision(GameObject* obj, FVector pos, IRotator rot, FVector scale, const std::vector<Triangle>& triangles); -}
\ No newline at end of file +} diff --git a/src/engine/editor/Editor.cpp b/src/engine/editor/Editor.cpp index 1741cafa5..2fada8a29 100644 --- a/src/engine/editor/Editor.cpp +++ b/src/engine/editor/Editor.cpp @@ -24,9 +24,6 @@ extern "C" { } namespace Editor { - int gfx_create_framebuffer(uint32_t width, uint32_t height, uint32_t native_width, uint32_t native_height, - uint8_t resize); - Editor::Editor() { } @@ -39,21 +36,35 @@ namespace Editor { printf("Editor: Loading Editor...\n"); eObjectPicker.Load(); for (auto& object : eGameObjects) { - GenerateCollisionMesh(object, object->Model, 1.0f); + GenerateCollisionMesh(object, (Gfx*)object->Model, 1.0f); object->Load(); } + printf("Editor: Loading Complete!\n"); } - void Editor::Tick() { + void Editor::GenerateCollision() { + // for (auto& actor : gWorldInstance.Actors) { + // GenerateCollisionMesh(actor, (Gfx*)actor->Model, 1.0f); + // } + } + void Editor::Tick() { if (CVarGetInteger("gEditorEnabled", 0) == true) { bEditorEnabled = true; } else { bEditorEnabled = false; + gIsEditorPaused = false; // Prevents game being paused with the editor closed. return; } + // Set camera + if (CVarGetInteger("gFreecam", 0) == true) { + eCamera = &cameras[CAMERA_FREECAM]; + } else { + eCamera = &cameras[0]; + } + auto wnd = GameEngine::Instance->context->GetWindow(); static bool wasMouseDown = false; @@ -63,16 +74,16 @@ namespace Editor { Ship::Coords mousePos = wnd->GetMousePos(); bool isMouseDown = wnd->GetMouseState(Ship::LUS_MOUSE_BTN_LEFT); - auto it = std::remove_if(eGameObjects.begin(), eGameObjects.end(), - [](auto& object) { - if (*object->DespawnFlag == object->DespawnValue) { - delete object; // Free the pointed-to memory - return true; // Remove the pointer from the vector - } - return false; - }); + //auto it = std::remove_if(eGameObjects.begin(), eGameObjects.end(), + // [](auto& object) { + // if (*object->DespawnFlag == object->DespawnValue) { + // delete object; // Free the pointed-to memory + // return true; // Remove the pointer from the vector + // } + // return false; + // }); - eGameObjects.erase(it, eGameObjects.end()); + //eGameObjects.erase(it, eGameObjects.end()); if (isMouseDown && !wasMouseDown) { // Mouse just pressed (Pressed state) @@ -80,7 +91,7 @@ namespace Editor { isDragging = false; } - if (isMouseDown) { + if (isMouseDown) { // Mouse is being held (Held state) int dx = mousePos.x - mouseStartPos.x; int dy = mousePos.y - mouseStartPos.y; @@ -122,15 +133,16 @@ namespace Editor { } } - GameObject* Editor::AddObject(const char* name, FVector* pos, IRotator* rot, FVector* scale, Gfx* model, float collScale, GameObject::CollisionType collision, float boundingBoxSize, int32_t* despawnFlag, int32_t despawnValue) { + GameObject* Editor::AddObject(FVector pos, IRotator rot, FVector scale, const char* model, float collScale, GameObject::CollisionType collision, float boundingBoxSize) { //printf("After AddObj: Pos(%f, %f, %f), Name: %s, Model: %s\n", // pos->x, pos->y, pos->z, name, model); - if (model != nullptr) { - eGameObjects.push_back(new GameObject(name, pos, rot, scale, model, {}, collision, boundingBoxSize, despawnFlag, despawnValue)); - GenerateCollisionMesh(eGameObjects.back(), model, collScale); + + if (nullptr != model && model[0] != '\0') { + eGameObjects.push_back(new GameObject(pos, rot, scale, model, {}, collision, boundingBoxSize)); + GenerateCollisionMesh(eGameObjects.back(), (Gfx*)LOAD_ASSET_RAW(model), collScale); } else { // to bounding box or sphere collision - eGameObjects.push_back(new GameObject(name, pos, rot, scale, model, {}, GameObject::CollisionType::BOUNDING_BOX, - 10.0f, despawnFlag, despawnValue)); + eGameObjects.push_back(new GameObject(pos, rot, scale, model, {}, GameObject::CollisionType::BOUNDING_BOX, + 10.0f)); } return eGameObjects.back(); } @@ -140,27 +152,37 @@ namespace Editor { } void Editor::ClearObjects() { + ResetGizmo(); + for (auto& obj : eGameObjects) { delete obj; } eGameObjects.clear(); } + // Reset the gizmo + void Editor::ResetGizmo() { + eObjectPicker.eGizmo._selected = static_cast<GameObject*>(nullptr); + eObjectPicker._selected = static_cast<GameObject*>(nullptr); + eObjectPicker.eGizmo.Pos = FVector(0, 0, 0); + eObjectPicker.eGizmo.Enabled = false; + } + void Editor::DeleteObject() { - Gizmo* gizmo = &eObjectPicker.eGizmo; - if (gizmo->_selected && gizmo->_selected->DespawnFlag) { - *gizmo->_selected->DespawnFlag = gizmo->_selected->DespawnValue; - gizmo->_selected = nullptr; - eObjectPicker._selected = nullptr; - } + std::visit([this](auto* obj) { + if (nullptr != obj) { + gEditor.ResetGizmo(); // Unselect the object to prevent crashes + obj->Destroy(); + } + }, eObjectPicker.eGizmo._selected); } void Editor::ClearMatrixPool() { EditorMatrix.clear(); } - void Editor::SelectObjectFromSceneExplorer(GameObject* object) { + void Editor::SelectObjectFromSceneExplorer(std::variant<AActor*, OObject*, GameObject*> object) { eObjectPicker._selected = object; eObjectPicker.eGizmo.Enabled = true; eObjectPicker.eGizmo.SetGizmoNoCursor(object); diff --git a/src/engine/editor/Editor.h b/src/engine/editor/Editor.h index 60e578b21..38af5b915 100644 --- a/src/engine/editor/Editor.h +++ b/src/engine/editor/Editor.h @@ -5,7 +5,11 @@ #include <libultra/gbi.h> #include "GameObject.h" + #ifdef __cplusplus +extern "C" { +#include "camera.h" +} #include "ObjectPicker.h" namespace Editor { @@ -22,16 +26,19 @@ public: void Tick(); void Draw(); void Load(); - GameObject* AddObject(const char* name, FVector* pos, IRotator* rot, FVector* scale, Gfx* model, float collScale, GameObject::CollisionType collision, float boundingBoxSize, int32_t* despawnFlag, int32_t despawnValue); + void GenerateCollision(); + GameObject* AddObject(FVector pos, IRotator rot, FVector scale, const char* model, float collScale, GameObject::CollisionType collision, float boundingBoxSize); void AddLight(const char* name, FVector* pos, s8* rot); void ClearObjects(); + void ResetGizmo(); void RemoveObject(); - void SelectObjectFromSceneExplorer(GameObject* object); + void SelectObjectFromSceneExplorer(std::variant<AActor*, OObject*, GameObject*> object); void SetLevelDimensions(s16 minX, s16 maxX, s16 minZ, s16 maxZ, s16 minY, s16 maxY); void ClearMatrixPool(); void DeleteObject(); bool bEditorEnabled = false; + Camera* eCamera = &cameras[0]; private: bool _draw = false; Vec3f _ray; diff --git a/src/engine/editor/EditorMath.cpp b/src/engine/editor/EditorMath.cpp index f541d92f2..38a1fff5e 100644 --- a/src/engine/editor/EditorMath.cpp +++ b/src/engine/editor/EditorMath.cpp @@ -42,12 +42,13 @@ bool IsInGameScreen() { FVector ScreenRayTrace() { auto wnd = GameEngine::Instance->context->GetWindow(); - Camera* camera = &cameras[0]; + Camera* camera = gEditor.eCamera; Ship::Coords mouse = wnd->GetMousePos(); auto gfx_current_game_window_viewport = GetInterpreter()->mGameWindowViewport; mouse.x -= gfx_current_game_window_viewport.x; mouse.y -= gfx_current_game_window_viewport.y; + // Get screen dimensions uint32_t width = OTRGetGameViewportWidth(); uint32_t height = OTRGetGameViewportHeight(); @@ -285,7 +286,6 @@ std::optional<FVector> QueryHandleIntersection(MtxF mtx, Ray ray, const Triangle if (IntersectRayTriangle(localRay, tri, t)) { FVector localClickPosition = localRay.Origin + localRay.Direction * t; FVector worldClickPosition = TransformVecByMatrix(localClickPosition, (float(*)[4])&mtx); - return worldClickPosition; // Stop checking objects if we selected a Gizmo handle } return std::nullopt; @@ -381,8 +381,9 @@ float CalculateAngle(const FVector& start, const FVector& end) { } void SetDirectionFromRotator(IRotator rot, s8 direction[3]) { + rot.yaw += 0xC000; //! @warning dumb hack to align the light properly float yaw = (rot.yaw) * (M_PI / 32768.0f); // Convert from n64 binary angles 0-0xFFFF 0-360 degrees to radians - float pitch = rot.pitch * (M_PI / 32768.0f); + float pitch = rot.pitch * (M_PI / 32768.0f); // Compute unit direction vector float x = cosf(yaw) * cosf(pitch); @@ -411,10 +412,11 @@ void SetRotatorFromDirection(FVector direction, IRotator* rot) { } FVector GetPositionAheadOfCamera(f32 dist) { - FVector pos = FVector(cameras[0].pos[0], cameras[0].pos[1], cameras[0].pos[2]); + Camera* camera = gEditor.eCamera; + FVector pos = FVector(camera->pos[0], camera->pos[1], camera->pos[2]); - f32 pitch = (cameras[0].rot[2] / 65535.0f) * 360.0f; - f32 yaw = (cameras[0].rot[1] / 65535.0f) * 360.0f; + f32 pitch = (camera->rot[2] / 65535.0f) * 360.0f; + f32 yaw = (camera->rot[1] / 65535.0f) * 360.0f; // Convert degrees to radians pitch = pitch * M_PI / 180.0f; @@ -422,7 +424,7 @@ FVector GetPositionAheadOfCamera(f32 dist) { // Compute forward vector FVector forward( - -sinf(yaw), // X + sinf(yaw), // X -sinf(pitch), // Y cosf(yaw) // Z (vertical component) ); diff --git a/src/engine/editor/GameObject.cpp b/src/engine/editor/GameObject.cpp index 6f52c8ac8..30a1722f4 100644 --- a/src/engine/editor/GameObject.cpp +++ b/src/engine/editor/GameObject.cpp @@ -3,8 +3,7 @@ namespace Editor { - GameObject::GameObject(const char* name, FVector* pos, IRotator* rot, FVector* scale, Gfx* model, std::vector<Triangle> triangles, CollisionType collision, float boundingBoxSize, int32_t* despawnFlag, int32_t despawnValue) { - Name = name; + GameObject::GameObject(FVector pos, IRotator rot, FVector scale, const char* model, std::vector<Triangle> triangles, CollisionType collision, float boundingBoxSize) { Pos = pos; Rot = rot; Scale = scale; @@ -12,13 +11,6 @@ namespace Editor { Triangles = triangles; Collision = collision; BoundingBoxSize = boundingBoxSize; - DespawnFlag = despawnFlag; - DespawnValue = despawnValue; - } - - GameObject::GameObject(FVector* pos, Vec3s* rot) { - //Pos = pos; - //Rot = rot; } GameObject::GameObject() {}; @@ -27,4 +19,23 @@ namespace Editor { void GameObject::Tick(){}; + FVector GameObject::GetLocation() const { + return Pos; + }; + IRotator GameObject::GetRotation() const { + return Rot; + } + FVector GameObject::GetScale() const { + return Scale; + } + void GameObject::Translate(FVector pos) { + Pos = pos; + }; + void GameObject::Rotate(IRotator rot) { + Rot = rot; + }; + void GameObject::SetScale(FVector scale) { + Scale = scale; + }; + } // namespace Editor diff --git a/src/engine/editor/GameObject.h b/src/engine/editor/GameObject.h index ca08d8ba9..35ce90dbf 100644 --- a/src/engine/editor/GameObject.h +++ b/src/engine/editor/GameObject.h @@ -6,6 +6,9 @@ #include "../CoreMath.h" #include "EditorMath.h" #include <vector> +#include "engine/SpawnParams.h" + +#include "src/port/ui/DefaultProperties.h" extern "C" { #include "common_structs.h" @@ -22,23 +25,32 @@ public: BOUNDING_SPHERE }; - GameObject(const char* name, FVector* pos, IRotator* rot, FVector* scale, Gfx* model, std::vector<Triangle> triangles, CollisionType collision, float boundingBoxSize, int32_t* despawnFlag, int32_t despawnValue); - GameObject(FVector* pos, Vec3s* rot); + GameObject(FVector pos, IRotator rot, FVector scale, const char* model, std::vector<Triangle> triangles, CollisionType collision, float boundingBoxSize); GameObject(); virtual void Tick(); virtual void Draw(); virtual void Load() {}; + FVector GetLocation() const; + IRotator GetRotation() const; + FVector GetScale() const; + void Translate(FVector pos); + void Rotate(IRotator rot); + void SetScale(FVector scale); + void Destroy() {}; const char* Name; - FVector* Pos; - IRotator* Rot; - FVector* Scale; - Gfx* Model; + const char* ResourceName; + FVector SpawnPos = {0.0f, 0.0f, 0.0f}; + IRotator SpawnRot = {0, 0, 0}; + FVector SpawnScale = {1.0f, 1.0f, 1.0f}; + float Speed; + FVector Pos; + IRotator Rot; + FVector Scale; + const char* Model = ""; std::vector<Triangle> Triangles; CollisionType Collision; float BoundingBoxSize; - int32_t* DespawnFlag; - int32_t DespawnValue; - + virtual void DrawEditorProperties() { DrawDefaultEditorProperties(); }; }; } diff --git a/src/engine/editor/Gizmo.cpp b/src/engine/editor/Gizmo.cpp index 4987242f0..9c518b53b 100644 --- a/src/engine/editor/Gizmo.cpp +++ b/src/engine/editor/Gizmo.cpp @@ -10,6 +10,10 @@ #include "port/Engine.h" #include <controller/controldevice/controller/mapping/keyboard/KeyboardScancodes.h> #include <window/Window.h> +#include "engine/Matrix.h" +#include "engine/Actor.h" +#include "engine/objects/Object.h" +#include "engine/editor/GameObject.h" #include "engine/actors/Ship.h" @@ -29,34 +33,34 @@ namespace Editor { void Gizmo::Load() { /* Translate handle collision */ - RedCollision.Pos = &Pos; - RedCollision.Model = (Gfx*)"__OTR__editor/gizmo/translate_handle_red"; + RedCollision.Pos = Pos; + RedCollision.Model = "__OTR__editor/gizmo/translate_handle_red"; - GreenCollision.Pos = &Pos; - GreenCollision.Model = (Gfx*)"__OTR__editor/gizmo/translate_handle_green"; + GreenCollision.Pos = Pos; + GreenCollision.Model = "__OTR__editor/gizmo/translate_handle_green"; - BlueCollision.Pos = &Pos; - BlueCollision.Model = (Gfx*)"__OTR__editor/gizmo/translate_handle_blue"; + BlueCollision.Pos = Pos; + BlueCollision.Model = "__OTR__editor/gizmo/translate_handle_blue"; /* Rotate handle collision */ - RedRotateCollision.Pos = &Pos; - RedRotateCollision.Model = (Gfx*)"__OTR__editor/gizmo/rot_handle_red"; + RedRotateCollision.Pos = Pos; + RedRotateCollision.Model = "__OTR__editor/gizmo/rot_handle_red"; - GreenRotateCollision.Pos = &Pos; - GreenRotateCollision.Model = (Gfx*)"__OTR__editor/gizmo/rot_handle_green"; + GreenRotateCollision.Pos = Pos; + GreenRotateCollision.Model = "__OTR__editor/gizmo/rot_handle_green"; - BlueRotateCollision.Pos = &Pos; - BlueRotateCollision.Model = (Gfx*)"__OTR__editor/gizmo/rot_handle_blue"; + BlueRotateCollision.Pos = Pos; + BlueRotateCollision.Model = "__OTR__editor/gizmo/rot_handle_blue"; /* Scale handle collision */ - RedScaleCollision.Pos = &Pos; - RedScaleCollision.Model = (Gfx*)"__OTR__editor/gizmo/scale_handle_red"; + RedScaleCollision.Pos = Pos; + RedScaleCollision.Model = "__OTR__editor/gizmo/scale_handle_red"; - GreenScaleCollision.Pos = &Pos; - GreenScaleCollision.Model = (Gfx*)"__OTR__editor/gizmo/scale_handle_green"; + GreenScaleCollision.Pos = Pos; + GreenScaleCollision.Model = "__OTR__editor/gizmo/scale_handle_green"; - BlueScaleCollision.Pos = &Pos; - BlueScaleCollision.Model = (Gfx*)"__OTR__editor/gizmo/scale_handle_blue"; + BlueScaleCollision.Pos = Pos; + BlueScaleCollision.Model = "__OTR__editor/gizmo/scale_handle_blue"; GenerateCollisionMesh(&RedCollision, (Gfx*)LOAD_ASSET_RAW(RedCollision.Model), 1.0f); GenerateCollisionMesh(&GreenCollision, (Gfx*)LOAD_ASSET_RAW(GreenCollision.Model), 1.0f); @@ -89,178 +93,256 @@ void Gizmo::Tick() { } // Makes the gizmo visible -void Gizmo::SetGizmo(GameObject* object, Ray ray) { - _selected = object; +void Gizmo::SetGizmo(const std::variant<AActor*, OObject*, GameObject*>& object, Ray ray) { _ray = ray.Direction; - Pos = FVector( - object->Pos->x, - object->Pos->y, - object->Pos->z - ); + std::visit([this](auto* obj) { + _selected = obj; + this->Pos = obj->GetLocation(); + }, object); } -void Gizmo::SetGizmoNoCursor(GameObject* object) { - _selected = object; - Pos = FVector( - object->Pos->x, - object->Pos->y, - object->Pos->z - ); +void Gizmo::SetGizmoNoCursor(const std::variant<AActor*, OObject*, GameObject*>& object) { + std::visit([this](auto* obj) { + _selected = obj; + Pos = obj->GetLocation(); + }, object); } void Gizmo::Translate() { static float length = 180.0f; // Default value - // Prevent nullptr exceptions - if (_selected == NULL || _selected->Pos == NULL) { - return; - } + std::visit([this](auto* obj) { + Camera* camera = gEditor.eCamera; + float x, y, z = 0; + if (nullptr == obj) { + return; + } + + const FVector location = obj->GetLocation(); - if (Enabled) { length = sqrt( - pow(_selected->Pos->x - cameras[0].pos[0], 2) + - pow(_selected->Pos->y - cameras[0].pos[1], 2) + - pow(_selected->Pos->z - cameras[0].pos[2], 2) + pow(location.x - camera->pos[0], 2) + + pow(location.y - camera->pos[1], 2) + + pow(location.z - camera->pos[2], 2) ); - switch(SelectedHandle) { + switch(this->SelectedHandle) { case GizmoHandle::All_Axis: - _selected->Pos->x = (cameras[0].pos[0] + _ray.x * PickDistance) + _cursorOffset.x; - _selected->Pos->y = (cameras[0].pos[1] + _ray.y * PickDistance) + _cursorOffset.y; - _selected->Pos->z = (cameras[0].pos[2] + _ray.z * PickDistance) + _cursorOffset.z; if (CVarGetInteger("gEditorSnapToGround", 0) == true) { - _selected->Pos->y = SnapToSurface(_selected->Pos); + y = SnapToSurface(location); + } else { + y = ((camera->pos[1] + _ray.y * PickDistance) + _cursorOffset.y); } + + obj->Translate( + FVector( + ((camera->pos[0] + _ray.x * PickDistance) + _cursorOffset.x), + y, + ((camera->pos[2] + _ray.z * PickDistance) + _cursorOffset.z) + ) + ); break; case GizmoHandle::X_Axis: - _selected->Pos->x = (cameras[0].pos[0] + _ray.x * length) + _cursorOffset.x; if (CVarGetInteger("gEditorSnapToGround", 0) == true) { - _selected->Pos->y = SnapToSurface(_selected->Pos); + y = SnapToSurface(location); + } else { + y = location.y; // Preserve Y } + + obj->Translate( + FVector( + ((camera->pos[0] + _ray.x * length) + _cursorOffset.x), + y, + location.z // Preserve Z + ) + ); break; case GizmoHandle::Y_Axis: - _selected->Pos->y = (cameras[0].pos[1] + _ray.y * length) + _cursorOffset.y; + obj->Translate( + FVector( + location.x, // Preserve X + ((camera->pos[1] + _ray.y * length) + _cursorOffset.y), + location.z // Preserve Z + ) + ); break; case GizmoHandle::Z_Axis: - _selected->Pos->z = (cameras[0].pos[2] + _ray.z * length) + _cursorOffset.z; if (CVarGetInteger("gEditorSnapToGround", 0) == true) { - _selected->Pos->y = SnapToSurface(_selected->Pos); + y = SnapToSurface(location); + } else { + y = location.y; // Preserve Y } + obj->Translate( + FVector( + location.x, // Preserve X + y, + ((camera->pos[2] + _ray.z * length) + _cursorOffset.z) + ) + ); break; } - if (CVarGetInteger("gEditorBoundary", 0) == true) { - _selected->Pos->x = MAX(_selected->Pos->x, dimensions.MinX); - _selected->Pos->x = MIN(_selected->Pos->x, dimensions.MaxX); + FVector newLoc = obj->GetLocation(); + x = newLoc.x; + y = newLoc.y; + z = newLoc.z; - _selected->Pos->y = MAX(_selected->Pos->y, dimensions.MinY); - _selected->Pos->y = MIN(_selected->Pos->y, dimensions.MaxY); - _selected->Pos->z = MAX(_selected->Pos->z, dimensions.MinZ); - _selected->Pos->z = MIN(_selected->Pos->z, dimensions.MaxZ); + if (CVarGetInteger("gEditorBoundary", 0) == true) { +#define EDITOR_CLAMP(value, min, max) ((value) < (min) ? min : (value) > (max) ? max : value) + x = EDITOR_CLAMP(newLoc.x, dimensions.MinX, dimensions.MaxX); + y = EDITOR_CLAMP(newLoc.y, dimensions.MinY, dimensions.MaxY); + z = EDITOR_CLAMP(newLoc.z, dimensions.MinZ, dimensions.MaxZ); + obj->Translate(FVector(x, y, z)); +#undef EDITOR_CLAMP } - Pos = FVector( - _selected->Pos->x, - _selected->Pos->y, - _selected->Pos->z - ); - } + // Update the gizmo position + Pos = FVector(x, y, z); + + // Pass the _selected object into this lambda function + }, _selected); } -f32 Gizmo::SnapToSurface(FVector* pos) { +f32 Gizmo::SnapToSurface(const FVector pos) { float y; - y = spawn_actor_on_surface(pos->x, 2000.0f, pos->z); + y = spawn_actor_on_surface(pos.x, 2000.0f, pos.z); if (y == 3000.0f || y == -3000.0f) { - y = pos->y; + y = pos.y; } return y; } void Gizmo::Rotate() { - FVector cam = FVector(cameras[0].pos[0], cameras[0].pos[1], cameras[0].pos[2]); + std::visit([this](auto* obj) { + Camera* camera = gEditor.eCamera; + FVector cam = FVector(camera->pos[0], camera->pos[1], camera->pos[2]); + IRotator rot; - if (_selected == nullptr || _selected->Rot == nullptr) { - return; - } + if (nullptr == obj) { + return; + } - // Store initial scale at the beginning of the drag - if (ManipulationStart) { - ManipulationStart = false; - InitialRotation = *_selected->Rot; // Store initial rotation - } + // Store initial scale at the beginning of the drag + if (ManipulationStart) { + ManipulationStart = false; + InitialRotation = obj->GetRotation(); // Store initial rotation + } - // Initial click position - FVector clickPos = *_selected->Pos - _cursorOffset; + // Initial click position + FVector clickPos = obj->GetLocation() - _cursorOffset; - // Calculate difference - FVector diff = (cam + _ray * PickDistance) - clickPos; + // Calculate difference + FVector diff = (cam + _ray * PickDistance) - clickPos; - // Set rotation sensitivity - diff = diff * 100.0f; - switch (SelectedHandle) { - case GizmoHandle::X_Axis: - _selected->Rot->pitch = (uint16_t)InitialRotation.pitch + diff.x; - break; - case GizmoHandle::Y_Axis: - _selected->Rot->yaw = (uint16_t)InitialRotation.yaw + diff.y; - break; - case GizmoHandle::Z_Axis: - _selected->Rot->roll = (uint16_t)InitialRotation.roll + diff.z; + // Set rotation sensitivity + diff = diff * 100.0f; + switch (SelectedHandle) { + case GizmoHandle::X_Axis: + rot.Set( + (uint16_t)(InitialRotation.pitch + diff.x), + InitialRotation.yaw, + InitialRotation.roll + ); break; - } + case GizmoHandle::Y_Axis: + rot.Set( + InitialRotation.pitch, + (uint16_t)(InitialRotation.yaw + diff.y), + InitialRotation.roll + ); + break; + case GizmoHandle::Z_Axis: + rot.Set( + InitialRotation.pitch, + InitialRotation.yaw, + (uint16_t)(InitialRotation.roll + diff.z) + ); + break; + } + obj->Rotate(rot); + // Pass the _selected object into this lambda function + }, _selected); } void Gizmo::Scale() { - FVector cam = FVector(cameras[0].pos[0], cameras[0].pos[1], cameras[0].pos[2]); - if (_selected == nullptr || _selected->Scale == nullptr) { - return; - } + std::visit([this](auto* obj) { + Camera* camera = gEditor.eCamera; + FVector cam = FVector(camera->pos[0], camera->pos[1], camera->pos[2]); + if (nullptr == obj) { + return; + } - // Store initial scale at the beginning of the drag - if (ManipulationStart) { - ManipulationStart = false; - InitialScale = *_selected->Scale; - } + // Store initial scale at the beginning of the drag + if (ManipulationStart) { + ManipulationStart = false; + InitialScale = obj->GetScale(); + } - // Initial click position - FVector clickPos = *_selected->Pos - _cursorOffset; + FVector scale = obj->GetScale(); - // Calculate difference - FVector diff = (cam + _ray * PickDistance) - clickPos; + // Initial click position + FVector clickPos = obj->GetLocation() - _cursorOffset; - // Lower scaling sensitivity - diff = diff * 0.01f; + // Calculate difference + FVector diff = (cam + _ray * PickDistance) - clickPos; - switch (SelectedHandle) { - case GizmoHandle::X_Axis: - _selected->Scale->x = InitialScale.x + -diff.x; - break; - case GizmoHandle::Y_Axis: - _selected->Scale->y = InitialScale.y + diff.y; - break; - case GizmoHandle::Z_Axis: - _selected->Scale->z = InitialScale.z + -diff.z; - break; - case GizmoHandle::All_Axis: - float uniformScale = (diff.x - diff.y - diff.z) / 3.0f; - uniformScale *= 1.8; // Increased sensitivity - _selected->Scale->x = uniformScale; - _selected->Scale->y = uniformScale; - _selected->Scale->z = uniformScale; - break; - } + // Lower scaling sensitivity + diff = diff * 0.01f; + + switch (SelectedHandle) { + case GizmoHandle::X_Axis: + obj->SetScale( + FVector( + (InitialScale.x + -diff.x), + scale.y, + scale.z + ) + ); + break; + case GizmoHandle::Y_Axis: + obj->SetScale( + FVector( + scale.x, + (InitialScale.y + diff.y), + scale.z + ) + ); + break; + case GizmoHandle::Z_Axis: + obj->SetScale( + FVector( + scale.x, + scale.y, + (InitialScale.z + -diff.z) + ) + ); + break; + case GizmoHandle::All_Axis: + float uniformScale = (diff.x - diff.y - diff.z) / 3.0f; + uniformScale *= 1.8; // Increased sensitivity + obj->SetScale( + FVector( + uniformScale, + uniformScale, + uniformScale + ) + ); + break; + } + // Pass the _selected object into this lambda function + }, _selected); } void Gizmo::Draw() { if (Enabled) { DrawHandles(); - //DebugCollision(&RedCollision, Pos, {0, 0, 0}, {0.05f, 0.05f, 0.05f}, RedCollision.Triangles); - //DebugCollision(&BlueCollision, Pos, {90, 0, 0}, {0.05f, 0.05f, 0.05f}, BlueCollision.Triangles); - //DebugCollision(&GreenCollision, Pos, {0, 90, 0}, {0.05f, 0.05f, 0.05f}, GreenCollision.Triangles); - //DebugCollision(&RedRotateCollision, Pos, {0, 0, 0}, {0.15f, 0.15f, 0.15f}, RedRotateCollision.Triangles); + // DebugCollision(&RedCollision, Pos, {0, 0, 0}, {0.05f, 0.05f, 0.05f}, RedCollision.Triangles); + // DebugCollision(&BlueCollision, Pos, {90, 0, 0}, {0.05f, 0.05f, 0.05f}, BlueCollision.Triangles); + // DebugCollision(&GreenCollision, Pos, {0, 90, 0}, {0.05f, 0.05f, 0.05f}, GreenCollision.Triangles); + // DebugCollision(&RedRotateCollision, Pos, {0, 0, 0}, {0.15f, 0.15f, 0.15f}, RedRotateCollision.Triangles); //DebugCollision((uintptr_t)_selected, Pos, BlueRotateCollision.Triangles); //DebugCollision((uintptr_t)_selected, Pos, GreenRotateCollision.Triangles); } @@ -316,11 +398,12 @@ void Gizmo::DrawHandles() { Editor_AddMatrix(mainMtx, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); if (center) { + Camera* camera = gEditor.eCamera; Mat4 CenterMtx; Editor_MatrixIdentity(CenterMtx); // Calculate camera-to-object distance - FVector cameraDir = FVector(Pos.x - cameras[0].pos[0], Pos.y - cameras[0].pos[1], Pos.z - cameras[0].pos[2]); + FVector cameraDir = FVector(Pos.x - camera->pos[0], Pos.y - camera->pos[1], Pos.z - camera->pos[2]); cameraDir = cameraDir.Normalize(); IRotator centerRot; diff --git a/src/engine/editor/Gizmo.h b/src/engine/editor/Gizmo.h index 47ef99549..f98b634b1 100644 --- a/src/engine/editor/Gizmo.h +++ b/src/engine/editor/Gizmo.h @@ -4,6 +4,9 @@ #include <libultra/gbi.h> #include "Collision.h" #include "GameObject.h" +#include "engine/Actor.h" +#include "engine/objects/Object.h" +#include <variant> namespace Editor { @@ -28,13 +31,13 @@ public: void Draw(); void Load(); - void SetGizmo(GameObject* object, Ray ray); - void SetGizmoNoCursor(GameObject* object); // Used for scene explorer selection + void SetGizmo(const std::variant<AActor*, OObject*, GameObject*>& object, Ray ray); + void SetGizmoNoCursor(const std::variant<AActor*, OObject*, GameObject*>& object); // Used for scene explorer selection void Translate(); void Rotate(); void Scale(); void DrawHandles(); - f32 SnapToSurface(FVector* pos); + f32 SnapToSurface(FVector pos); struct TrackDimensions { s16 MinX = -10000; @@ -78,7 +81,7 @@ public: float HandleSize = 2.0f; FVector _ray; - GameObject* _selected = nullptr; + std::variant<AActor*, OObject*, GameObject*> _selected; private: bool _draw = false; }; diff --git a/src/engine/editor/Handles.cpp b/src/engine/editor/Handles.cpp index 9074a2b45..532af6af6 100644 --- a/src/engine/editor/Handles.cpp +++ b/src/engine/editor/Handles.cpp @@ -5,8 +5,6 @@ namespace Editor { Handles::Handles() { - Pos = &pos; - Rot = &rot; } void Handles::Load() { diff --git a/src/engine/editor/Handles.h b/src/engine/editor/Handles.h index 1faa4b59e..ecd8f8f90 100644 --- a/src/engine/editor/Handles.h +++ b/src/engine/editor/Handles.h @@ -13,7 +13,5 @@ namespace Editor { virtual void Draw() override; virtual void Load() override; - FVector pos; - IRotator rot; }; } diff --git a/src/engine/editor/Light.cpp b/src/engine/editor/Light.cpp index 594383bd3..abd9ee85e 100644 --- a/src/engine/editor/Light.cpp +++ b/src/engine/editor/Light.cpp @@ -4,6 +4,7 @@ #include "../CoreMath.h" #include <libultra/types.h> #include "../World.h" +#include "engine/Matrix.h" #include "Light.h" #include "port/Engine.h" @@ -30,14 +31,16 @@ namespace Editor { size_t LightObject::NumLights = 0; - LightObject::LightObject(const char* name, FVector* pos, s8* direction) : GameObject(nullptr, nullptr) { + LightObject::LightObject(const char* name, FVector* pos, s8* direction) { Name = name; - Pos = &LightPos; - Rot = &LightRot; - Scale = &LightScale; + ResourceName = "editor:light"; - DespawnFlag = &_despawnFlag; - DespawnValue = -1; + Pos = FVector(0, 100, 0); + Rot = IRotator(0, 0, 0); + Scale = FVector(0.1, 0.1, 0.1); + + SpawnPos = Pos; + SpawnRot = Rot; Direction = direction; @@ -51,9 +54,10 @@ size_t LightObject::NumLights = 0; } void LightObject::Tick() { - SetDirectionFromRotator(*Rot, Direction); + SetDirectionFromRotator(Rot, Direction); } void LightObject::Draw() { + Camera* camera = gEditor.eCamera; Mat4 mtx_sun; Editor_MatrixIdentity(mtx_sun); gSPSetGeometryMode(gDisplayListHead++, G_SHADING_SMOOTH); @@ -61,23 +65,22 @@ size_t LightObject::NumLights = 0; // Calculate camera-to-object distance - FVector cameraDir = FVector(LightPos.x - cameras[0].pos[0], LightPos.y - cameras[0].pos[1], LightPos.z - cameras[0].pos[2]); + FVector cameraDir = FVector(Pos.x - camera->pos[0], Pos.y - camera->pos[1], Pos.z - camera->pos[2]); cameraDir = cameraDir.Normalize(); IRotator centerRot; SetRotatorFromDirection(cameraDir, ¢erRot); - // Account for object not facing the correct direction when exported + // The sun was exported facing the wrong direction. + // Thus, force the sun texture to face the camera. centerRot.yaw += 0x4000; - ApplyMatrixTransformations(mtx_sun, LightPos, centerRot, LightScale); + ApplyMatrixTransformations(mtx_sun, Pos, centerRot, Scale); Editor_AddMatrix(mtx_sun, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPDisplayList(gDisplayListHead++, sun_LightModel_mesh); // Draw Arrow Mat4 mtx_arrow; - IRotator rot = LightRot; - rot.yaw += 0x4000; - ApplyMatrixTransformations(mtx_arrow, LightPos, rot, LightScale); + ApplyMatrixTransformations(mtx_arrow, Pos, Rot, Scale); Editor_AddMatrix(mtx_arrow, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPDisplayList(gDisplayListHead++, (Gfx*)"__OTR__editor/light/sun_arrow"); } diff --git a/src/engine/editor/Light.h b/src/engine/editor/Light.h index 972942663..3a177053e 100644 --- a/src/engine/editor/Light.h +++ b/src/engine/editor/Light.h @@ -3,7 +3,6 @@ #include <libultraship/libultraship.h> #include <libultra/gbi.h> #include "Collision.h" -#include "Gizmo.h" #include "GameObject.h" namespace Editor { @@ -17,11 +16,7 @@ public: virtual void Load() override; static size_t NumLights; - FVector LightPos = FVector(0, 100, 0); - IRotator LightRot = IRotator(0, 0, 0); - FVector LightScale = FVector(0.1, 0.1, 0.1); s8* Direction; - s32 _despawnFlag = 0; u8 sun_sun_rgba32[16384] = { 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, diff --git a/src/engine/editor/ObjectPicker.cpp b/src/engine/editor/ObjectPicker.cpp index 9cfeed5f8..a59cd507a 100644 --- a/src/engine/editor/ObjectPicker.cpp +++ b/src/engine/editor/ObjectPicker.cpp @@ -2,7 +2,9 @@ #include <libultra/gbi.h> #include "../CoreMath.h" #include <libultra/types.h> -#include "../World.h" +#include "engine/World.h" +#include "engine/Actor.h" +#include "engine/objects/Object.h" #include "ObjectPicker.h" #include "port/Engine.h" @@ -33,8 +35,9 @@ void ObjectPicker::Tick() { } void ObjectPicker::SelectObject(std::vector<GameObject*> objects) { + Camera* camera = gEditor.eCamera; Ray ray; - ray.Origin = FVector(cameras[0].pos[0], cameras[0].pos[1], cameras[0].pos[2]); + ray.Origin = FVector(camera->pos[0], camera->pos[1], camera->pos[2]); // This allows selection of objects in the scene explorer. // Otherwise this would still run when selecting buttons in editor windows. @@ -43,22 +46,23 @@ void ObjectPicker::SelectObject(std::vector<GameObject*> objects) { ObjectPicker::FindObject(ray, objects); - if (_selected != nullptr) { - eGizmo.SetGizmo(_selected, ray); - eGizmo.Enabled = true; - } else { - //eGizmo.Disable(); - eGizmo.Enabled = false; - eGizmo._selected = nullptr; - } + std::visit([this, ray](auto* obj) { + if (obj) { + eGizmo.SetGizmo(_selected, ray); + eGizmo.Enabled = true; + } else { + eGizmo.Enabled = false; + _selected = static_cast<GameObject*>(nullptr); + } + }, _selected); } } void ObjectPicker::DragHandle() { + Camera* camera = gEditor.eCamera; Ray ray; - ray.Origin = FVector(cameras[0].pos[0], cameras[0].pos[1], cameras[0].pos[2]); + ray.Origin = FVector(camera->pos[0], camera->pos[1], camera->pos[2]); ray.Direction = ScreenRayTrace(); - // Skip if a drag is already in progress if (eGizmo.SelectedHandle != Gizmo::GizmoHandle::None) { eGizmo._ray = ray.Direction; @@ -116,7 +120,6 @@ void ObjectPicker::DragHandle() { break; } - if (closestHandle != Gizmo::GizmoHandle::None && closestClickPos.has_value()) { eGizmo.SelectedHandle = closestHandle; eGizmo._ray = ray.Direction; @@ -126,32 +129,68 @@ void ObjectPicker::DragHandle() { } void ObjectPicker::Draw() { - if (_selected != NULL) { - eGizmo.Draw(); - } + std::visit([](auto* obj) { + if (obj) { + gEditor.eObjectPicker.eGizmo.Draw(); + } + }, _selected); if (Debug) { + Camera* camera = gEditor.eCamera; Mat4 CursorMtx; IRotator rot = IRotator(0,0,0); - FVector scale = FVector(0.1, 0.1, 0.1); + FVector scale = FVector(1, 1, 1); FVector ray = ScreenRayTrace(); - float x = (cameras[0].pos[0] + ray.x * 800); - float y = (cameras[0].pos[1] + ray.y * 800); - float z = (cameras[0].pos[2] + ray.z * 800); + float x = (camera->pos[0] + ray.x * 800); + float y = (camera->pos[1] + ray.y * 800); + float z = (camera->pos[2] + ray.z * 800); ApplyMatrixTransformations((float(*)[4])&CursorMtx, FVector(x, y, z), rot, scale); Editor_AddMatrix((float(*)[4])&CursorMtx, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); - gSPDisplayList(gDisplayListHead++, (Gfx*)"__OTR__tracks/sphere"); + gSPDisplayList(gDisplayListHead++, (Gfx*)"__OTR__gizmo/gizmo_center_button"); } } void ObjectPicker::FindObject(Ray ray, std::vector<GameObject*> objects) { - bool found = false; - GameObject* closestObject = nullptr; - float closestDistance = FLT_MAX; + float distance = FLT_MAX; + std::variant<AActor*, OObject*, GameObject*> object; + + _selected = static_cast<GameObject*>(nullptr); - for (auto& object : objects) { + auto [hitActor, hitActorDist] = ObjectPicker::CheckAActorRay(ray); + if (hitActor) { + object = hitActor; + distance = hitActorDist; + } + + // OObjects + auto [hitObject, hitObjectDist] = ObjectPicker::CheckOObjectRay(ray); + if (hitObject && (hitObjectDist < distance)) { + object = hitObject; + distance = hitObjectDist; + } + + // Editor objects + auto [hitEditorObject, hitEditorObjectDist] = ObjectPicker::CheckEditorObjectRay(ray); + if (hitEditorObject && (hitEditorObjectDist < distance)) { + object = hitEditorObject; + distance = hitEditorObjectDist; + } + + // Set _selected from object variant + _selected = object; + std::visit([this](auto* obj) { + if (obj) { + } + }, object); +} + +std::pair<GameObject*, float> ObjectPicker::CheckEditorObjectRay(Ray ray) { + GameObject* hitObject = nullptr; + float hitDistance = FLT_MAX; + + for (auto& object : gEditor.eGameObjects) { float boundingBox = object->BoundingBoxSize; if (boundingBox == 0.0f) { boundingBox = 2.0f; @@ -161,11 +200,10 @@ void ObjectPicker::FindObject(Ray ray, std::vector<GameObject*> objects) { case GameObject::CollisionType::VTX_INTERSECT: for (const auto& tri : object->Triangles) { float t; - if (IntersectRayTriangleAndTransform(ray, *object->Pos, tri, t)) { - if (t < closestDistance) { - closestDistance = t; - closestObject = object; - printf("SELECTED OBJECT\n"); + if (IntersectRayTriangleAndTransform(ray, object->Pos, tri, t)) { + if (t < hitDistance) { + hitDistance = t; + hitObject = object; } } } @@ -173,18 +211,18 @@ void ObjectPicker::FindObject(Ray ray, std::vector<GameObject*> objects) { case GameObject::CollisionType::BOUNDING_BOX: { float max = 2.0f; float min = -2.0f; - Vec3f boxMin = { object->Pos->x + boundingBox * min, - object->Pos->y + boundingBox * min, - object->Pos->z + boundingBox * min }; + Vec3f boxMin = { object->Pos.x + boundingBox * min, + object->Pos.y + boundingBox * min, + object->Pos.z + boundingBox * min }; - Vec3f boxMax = { object->Pos->x + boundingBox * max, - object->Pos->y + boundingBox * max, - object->Pos->z + boundingBox * max }; + Vec3f boxMax = { object->Pos.x + boundingBox * max, + object->Pos.y + boundingBox * max, + object->Pos.z + boundingBox * max }; float t; if (QueryCollisionRayActor(&ray.Origin.x, &ray.Direction.x, boxMin, boxMax, &t)) { - if (t < closestDistance) { - closestDistance = t; - closestObject = object; + if (t < hitDistance) { + hitDistance = t; + hitObject = object; printf("FOUND BOUNDING BOX OBJECT\n"); } break; @@ -196,12 +234,62 @@ void ObjectPicker::FindObject(Ray ray, std::vector<GameObject*> objects) { break; } } - if (closestObject != nullptr) { - _selected = closestObject; - // printf("FOUND COLLISION %d\n", type); - } else { - // printf("NO COLLISION\n"); - _selected = nullptr; + return std::pair(hitObject, hitDistance); +} + +std::pair<OObject*, float> ObjectPicker::CheckOObjectRay(Ray ray) { + OObject* hitObject = nullptr; + float hitDistance = FLT_MAX; + + + return std::pair(hitObject, hitDistance); +} + +std::pair<AActor*, float> ObjectPicker::CheckAActorRay(Ray ray) { + AActor* hitActor = nullptr; + float hitDistance = FLT_MAX; + + for (auto actor : gWorldInstance.Actors) { + if ((actor->bPendingDestroy) && (!actor->IsMod())) { + continue; + } + + float boundingBox = actor->BoundingBoxSize; + if (boundingBox == 0.0f) { + boundingBox = 2.0f; + } + + if (actor->Triangles.size()) { + for (const auto& tri : actor->Triangles) { + float t; + if (IntersectRayTriangleAndTransform(ray, FVector(actor->Pos[0], actor->Pos[1], actor->Pos[2]), tri, t)) { + if (t < hitDistance) { + hitDistance = t; + hitActor = static_cast<AActor*>(actor); + } + } + } + } else { + float max = 1.2f; + float min = -1.2f; + Vec3f boxMin = { actor->Pos[0] + boundingBox * min, + actor->Pos[1] + boundingBox * min, + actor->Pos[2] + boundingBox * min }; + + Vec3f boxMax = { actor->Pos[0] + boundingBox * max, + actor->Pos[1] + boundingBox * max, + actor->Pos[2] + boundingBox * max }; + float t; + if (QueryCollisionRayActor(&ray.Origin.x, &ray.Direction.x, boxMin, boxMax, &t)) { + if (t < hitDistance) { + hitDistance = t; + hitActor = static_cast<AActor*>(actor); + } + } + } } + + return std::pair(hitActor, hitDistance); } + } diff --git a/src/engine/editor/ObjectPicker.h b/src/engine/editor/ObjectPicker.h index e96bddcbc..0dbdca215 100644 --- a/src/engine/editor/ObjectPicker.h +++ b/src/engine/editor/ObjectPicker.h @@ -5,6 +5,7 @@ #include "Collision.h" #include "Gizmo.h" #include "GameObject.h" +#include "engine/Matrix.h" namespace Editor { class ObjectPicker { @@ -16,13 +17,17 @@ namespace Editor { void Load(); void Tick(); Gizmo eGizmo; - GameObject* _selected; + std::variant<AActor*, OObject*, GameObject*> _selected; private: bool _draw = false; GameObject* _lastSelected; s32 Inverse(MtxF* src, MtxF* dest); void Copy(MtxF* src, MtxF* dest); void Clear(MtxF* mf); + // actor, distance from camera + std::pair<AActor*, float> CheckAActorRay(Ray ray); + std::pair<OObject*, float> CheckOObjectRay(Ray ray); + std::pair<GameObject*, float> CheckEditorObjectRay(Ray ray); bool Debug = false; }; } diff --git a/src/engine/editor/SceneManager.cpp b/src/engine/editor/SceneManager.cpp index ae41f2f1d..bd3414474 100644 --- a/src/engine/editor/SceneManager.cpp +++ b/src/engine/editor/SceneManager.cpp @@ -1,11 +1,12 @@ #include "SceneManager.h" #include "port/Game.h" -#include "CoreMath.h" +#include "engine/CoreMath.h" #include "World.h" #include "GameObject.h" #include <iostream> #include <fstream> +#include <optional> // Must be before json.hpp #include <nlohmann/json.hpp> #include "port/Engine.h" #include <libultraship/src/resource/type/Json.h> @@ -13,6 +14,19 @@ #include <libultraship/src/resource/File.h> #include "port/resource/type/ResourceType.h" +#include "engine/vehicles/Train.h" + +#include "engine/objects/Object.h" +#include "engine/objects/Thwomp.h" +#include "engine/objects/Snowman.h" +#include <iostream> + +extern "C" { +#include "common_structs.h" +#include "actors.h" +#include "actor_types.h" +} + namespace Editor { std::shared_ptr<Ship::Archive> CurrentArchive; @@ -33,45 +47,42 @@ namespace Editor { } data["StaticMeshActors"] = staticMesh; - // nlohmann::json actors; - - // for (const auto& actor : gWorldInstance.Actors) { - // actors.push_back(actor->to_json()); - // } - // data["Actors"] = actors; + nlohmann::json actors; - // nlohmann::json objects; + SaveActors(actors); - // for (const auto& object : gWorldInstance.Objects) { - // objects.push_back(object->to_json()); - // } - // data["Objects"] = objects; + data["Actors"] = actors; try { - auto dat = data.dump(); + auto dat = data.dump(2); std::vector<uint8_t> stringify; stringify.assign(dat.begin(), dat.end()); bool wrote = GameEngine::Instance->context->GetResourceManager()->GetArchiveManager()->WriteFile(CurrentArchive, SceneFile, stringify); if (wrote) { - printf("Successfully wrote scene file!\n Wrote: %s\n", SceneFile.c_str()); + // Tell the cache this needs to be reloaded + auto resource = GameEngine::Instance->context->GetResourceManager()->GetCachedResource(SceneFile); + if (resource) { + resource->Dirty(); + } } else { - printf("Failed to write scene file!\n"); + printf("[SceneManager::SaveLevel] Failed to write scene file!\n"); } } catch (const nlohmann::json::exception& e) { - printf("SceneManager::SaveLevel():\n JSON error during dump: %s\n", e.what()); + printf("[SceneManager::SaveLevel]\n JSON error during dump: %s\n", e.what()); } } else { printf("Could not save scene file, SceneFile or CurrentArchive not set\n"); } } - void LoadLevel(std::shared_ptr<Ship::Archive> archive, Course* course, std::string sceneFile) { - SceneFile = sceneFile; - if (archive && (course != nullptr)) { + /** Do not use gWorldInstance.CurrentCourse during loading! The current track is not guaranteed! **/ + void LoadLevel(Course* course, std::string sceneFile) { + SceneFile = sceneFile; + if ((nullptr != course) && (nullptr != course->RootArchive)) { auto initData = std::make_shared<Ship::ResourceInitData>(); - initData->Parent = archive; + initData->Parent = course->RootArchive; initData->Format = RESOURCE_FORMAT_BINARY; initData->ByteOrder = Ship::Endianness::Little; initData->Type = static_cast<uint32_t>(Ship::ResourceType::Json); @@ -97,6 +108,20 @@ namespace Editor { std::cerr << "Props data not found in the JSON file!" << std::endl; } + /** Populate Track SpawnParams for spawning actors **/ + if (data.contains("Actors")) { + auto & actorsJson = data["Actors"]; + course->SpawnList.clear(); + for (const auto& actor : actorsJson) { + SpawnParams params; + params.from_json(actor); //<SpawnParams>(); + if (!params.Name.empty()) { + course->SpawnList.push_back(params); + } + } + SPDLOG_INFO("[SceneManager] Loaded Scene File!"); + } + // Load the Actors (deserialize them) if (data.contains("StaticMeshActors")) { auto& actorsJson = data["StaticMeshActors"]; @@ -106,7 +131,7 @@ namespace Editor { Load_AddStaticMeshActor(actorJson); } } else { - std::cerr << "Actors data not found in the JSON file!" << std::endl; + SPDLOG_INFO("[SceneManager::LoadLevel] [scene.json] This track contains no StaticMeshActors!"); } } } @@ -118,8 +143,6 @@ namespace Editor { printf("After from_json: Pos(%f, %f, %f), Name: %s, Model: %s\n", actor->Pos.x, actor->Pos.y, actor->Pos.z, actor->Name.c_str(), actor->Model.c_str()); - gEditor.AddObject(actor->Name.c_str(), &actor->Pos, &actor->Rot, &actor->Scale, (Gfx*) nullptr, 1.0f, - GameObject::CollisionType::BOUNDING_BOX, 20.0f, (int32_t*) &actor->bPendingDestroy, (int32_t) 1); } void SetSceneFile(std::shared_ptr<Ship::Archive> archive, std::string sceneFile) { @@ -127,11 +150,11 @@ namespace Editor { SceneFile = sceneFile; } - void LoadMinimap(std::shared_ptr<Ship::Archive> archive, Course* course, std::string filePath) { + void LoadMinimap(Course* course, std::string filePath) { printf("LOADING MINIMAP %s\n", filePath.c_str()); - if (archive) { + if ((nullptr != course) && (nullptr != course->RootArchive)) { auto initData = std::make_shared<Ship::ResourceInitData>(); - initData->Parent = archive; + initData->Parent = course->RootArchive; initData->Format = RESOURCE_FORMAT_BINARY; initData->ByteOrder = Ship::Endianness::Little; initData->Type = static_cast<uint32_t>(MK64::ResourceType::Minimap); @@ -153,4 +176,71 @@ namespace Editor { } } } + + void SaveActors(nlohmann::json& actorList) { + for (const auto& actor : gWorldInstance.Actors) { + SpawnParams params{}; + bool alreadyProcessed = false; + + // Only some actors are supported for saving. + // Bananas and stuff don't make sense to be saved. + switch(actor->Type) { + case ACTOR_ITEM_BOX: + case ACTOR_FAKE_ITEM_BOX: + case ACTOR_TREE_MARIO_RACEWAY: + case ACTOR_TREE_YOSHI_VALLEY: + case ACTOR_TREE_ROYAL_RACEWAY: + case ACTOR_TREE_MOO_MOO_FARM: + case ACTOR_PALM_TREE: + case ACTOR_TREE_LUIGI_RACEWAY: // A plant? + case ACTOR_UNKNOWN_0x1B: + case ACTOR_TREE_PEACH_CASTLE: + case ACTOR_TREE_FRAPPE_SNOWLAND: + case ACTOR_CACTUS1_KALAMARI_DESERT: + case ACTOR_CACTUS2_KALAMARI_DESERT: + case ACTOR_CACTUS3_KALAMARI_DESERT: + case ACTOR_BUSH_BOWSERS_CASTLE: + params.Name = get_actor_resource_location_name(actor->Type); + params.Location = FVector(actor->Pos[0], actor->Pos[1], actor->Pos[2]); + if (!params.Name.empty()) { + actorList.push_back(params.to_json()); + } + alreadyProcessed = true; + break; + case ACTOR_PIRANHA_PLANT: + params.Name = get_actor_resource_location_name(actor->Type); + params.Location = FVector(actor->Pos[0], actor->Pos[1], actor->Pos[2]); + // params.Type = // Need this to use royal raceway version + actorList.push_back(params.to_json()); + alreadyProcessed = true; + break; + case ACTOR_YOSHI_EGG: + params.Name = get_actor_resource_location_name(actor->Type); + params.Location = FVector(actor->Velocity[0], actor->Pos[1], actor->Velocity[2]); // Velocity is pathCenter + if (!params.Name.empty()) { + actorList.push_back(params.to_json()); + } + alreadyProcessed = true; + break; + } + + if (!alreadyProcessed) { + actor->SetSpawnParams(params); + if (!params.Name.empty()) { + actorList.push_back(params.to_json()); + } + } + } + + for (const auto& object : gWorldInstance.Objects) { + SpawnParams params; + object->SetSpawnParams(params); + + // Unimplemented objects should not be added to the SpawnList + // The name field is required. If not set, then its not implemented yet. + if (!params.Name.empty()) { + actorList.push_back(params.to_json()); + } + } + } } diff --git a/src/engine/editor/SceneManager.h b/src/engine/editor/SceneManager.h index 58699107e..6a6644a71 100644 --- a/src/engine/editor/SceneManager.h +++ b/src/engine/editor/SceneManager.h @@ -1,13 +1,21 @@ +#pragma once + #include <libultraship/libultraship.h> +#include "CoreMath.h" #include "engine/courses/Course.h" +#include <optional> +#include <nlohmann/json.hpp> namespace Editor { - void SaveLevel(); - void LoadLevel(std::shared_ptr<Ship::Archive> archive, Course* course, std::string sceneFile); - void Load_AddStaticMeshActor(const nlohmann::json& actorJson); - void SetSceneFile(std::shared_ptr<Ship::Archive> archive, std::string sceneFile); - void LoadMinimap(std::shared_ptr<Ship::Archive> archive, Course* course, std::string filePath); - - extern std::shared_ptr<Ship::Archive> CurrentArchive; // This is used to retrieve and write the scene data file - extern std::string SceneFile; + void SaveLevel(); + void LoadLevel(Course* course, std::string sceneFile); + void Load_AddStaticMeshActor(const nlohmann::json& actorJson); + void SetSceneFile(std::shared_ptr<Ship::Archive> archive, std::string sceneFile); + void LoadMinimap(Course* course, std::string filePath); + + void SaveActors(nlohmann::json& actorList); + void SpawnActors(std::vector<std::pair<std::string, SpawnParams>> spawnList); + + extern std::shared_ptr<Ship::Archive> CurrentArchive; // This is used to retrieve and write the scene data file + extern std::string SceneFile; } diff --git a/src/engine/objects/Bat.cpp b/src/engine/objects/Bat.cpp index e4f3a2086..25c28c751 100644 --- a/src/engine/objects/Bat.cpp +++ b/src/engine/objects/Bat.cpp @@ -19,8 +19,13 @@ const char* sBoardwalkTexList[] = { gTextureBat1, gTextureBat2, gTextureBat3, gT size_t OBat::_count = 0; -OBat::OBat(const FVector& pos, const IRotator& rot) { +OBat::OBat(const SpawnParams& params) : OObject(params) { Name = "Bat"; + ResourceName = "mk:bat"; + + //! @warning this likely needs to be rot.Set() + IRotator rot = params.Rotation.value_or(IRotator(0, 0, 0)); + find_unused_obj_index(&_objectIndex); init_texture_object(_objectIndex, (uint8_t*) d_course_banshee_boardwalk_bat_tlut, sBoardwalkTexList, 0x20U, diff --git a/src/engine/objects/Bat.h b/src/engine/objects/Bat.h index d6b7e8774..75318e5d8 100644 --- a/src/engine/objects/Bat.h +++ b/src/engine/objects/Bat.h @@ -28,7 +28,18 @@ extern "C" { */ class OBat : public OObject { public: - explicit OBat(const FVector& pos, const IRotator& rot); + + // This is simply a helper function to keep Spawning code clean + static inline OBat* Spawn(const FVector& pos, const IRotator& rot) { + SpawnParams params = { + .Name = "mk:bat", + .Location = pos, + .Rotation = rot, + }; + return static_cast<OBat*>(gWorldInstance.AddObject(new OBat(params))); + } + + explicit OBat(const SpawnParams& params); ~OBat() { _count--; diff --git a/src/engine/objects/BombKart.cpp b/src/engine/objects/BombKart.cpp index 78d4762a9..c465aea43 100644 --- a/src/engine/objects/BombKart.cpp +++ b/src/engine/objects/BombKart.cpp @@ -29,52 +29,61 @@ extern s8 gPlayerCount; size_t OBombKart::_count = 0; -OBombKart::OBombKart(FVector pos, TrackPathPoint* waypoint, uint16_t waypointIndex, uint16_t state, f32 unk_3C) { +OBombKart::OBombKart(const SpawnParams& params) : OObject(params) { Name = "Bomb Kart"; + ResourceName = "mk:bomb_kart"; + _idx = _count; - Vec3f _pos = {0, 0, 0}; + uint32_t pathIndex = params.PathIndex.value_or(0); + uint32_t pathPoint = params.PathPoint.value_or(0); + FVector constPos; - if (waypoint) { // Spawn kart on waypoint - _pos[0] = waypoint->posX; - _pos[1] = waypoint->posY; - _pos[2] = waypoint->posZ; - } else { // Spawn kart on a surface with the provided position + // Spawn kart on a surface with the provided position + if (params.Location.has_value()) { + constPos = params.Location.value(); // Set height to the default value of 2000.0f unless Pos[1] is higher. // This allows placing these on very high surfaces. - f32 height = (pos.y > 2000.0f) ? pos.y : 2000.0f; - _pos[0] = pos.x; - _pos[1] = spawn_actor_on_surface(pos.x, height, pos.z); - _pos[2] = pos.z; + f32 height = (constPos.y > 2000.0f) ? constPos.y : 2000.0f; + constPos.y = spawn_actor_on_surface(constPos.x, height, constPos.z); + } else { // Spawn kart on waypoint + constPos.x = gTrackPaths[pathIndex][pathPoint].x; + constPos.y = gTrackPaths[pathIndex][pathPoint].y; + constPos.z = gTrackPaths[pathIndex][pathPoint].z; } - WaypointIndex = waypointIndex; - Unk_3C = unk_3C; - State = static_cast<States>(state); - - Pos[0] = _pos[0]; - Pos[1] = _pos[1]; - Pos[2] = _pos[2]; - _spawnPos[0] = _pos[0]; - _spawnPos[1] = _pos[1]; - _spawnPos[2] = _pos[2]; - CenterY = _pos[1]; - WheelPos[0][0] = _pos[0]; - WheelPos[0][1] = _pos[1]; - WheelPos[0][2] = _pos[2]; - WheelPos[1][0] = _pos[0]; - WheelPos[1][1] = _pos[1]; - WheelPos[1][2] = _pos[2]; - WheelPos[2][0] = _pos[0]; - WheelPos[2][1] = _pos[1]; - WheelPos[2][2] = _pos[2]; - WheelPos[3][0] = _pos[0]; - WheelPos[3][1] = _pos[1]; - WheelPos[3][2] = _pos[2]; - check_bounding_collision(&_Collision, 2.0f, _pos[0], _pos[1], _pos[2]); + Behaviour = static_cast<OBombKart::States>(params.Behaviour.value_or(OBombKart::States::COUNTERCLOCKWISE)); + SpeedB = params.SpeedB.value_or(2.7f); // Chase speed + + WaypointIndex = params.PathPoint.value_or(0); + Unk_3C = params.Speed.value_or(0); + + Pos[0] = constPos.x; + Pos[1] = constPos.y; + Pos[2] = constPos.z; + CenterY = constPos.y; + WheelPos[0][0] = constPos.x; + WheelPos[0][1] = constPos.y; + WheelPos[0][2] = constPos.z; + WheelPos[1][0] = constPos.x; + WheelPos[1][1] = constPos.y; + WheelPos[1][2] = constPos.z; + WheelPos[2][0] = constPos.x; + WheelPos[2][1] = constPos.y; + WheelPos[2][2] = constPos.z; + WheelPos[3][0] = constPos.x; + WheelPos[3][1] = constPos.y; + WheelPos[3][2] = constPos.z; + check_bounding_collision(&_Collision, 2.0f, constPos.x, constPos.y, constPos.z); find_unused_obj_index(&_objectIndex); + Object* object = &gObjectList[_objectIndex]; + + object->origin_pos[0] = Pos[0]; + object->origin_pos[1] = Pos[1]; + object->origin_pos[2] = Pos[2]; + _count++; } @@ -103,7 +112,7 @@ void OBombKart::Tick() { f32 sp94; f32 sp88; Vec3f newPos; - States state; + OBombKart::States state; u16 bounceTimer; UNUSED u16 sp4C; u16 temp_t6; @@ -112,7 +121,7 @@ void OBombKart::Tick() { TrackPathPoint* temp_v0_4; Player* player; - state = State; + state = Behaviour; if (state == States::DISABLED) { return; @@ -137,6 +146,7 @@ void OBombKart::Tick() { if ((((temp_f0 * temp_f0) + (temp_f2 * temp_f2)) + (temp_f12 * temp_f12)) < 25.0f) { circleTimer = 0; state = States::EXPLODE; + Behaviour = States::EXPLODE; player->soundEffects |= 0x400000; player->type &= ~0x2000; } @@ -151,6 +161,7 @@ void OBombKart::Tick() { temp_f12 = newPos[2] - player->pos[2]; if ((((temp_f0 * temp_f0) + (temp_f2 * temp_f2)) + (temp_f12 * temp_f12)) < 25.0f) { state = States::EXPLODE; + Behaviour = States::EXPLODE; circleTimer = 0; if (IsFrappeSnowland()) { player->soundEffects |= 0x01000000; @@ -163,44 +174,44 @@ void OBombKart::Tick() { } } switch(state) { - case States::CCW: + case States::COUNTERCLOCKWISE: circleTimer = (circleTimer + 356) % 360; temp_t6 = (circleTimer * 0xFFFF) / 360; sp118 = coss(temp_t6) * 25.0; temp_f0_3 = sins(temp_t6) * 25.0; temp_v0_2 = &gTrackPaths[0][waypoint]; - newPos[0] = temp_v0_2->posX + sp118; + newPos[0] = temp_v0_2->x + sp118; newPos[1] = CenterY + 3.5f; - newPos[2] = temp_v0_2->posZ + temp_f0_3; + newPos[2] = temp_v0_2->z + temp_f0_3; D_80162FB0[0] = newPos[0]; D_80162FB0[1] = newPos[1]; D_80162FB0[2] = newPos[2]; temp_t7 = (((circleTimer + 1) % 360) * 0xFFFF) / 360; sp118 = coss(temp_t7) * 25.0; temp_f0_3 = sins(temp_t7) * 25.0; - D_80162FC0[0] = temp_v0_2->posX + sp118; - D_80162FC0[1] = temp_v0_2->posY; - D_80162FC0[2] = temp_v0_2->posZ + temp_f0_3; + D_80162FC0[0] = temp_v0_2->x + sp118; + D_80162FC0[1] = temp_v0_2->y; + D_80162FC0[2] = temp_v0_2->z + temp_f0_3; someRot = (get_angle_between_two_vectors(D_80162FB0, D_80162FC0) * 0xFFFF) / 65520; break; - case States::CW: + case States::CLOCKWISE: circleTimer = (circleTimer + 4) % 360; temp_t6 = (circleTimer * 0xFFFF) / 360; sp118 = coss(temp_t6) * 25.0; temp_f0_3 = sins(temp_t6) * 25.0; temp_v0_2 = &gTrackPaths[0][waypoint]; - newPos[0] = temp_v0_2->posX + sp118; + newPos[0] = temp_v0_2->x + sp118; newPos[1] = CenterY + 3.5f; - newPos[2] = temp_v0_2->posZ + temp_f0_3; + newPos[2] = temp_v0_2->z + temp_f0_3; D_80162FB0[0] = newPos[0]; D_80162FB0[1] = newPos[1]; D_80162FB0[2] = newPos[2]; temp_t7 = (((circleTimer + 1) % 360) * 0xFFFF) / 360; sp118 = coss(temp_t7) * 25.0; temp_f0_3 = sins(temp_t7) * 25.0; - D_80162FC0[0] = temp_v0_2->posX + sp118; - D_80162FC0[1] = temp_v0_2->posY; - D_80162FC0[2] = temp_v0_2->posZ + temp_f0_3; + D_80162FC0[0] = temp_v0_2->x + sp118; + D_80162FC0[1] = temp_v0_2->y; + D_80162FC0[2] = temp_v0_2->z + temp_f0_3; someRot = (get_angle_between_two_vectors(D_80162FB0, D_80162FC0) * 0xFFFF) / 65520; break; case States::STATIONARY: @@ -218,13 +229,13 @@ void OBombKart::Tick() { } if (((s32) waypoint) < 0x1A) { temp_v0_2 = &gTrackPaths[3][(waypoint + 1) % gPathCountByPathIndex[3]]; - D_80162FB0[0] = temp_v0_2->posX; - D_80162FB0[1] = temp_v0_2->posY; - D_80162FB0[2] = temp_v0_2->posZ; + D_80162FB0[0] = temp_v0_2->x; + D_80162FB0[1] = temp_v0_2->y; + D_80162FB0[2] = temp_v0_2->z; temp_v0_4 = &gTrackPaths[3][(waypoint + 2) % gPathCountByPathIndex[3]]; - D_80162FC0[0] = temp_v0_4->posX; - D_80162FC0[1] = temp_v0_4->posY; - D_80162FC0[2] = temp_v0_4->posZ; + D_80162FC0[0] = temp_v0_4->x; + D_80162FC0[1] = temp_v0_4->y; + D_80162FC0[2] = temp_v0_4->z; someRot = (get_angle_between_two_vectors(D_80162FB0, D_80162FC0) * 0xFFFF) / 65520; } else { D_80162FB0[0] = newPos[0]; @@ -266,13 +277,13 @@ void OBombKart::Tick() { break; case States::EXPLODE: temp_v0_2 = &gTrackPaths[0][waypoint]; - D_80162FB0[0] = temp_v0_2->posX; - D_80162FB0[1] = temp_v0_2->posY; - D_80162FB0[2] = temp_v0_2->posZ; + D_80162FB0[0] = temp_v0_2->x; + D_80162FB0[1] = temp_v0_2->y; + D_80162FB0[2] = temp_v0_2->z; temp_v0_4 = &gTrackPaths[0][(waypoint + 1) % gPathCountByPathIndex[0]]; - D_80162FC0[0] = temp_v0_4->posX; - D_80162FC0[1] = temp_v0_4->posY; - D_80162FC0[2] = temp_v0_4->posZ; + D_80162FC0[0] = temp_v0_4->x; + D_80162FC0[1] = temp_v0_4->y; + D_80162FC0[2] = temp_v0_4->z; newPos[1] += 3.0f - (circleTimer * 0.3f); someRot = (get_angle_between_two_vectors(D_80162FB0, D_80162FC0) * 0xFFFF) / 65520; break; @@ -290,8 +301,9 @@ void OBombKart::Tick() { spA0 = temp_f2_4; sp94 = temp_f2_4; sp88 = temp_f2_4; - if (circleTimer >= 31) { + if (circleTimer > 30) { state = States::DISABLED; + Behaviour = States::DISABLED; } } else { sp118 = coss(0xFFFF - someRot) * 1.5f; @@ -326,7 +338,7 @@ void OBombKart::Tick() { WaypointIndex = waypoint; Unk_3C = unk_3C; SomeRot = someRot; - State = state; + // State = state; BounceTimer = bounceTimer; CircleTimer = circleTimer; } @@ -373,8 +385,8 @@ void OBombKart::Draw(s32 cameraId) { } // huh??? - s32 state = State; - if (State != States::DISABLED) { + OBombKart::States state = Behaviour; + if (state != States::DISABLED) { gObjectList[_objectIndex].pos[0] = Pos[0]; gObjectList[_objectIndex].pos[1] = Pos[1]; gObjectList[_objectIndex].pos[2] = Pos[2]; @@ -386,7 +398,7 @@ void OBombKart::Draw(s32 cameraId) { D_80183E80[2] = 0x8000; func_800563DC(_objectIndex, cameraId, 0x000000FF); OBombKart::SomeRender(camera->pos); - if (((u32) temp_s4 < 0x4E21U) && (state != BOMB_STATE_EXPLODED)) { + if (((u32) temp_s4 < 0x4E21U) && (state != OBombKart::States::EXPLODE)) { OBombKart::LoadMtx(); } } @@ -436,7 +448,9 @@ void OBombKart::Waypoint(s32 screenId) { playerWaypoint = gNearestPathPointByPlayerId[screenId]; playerHUD[screenId].unk_74 = 0; - if ((State == States::EXPLODE) || (State == States::DISABLED)) { return; }; + + OBombKart::States state = Behaviour; + if ((state == States::EXPLODE) || (state == States::DISABLED)) { return; }; bombWaypoint = WaypointIndex; waypointDiff = bombWaypoint - playerWaypoint; if ((waypointDiff < -5) || (waypointDiff > 0x1E)) { return; }; @@ -455,7 +469,7 @@ Player* OBombKart::FindTarget() { } void OBombKart::Chase(Player* player, Vec3f pos) { - const f32 speed = 2.7f; // Speed the kart uses in a chase + const f32 speed = SpeedB; // Speed the kart uses in a chase if (!player) return; // Ensure player is valid @@ -471,18 +485,18 @@ void OBombKart::Chase(Player* player, Vec3f pos) { // Reset distance if (xz_dist > 700.0f) { _target = NULL; - pos[0] = _spawnPos[0]; - pos[1] = _spawnPos[1]; - pos[2] = _spawnPos[2]; + pos[0] = gObjectList[_objectIndex].origin_pos[0]; + pos[1] = gObjectList[_objectIndex].origin_pos[1]; + pos[2] = gObjectList[_objectIndex].origin_pos[2]; return; } // Break off the chase if player has boo item if (player->effects & BOO_EFFECT) { _target = NULL; - pos[0] = _spawnPos[0]; - pos[1] = _spawnPos[1]; - pos[2] = _spawnPos[2]; + pos[0] = gObjectList[_objectIndex].origin_pos[0]; + pos[1] = gObjectList[_objectIndex].origin_pos[1]; + pos[2] = gObjectList[_objectIndex].origin_pos[2]; return; } @@ -509,4 +523,62 @@ void OBombKart::Chase(Player* player, Vec3f pos) { pos[2] = newPosition[2]; check_bounding_collision(&_Collision, 10.0f, pos[0], pos[1], pos[2]); -}
\ No newline at end of file +} + +void OBombKart::Translate(FVector pos) { + Pos[0] = pos.x; + Pos[1] = pos.y; + Pos[2] = pos.z; + if (_objectIndex != -1) { + Object* object = &gObjectList[_objectIndex]; + object->pos[0] = pos.x; + object->pos[1] = pos.y; + object->pos[2] = pos.z; + object->origin_pos[0] = pos.x; + object->origin_pos[1] = pos.y; + object->origin_pos[2] = pos.z; + } else { + printf("Editor tried to translate null OObject\n"); + } +} + +void OBombKart::DrawEditorProperties() { + Object* obj = &gObjectList[_objectIndex]; + + ImGui::Text("Behaviour"); + ImGui::SameLine(); + + int32_t behaviour = static_cast<int32_t>(Behaviour); + const char* items[] = { "Disabled", "Counterclockwise", "Clockwise", "Stationary", "Chase", "Explode", "Podium" }; + + if (ImGui::Combo("##Behaviour", &behaviour, items, IM_ARRAYSIZE(items))) { + Behaviour = static_cast<OBombKart::States>(behaviour); + } + + ImGui::Text("Location"); + ImGui::SameLine(); + FVector location = FVector(obj->pos[0], obj->pos[1], obj->pos[2]); + if (ImGui::DragFloat3("##Location", (float*)&location)) { + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPos")) { + FVector location = FVector(0, 0, 0); + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::Text("Chase Speed"); + ImGui::SameLine(); + + float speed = SpeedB; + if (ImGui::DragFloat("##Speed", &speed, 0.1f)) { + SpeedB = speed; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeed")) { + SpeedB = 2.7f; + } +} diff --git a/src/engine/objects/BombKart.h b/src/engine/objects/BombKart.h index 86c2c0ef3..20c8392a0 100644 --- a/src/engine/objects/BombKart.h +++ b/src/engine/objects/BombKart.h @@ -26,8 +26,8 @@ class OBombKart : public OObject { public: enum States : uint16_t { // 0,1,3,5 DISABLED, - CCW, - CW, + COUNTERCLOCKWISE, + CLOCKWISE, STATIONARY, CHASE, EXPLODE, @@ -49,8 +49,34 @@ class OBombKart : public OObject { f32 CenterY; // Center of the circle Collision _Collision; + + // This is simply a helper function to keep Spawning code clean + // Spawn object at a position + static inline OBombKart* Spawn(FVector pos, uint16_t behaviour, f32 unk_3C) { + SpawnParams params = { + .Name = "mk:bomb_kart", + .Behaviour = behaviour, + .Location = pos, + .Speed = unk_3C, // Only used for podium ceremony. Arbitrarily chose Speed for this + .SpeedB = 2.7f, // Chase speed + }; + return static_cast<OBombKart*>(gWorldInstance.AddObject(new OBombKart(params))); + } + + // Spawn object at a point along the tracks path + static inline OBombKart* Spawn(uint32_t pathIndex, uint32_t pathPoint, uint16_t behaviour, f32 unk_3C) { + SpawnParams params = { + .Name = "mk:bomb_kart", + .Behaviour = behaviour, + .PathIndex = pathIndex, + .PathPoint = pathPoint, + .Speed = unk_3C, // Only used for podium ceremony. Arbitrarily chose Speed for this + }; + return static_cast<OBombKart*>(gWorldInstance.AddObject(new OBombKart(params))); + } + // Set waypoint to NULL if using a spawn position and not a waypoint. - explicit OBombKart(FVector pos, TrackPathPoint* waypoint, uint16_t waypointIndex, uint16_t state, f32 unk_3C); + explicit OBombKart(const SpawnParams& params); ~OBombKart() { _count--; @@ -62,17 +88,20 @@ class OBombKart : public OObject { virtual void Tick() override; virtual void Draw(s32 cameraId) override; + virtual void Translate(FVector pos) override; + virtual void DrawEditorProperties() override; void DrawBattle(s32 cameraId); void SomeRender(Vec3f arg1); void LoadMtx(); void Waypoint(s32 screenId); + OBombKart::States Behaviour = OBombKart::States::COUNTERCLOCKWISE; + float SpeedB = 2.7f; private: static size_t _count; s32 _idx; Player* FindTarget(); void Chase(Player*, Vec3f pos); - Vec3f _spawnPos; Player* _target = NULL; }; diff --git a/src/engine/objects/Boos.cpp b/src/engine/objects/Boos.cpp index dbd380901..a22b0edd0 100644 --- a/src/engine/objects/Boos.cpp +++ b/src/engine/objects/Boos.cpp @@ -23,12 +23,20 @@ extern "C" { size_t OBoos::_count = 0; -OBoos::OBoos(size_t numBoos, const IPathSpan& leftBoundary, const IPathSpan& active, const IPathSpan& rightBoundary) { +OBoos::OBoos(const SpawnParams& params) : OObject(params) { Name = "Boos"; + ResourceName = "mk:boos"; + + size_t numBoos = params.Count.value_or(5); + + ActiveZone = params.TriggerSpan.value_or(IPathSpan(30, 50)); + LeftTrigger = params.LeftExitSpan.value_or(IPathSpan(0, 10)); + RightTrigger = params.RightExitSpan.value_or(IPathSpan(80, 100)); + // Max five boos allowed due to limited splines // D_800E5D9C if (numBoos > 10) { - printf("Boos.cpp: Only 10 boos allowed.\n"); + printf("[Boos.cpp] Only 10 boos allowed.\n"); numBoos = 10; } @@ -40,9 +48,14 @@ OBoos::OBoos(size_t numBoos, const IPathSpan& leftBoundary, const IPathSpan& act } _numBoos = numBoos; - _leftBoundary = leftBoundary; - _active = active; - _rightBoundary = rightBoundary; +} + +void OBoos::SetSpawnParams(SpawnParams& params) { + OObject::SetSpawnParams(params); + params.Count = _numBoos; + params.LeftExitSpan = LeftTrigger; + params.TriggerSpan = ActiveZone; + params.RightExitSpan = RightTrigger; } void OBoos::Tick() { @@ -123,7 +136,8 @@ void OBoos::func_8007CA70(void) { if (_isActive == false) { _playerId = OBoos::func_8007C9F8(); point = &gNearestPathPointByPlayerId[_playerId]; - if ((*point > _active.Start) && (*point < _active.End)) { + + if ((*point > ActiveZone.Start) && (*point < ActiveZone.End)) { // First group entrance OBoos::BooStart(0, _playerId); } @@ -131,11 +145,14 @@ void OBoos::func_8007CA70(void) { if (_isActive == true) { point = &gNearestPathPointByPlayerId[_playerId]; - if ((*point > _leftBoundary.Start) && (*point < _leftBoundary.End)) { + // Left boundary + if ((*point > LeftTrigger.Start) && (*point < LeftTrigger.End)) { // First group exit reverse direction OBoos::BooExit(0); } - if ((*point > _rightBoundary.Start) && (*point < _rightBoundary.End)) { + + // Right boundary + if ((*point > RightTrigger.Start) && (*point < RightTrigger.End)) { // First group exit OBoos::BooExit(0); } @@ -274,3 +291,50 @@ void OBoos::BooExit(s32 group) { _isActive = false; } + +void OBoos::DrawEditorProperties() { + ImGui::Text("Num Boos"); + ImGui::SameLine(); + + int count = static_cast<int>(_count); + if (ImGui::InputInt("##Count", &count)) { + // Clamp to uint32_t range (only lower bound needed if assuming positive values) + if (count < 0) count = 0; + if (count > 10) count = 10; + _count = static_cast<uint32_t>(count); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetCount")) { + _count = 5; + } + + ImGui::Text("Left Exit Span"); + ImGui::SameLine(); + + if (ImGui::DragInt2("##LeftExitSpan", (int*)&LeftTrigger)) { + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetLeftExitSpan")) { + LeftTrigger = IPathSpan(0, 0); + } + + ImGui::Text("Trigger Span"); + ImGui::SameLine(); + + if (ImGui::DragInt2("##TriggerSpan", (int*)&ActiveZone)) { + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetTriggerSpan")) { + ActiveZone = IPathSpan(0, 0); + } + + ImGui::Text("Right Exit Span"); + ImGui::SameLine(); + + if (ImGui::DragInt2("##RightExitSpan", (int*)&RightTrigger)) { + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetRightExitSpan")) { + RightTrigger = IPathSpan(0, 0); + } +} diff --git a/src/engine/objects/Boos.h b/src/engine/objects/Boos.h index cc49df474..693405dcb 100644 --- a/src/engine/objects/Boos.h +++ b/src/engine/objects/Boos.h @@ -36,7 +36,19 @@ extern "C" { */ class OBoos : public OObject { public: - explicit OBoos(size_t numBoos, const IPathSpan& leftBoundary, const IPathSpan& active, const IPathSpan& rightBoundary); + // This is simply a helper function to keep Spawning code clean + static inline OBoos* Spawn(size_t numBoos, const IPathSpan& leftBoundary, const IPathSpan& triggerBoundary, const IPathSpan& rightBoundary) { + SpawnParams params = { + .Name = "mk:boos", + .Count = numBoos, + .LeftExitSpan = leftBoundary, + .TriggerSpan = triggerBoundary, + .RightExitSpan = rightBoundary, + }; + return static_cast<OBoos*>(gWorldInstance.AddObject(new OBoos(params))); + } + + explicit OBoos(const SpawnParams& params); ~OBoos() { _count--; @@ -46,8 +58,10 @@ public: return _count; } + virtual void SetSpawnParams(SpawnParams& params) override; virtual void Tick() override; virtual void Draw(s32 cameraId) override; + virtual void DrawEditorProperties() override; void func_800523B8(s32 objectIndex, s32 arg1, u32 arg2); void func_8007CA70(void); @@ -59,6 +73,9 @@ public: void BooExit(s32 someIndex); void func_8007C550(s32 objectIndex); + IPathSpan LeftTrigger; + IPathSpan ActiveZone; + IPathSpan RightTrigger; private: FVector _pos; static size_t _count; @@ -68,8 +85,4 @@ private: bool _isActive = false; s32 _playerId = 0; - - IPathSpan _leftBoundary; - IPathSpan _active; - IPathSpan _rightBoundary; }; diff --git a/src/engine/objects/ChainChomp.cpp b/src/engine/objects/ChainChomp.cpp index 0d7ec6bc9..fd3faa622 100644 --- a/src/engine/objects/ChainChomp.cpp +++ b/src/engine/objects/ChainChomp.cpp @@ -18,6 +18,7 @@ size_t OChainChomp::_count = 0; OChainChomp::OChainChomp() { Name = "Chain Chomp"; + ResourceName = "mk:chain_chomp"; _idx = _count; init_object(indexObjectList2[_count], 0); _objectIndex = indexObjectList2[_count]; @@ -92,7 +93,7 @@ void OChainChomp::func_80085878(s32 objectIndex, s32 arg1) { object->unk_084[8] = (arg1 * 0x12C) + 0x1F4; set_obj_origin_pos(objectIndex, 0.0f, -15.0f, 0.0f); temp_v0 = &gCurrentTrackPath[(u16) object->unk_084[8]]; - set_obj_origin_offset(objectIndex, temp_v0->posX, temp_v0->posY, temp_v0->posZ); + set_obj_origin_offset(objectIndex, temp_v0->x, temp_v0->y, temp_v0->z); set_obj_direction_angle(objectIndex, 0U, 0U, 0U); object->unk_034 = 4.0f; object->type = get_animation_length(d_rainbow_road_unk3, 0); diff --git a/src/engine/objects/CheepCheep.cpp b/src/engine/objects/CheepCheep.cpp index ed489dc88..a2432f57e 100644 --- a/src/engine/objects/CheepCheep.cpp +++ b/src/engine/objects/CheepCheep.cpp @@ -1,8 +1,10 @@ #include "CheepCheep.h" +#include "port/Game.h" #include "assets/banshee_boardwalk_data.h" #include "assets/common_data.h" + extern "C" { #include "math_util.h" #include "math_util_2.h" @@ -17,17 +19,22 @@ extern Vec3s D_800E634C[]; extern Lights1 D_800E45C0[]; } -OCheepCheep::OCheepCheep(const FVector& pos, CheepType type, IPathSpan span) { +OCheepCheep::OCheepCheep(const SpawnParams& params) : OObject(params) { Name = "Cheep Cheep"; - _type = type; - _spawnPos = pos; - _span = span; + ResourceName = "mk:cheep_cheep"; + _behaviour = static_cast<Behaviour>(params.Behaviour.value_or(0)); +} + +void OCheepCheep::SetSpawnParams(SpawnParams& params) { + OObject::SetSpawnParams(params); + params.Behaviour = static_cast<int16_t>(_behaviour); + params.PathSpan = ActivationPoints; } void OCheepCheep::Tick() { // update_cheep_cheep s32 objectIndex; - switch (_type) { - case CheepType::RACE: + switch (_behaviour) { + case Behaviour::RACE: UNUSED s32 pad; OCheepCheep::func_8007BD04(0); @@ -35,7 +42,7 @@ void OCheepCheep::Tick() { // update_cheep_cheep OCheepCheep::func_8007BBBC(objectIndex); object_calculate_new_pos_offset(objectIndex); break; - case CheepType::PODIUM_CEREMONY: + case Behaviour::PODIUM_CEREMONY: objectIndex = indexObjectList2[0]; if (D_801658BC == 1) { D_801658BC = 0; @@ -115,9 +122,11 @@ void OCheepCheep::func_8007BD04(s32 playerId) { objectIndex = indexObjectList2[0]; if (gObjectList[objectIndex].state == 0) { - if (((s32) gNearestPathPointByPlayerId[playerId] >= _span.Start) && - ((s32) gNearestPathPointByPlayerId[playerId] <= _span.End)) { - set_obj_origin_pos(objectIndex, xOrientation * _spawnPos.x, _spawnPos.y, _spawnPos.z); + IPathSpan span = ActivationPoints; + if (((s32) gNearestPathPointByPlayerId[playerId] >= span.Start) && + ((s32) gNearestPathPointByPlayerId[playerId] <= span.End)) { + FVector pos = SpawnPos; + set_obj_origin_pos(objectIndex, xOrientation * pos.x, pos.y, pos.z); init_object(objectIndex, 1); } } @@ -242,3 +251,35 @@ void OCheepCheep::func_8007BFB0(s32 objectIndex) { object_add_velocity_offset_y(objectIndex); object_calculate_new_pos_offset(objectIndex); } + +void OCheepCheep::DrawEditorProperties() { + Object* obj = &gObjectList[_objectIndex]; + + ImGui::Text("Location"); + ImGui::SameLine(); + FVector location = FVector(obj->pos[0], obj->pos[1], obj->pos[2]); + if (ImGui::DragFloat3("##Location", (float*)&location)) { + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPos")) { + FVector location = FVector(0, 0, 0); + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + IPathSpan span = ActivationPoints; + + ImGui::Text("Path Span"); + ImGui::SameLine(); + + if (ImGui::DragInt2("##PathSpan", (int*)&span)) { + ActivationPoints = span; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathSpan")) { + ActivationPoints = IPathSpan(0.0f, 0.0f); + } +} diff --git a/src/engine/objects/CheepCheep.h b/src/engine/objects/CheepCheep.h index 32e9a5aeb..4cbe8bd60 100644 --- a/src/engine/objects/CheepCheep.h +++ b/src/engine/objects/CheepCheep.h @@ -3,6 +3,7 @@ #include <libultraship.h> #include <vector> #include "Object.h" +#include "engine/CoreMath.h" #include "World.h" @@ -18,20 +19,30 @@ extern "C" { class OCheepCheep : public OObject { public: - enum CheepType { + enum class Behaviour : int16_t { RACE, PODIUM_CEREMONY }; - enum Behaviour : uint16_t { - }; + // This is simply a helper function to keep Spawning code clean + static inline OCheepCheep* Spawn(const FVector& pos, Behaviour behaviour, IPathSpan span) { + SpawnParams params = { + .Name = "mk:cheep_cheep", + .Behaviour = static_cast<int16_t>(behaviour), + .Location = pos, + .PathSpan = span, + }; + return static_cast<OCheepCheep*>(gWorldInstance.AddObject(new OCheepCheep(params))); + } -public: + explicit OCheepCheep(const SpawnParams& params); - explicit OCheepCheep(const FVector& pos, CheepType type, IPathSpan span); + IPathSpan ActivationPoints; // Path points activation points + virtual void SetSpawnParams(SpawnParams& params) override; virtual void Tick() override; virtual void Draw(s32 cameraId) override; + virtual void DrawEditorProperties() override; void func_8007BBBC(s32 objectIndex); void func_8007BD04(s32 playerId); void init_var_cheep_cheep(s32 objectIndex); @@ -41,8 +52,5 @@ public: private: s32 _idx; - CheepType _type; - FVector _spawnPos; - IPathSpan _span; - + Behaviour _behaviour; }; diff --git a/src/engine/objects/Crab.cpp b/src/engine/objects/Crab.cpp index 98c781b5a..9e4e4687b 100644 --- a/src/engine/objects/Crab.cpp +++ b/src/engine/objects/Crab.cpp @@ -26,24 +26,31 @@ extern "C" { size_t OCrab::_count = 0; -OCrab::OCrab(const FVector2D& start, const FVector2D& end) { +OCrab::OCrab(const SpawnParams& params) : OObject(params) { Name = "Crab"; + ResourceName = "mk:crab"; _idx = _count; - _start = start; - _end = end; + _start = params.PatrolStart.value_or(FVector2D(0, 0)); + _end = params.PatrolEnd.value_or(FVector2D(0, 0)); find_unused_obj_index(&_objectIndex); init_object(_objectIndex, 0); - gObjectList[_objectIndex].pos[0] = gObjectList[_objectIndex].origin_pos[0] = start.x * xOrientation; - gObjectList[_objectIndex].pos[2] = gObjectList[_objectIndex].origin_pos[2] = start.z; + gObjectList[_objectIndex].pos[0] = gObjectList[_objectIndex].origin_pos[0] = _start.x * xOrientation; + gObjectList[_objectIndex].pos[2] = gObjectList[_objectIndex].origin_pos[2] = _start.z; - gObjectList[_objectIndex].unk_01C[0] = end.x * xOrientation; - gObjectList[_objectIndex].unk_01C[2] = end.z; + gObjectList[_objectIndex].unk_01C[0] = _end.x * xOrientation; + gObjectList[_objectIndex].unk_01C[2] = _end.z; _count++; } +void OCrab::SetSpawnParams(SpawnParams& params) { + params.Name = std::string(ResourceName); + params.PatrolStart = _start; + params.PatrolEnd = _end; +} + void OCrab::Tick(void) { s32 objectIndex = _objectIndex; if (gObjectList[objectIndex].state != 0) { @@ -189,3 +196,29 @@ void OCrab::func_80082E18(s32 objectIndex) { func_80089F24(objectIndex); } } + +void OCrab::DrawEditorProperties() { + ImGui::Text("Start Location"); + ImGui::SameLine(); + + if (ImGui::DragFloat2("##PathSpan", (float*)&_start)) { + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathSpan")) { + _start = FVector2D(0.0f, 0.0f); + gObjectList[_objectIndex].pos[0] = gObjectList[_objectIndex].origin_pos[0] = _start.x * xOrientation; + gObjectList[_objectIndex].pos[2] = gObjectList[_objectIndex].origin_pos[2] = _start.z; + } + + ImGui::Text("Patrol Location"); + ImGui::SameLine(); + + if (ImGui::DragFloat2("##PatrolLoc", (float*)&_end)) { + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPatrolLoc")) { + _end = FVector2D(0.0f, 0.0f); + gObjectList[_objectIndex].unk_01C[0] = _end.x * xOrientation; + gObjectList[_objectIndex].unk_01C[2] = _end.z; + } +} diff --git a/src/engine/objects/Crab.h b/src/engine/objects/Crab.h index 4cadd3b87..d2c694ff1 100644 --- a/src/engine/objects/Crab.h +++ b/src/engine/objects/Crab.h @@ -30,10 +30,23 @@ extern "C" { */ class OCrab : public OObject { public: - explicit OCrab(const FVector2D& start, const FVector2D& end); + // This is simply a helper function to keep Spawning code clean + static inline OCrab* Spawn(const FVector2D& start, const FVector2D& end) { + SpawnParams params = { + .Name = "mk:crab", + .PatrolStart = start, + .PatrolEnd = end, + }; + return static_cast<OCrab*>(gWorldInstance.AddObject(new OCrab(params))); + } + + explicit OCrab(const SpawnParams& params); virtual void Tick() override; virtual void Draw(s32 cameraId) override; + virtual void SetSpawnParams(SpawnParams& params) override; + virtual void DrawEditorProperties() override; + void DrawModel(s32 cameraId); void init_ktb_crab(s32 objectIndex); diff --git a/src/engine/objects/Flagpole.cpp b/src/engine/objects/Flagpole.cpp index acd74ba2b..58581e3c9 100644 --- a/src/engine/objects/Flagpole.cpp +++ b/src/engine/objects/Flagpole.cpp @@ -15,16 +15,24 @@ extern "C" { size_t OFlagpole::_count = 0; -OFlagpole::OFlagpole(const FVector& pos, s16 direction) { +OFlagpole::OFlagpole(const SpawnParams& params) : OObject(params) { Name = "Flagpole"; + ResourceName = "mk:flagpole"; _idx = _count; - _pos = pos; - _direction = direction; find_unused_obj_index(&_objectIndex); - init_object(_objectIndex, 0); + SpawnPos = params.Location.value_or(FVector(0, 0, 0)); + gObjectList[_objectIndex].pos[0] = SpawnPos.x; + gObjectList[_objectIndex].pos[1] = SpawnPos.y; + gObjectList[_objectIndex].pos[2] = SpawnPos.z; + + SpawnRot = params.Rotation.value_or(IRotator(0, 0, 0)); + gObjectList[_objectIndex].orientation[0] = SpawnRot.pitch; + gObjectList[_objectIndex].orientation[1] = SpawnRot.yaw; + gObjectList[_objectIndex].orientation[2] = SpawnRot.roll; + _count++; } @@ -49,7 +57,7 @@ void OFlagpole::Draw(s32 cameraId) { // func_80055228 void OFlagpole::func_80055164(s32 objectIndex) { // func_80055164 if (gObjectList[objectIndex].state >= 2) { gSPDisplayList(gDisplayListHead++, (Gfx*)D_0D0077A0); - rsp_set_matrix_transformation(gObjectList[objectIndex].pos, gObjectList[objectIndex].direction_angle, + rsp_set_matrix_transformation(gObjectList[objectIndex].pos, gObjectList[objectIndex].orientation, gObjectList[objectIndex].sizeScaling); if (gIsGamePaused == 0) { gObjectList[objectIndex].unk_0A2 = render_animated_model((Armature*) gObjectList[objectIndex].model, @@ -67,9 +75,11 @@ void OFlagpole::func_80082F1C(s32 objectIndex) { gObjectList[objectIndex].vertex = (Vtx*) d_course_yoshi_valley_unk4; gObjectList[objectIndex].sizeScaling = 0.027f; object_next_state(objectIndex); - set_obj_origin_pos(objectIndex, _pos.x * xOrientation, _pos.y, _pos.z); + FVector pos = SpawnPos; + set_obj_origin_pos(objectIndex, pos.x * xOrientation, pos.y, pos.z); set_obj_origin_offset(objectIndex, 0.0f, 0.0f, 0.0f); - set_obj_direction_angle(objectIndex, 0U, _direction, 0U); + IRotator rot = SpawnRot; + set_obj_orientation(objectIndex, rot.pitch, rot.yaw, rot.roll); // changed from directional_angle to orientation for editor support } void OFlagpole::func_80083018(s32 objectIndex) { diff --git a/src/engine/objects/Flagpole.h b/src/engine/objects/Flagpole.h index 5824fcf18..db5621a2d 100644 --- a/src/engine/objects/Flagpole.h +++ b/src/engine/objects/Flagpole.h @@ -17,9 +17,23 @@ extern "C" { #include "some_data.h" } +// This used to use directional_angle for rot. It now uses orientation for editor compatibility. +// There doesn't seem to be any reason this actor's behaviour would differ from this class OFlagpole : public OObject { public: - explicit OFlagpole(const FVector& pos, s16 direction); + explicit OFlagpole(const SpawnParams& params); + + // This is simply a helper function to keep Spawning code clean + static inline OFlagpole* Spawn(FVector pos, s16 direction) { + IRotator rot; + rot.Set(0, direction, 0); + SpawnParams params = { + .Name = "mk:flagpole", + .Location = pos, + .Rotation = rot, + }; + return static_cast<OFlagpole*>(gWorldInstance.AddObject(new OFlagpole(params))); + } ~OFlagpole() { _count--; @@ -38,8 +52,6 @@ public: void func_80083060(s32 objectIndex); private: - FVector _pos; - s16 _direction; static size_t _count; size_t _idx; }; diff --git a/src/engine/objects/GrandPrixBalloons.cpp b/src/engine/objects/GrandPrixBalloons.cpp index 1315589f2..05ba880c6 100644 --- a/src/engine/objects/GrandPrixBalloons.cpp +++ b/src/engine/objects/GrandPrixBalloons.cpp @@ -17,8 +17,10 @@ extern "C" { size_t OGrandPrixBalloons::_count = 0; -OGrandPrixBalloons::OGrandPrixBalloons(const FVector& pos) { - Pos = pos; +OGrandPrixBalloons::OGrandPrixBalloons(const SpawnParams& params) : OObject(params) { + Name = "Grand Prix Balloons"; + ResourceName = "mk:grand_prix_balloons"; + Pos = params.Location.value_or(FVector(0, 0, 0)); _active = 1; if (gPlayerCount == 1) { diff --git a/src/engine/objects/GrandPrixBalloons.h b/src/engine/objects/GrandPrixBalloons.h index 93ec04479..04bac0915 100644 --- a/src/engine/objects/GrandPrixBalloons.h +++ b/src/engine/objects/GrandPrixBalloons.h @@ -25,7 +25,16 @@ extern "C" { class OGrandPrixBalloons : public OObject { public: - explicit OGrandPrixBalloons(const FVector& pos); + explicit OGrandPrixBalloons(const SpawnParams& params); + + // This is simply a helper function to keep Spawning code clean + static inline OGrandPrixBalloons* Spawn(const FVector& pos) { + SpawnParams params = { + .Name = "mk:grand_prix_balloons", + .Location = pos, + }; + return static_cast<OGrandPrixBalloons*>(gWorldInstance.AddObject(new OGrandPrixBalloons(params))); + } ~OGrandPrixBalloons() { _count--; diff --git a/src/engine/objects/Hedgehog.cpp b/src/engine/objects/Hedgehog.cpp index 1c2aada9d..a36853a91 100644 --- a/src/engine/objects/Hedgehog.cpp +++ b/src/engine/objects/Hedgehog.cpp @@ -1,5 +1,7 @@ #include "Hedgehog.h" -#include "World.h" +#include "engine/World.h" +#include "port/Game.h" +#include "port/interpolation/FrameInterpolation.h" extern "C" { #include "render_objects.h" @@ -11,60 +13,64 @@ extern "C" { #include "code_80086E70.h" #include "code_80057C60.h" } -#include "port/interpolation/FrameInterpolation.h" size_t OHedgehog::_count = 0; -OHedgehog::OHedgehog(const FVector& pos, const FVector2D& patrolPoint, s16 unk) { +OHedgehog::OHedgehog(const SpawnParams& params) : OObject(params) { Name = "Hedgehog"; + ResourceName = "mk:hedgehog"; _idx = _count; - _pos = pos; - - s32 objectId = indexObjectList2[_idx]; - _objectIndex = objectId; - init_object(objectId, 0); - gObjectList[objectId].pos[0] = gObjectList[objectId].origin_pos[0] = pos.x * xOrientation; - gObjectList[objectId].pos[1] = gObjectList[objectId].surfaceHeight = pos.y + 6.0; - gObjectList[objectId].pos[2] = gObjectList[objectId].origin_pos[2] = pos.z; - gObjectList[objectId].unk_0D5 = (u8) unk; - gObjectList[objectId].unk_09C = patrolPoint.x * xOrientation; - gObjectList[objectId].unk_09E = patrolPoint.z; + SpawnPos = params.Location.value_or(FVector(0, 0, 0)); + PatrolEnd = params.PatrolEnd.value_or(FVector2D(0, 0)); + + find_unused_obj_index(&_objectIndex); + + init_object(_objectIndex, 0); + gObjectList[_objectIndex].pos[0] = gObjectList[_objectIndex].origin_pos[0] = SpawnPos.x * xOrientation; + gObjectList[_objectIndex].pos[1] = gObjectList[_objectIndex].surfaceHeight = SpawnPos.y + 6.0; + gObjectList[_objectIndex].pos[2] = gObjectList[_objectIndex].origin_pos[2] = SpawnPos.z; + gObjectList[_objectIndex].unk_0D5 = (u8) params.Behaviour.value_or(9); + gObjectList[_objectIndex].unk_09C = PatrolEnd.x * xOrientation; + gObjectList[_objectIndex].unk_09E = PatrolEnd.z; _count++; } -void OHedgehog::Tick() { - s32 objectIndex = indexObjectList2[_idx]; +void OHedgehog::SetSpawnParams(SpawnParams& params) { + params.Name = std::string(ResourceName); + params.Location = SpawnPos; + params.PatrolEnd = PatrolEnd; +} - OHedgehog::func_800833D0(objectIndex, _idx); - OHedgehog::func_80083248(objectIndex); - OHedgehog::func_80083474(objectIndex); +void OHedgehog::Tick() { + OHedgehog::func_800833D0(_objectIndex, _idx); + OHedgehog::func_80083248(_objectIndex); + OHedgehog::func_80083474(_objectIndex); // This func clears a bit from all hedgehogs. This results in setting the height of all hedgehogs to zero. // The solution is to only clear the bit from the current instance; `self` or `this` // func_80072120(indexObjectList2, NUM_HEDGEHOGS); - clear_object_flag(objectIndex, 0x00600000); // The fix + clear_object_flag(_objectIndex, 0x00600000); // The fix } void OHedgehog::Draw(s32 cameraId) { - s32 objectIndex = indexObjectList2[_idx]; - u32 something = func_8008A364(objectIndex, cameraId, 0x4000U, 0x000003E8); + u32 something = func_8008A364(_objectIndex, cameraId, 0x4000U, 0x000003E8); if (CVarGetInteger("gNoCulling", 0) == 1) { something = MIN(something, 0x52211U - 1); } - if (is_obj_flag_status_active(objectIndex, VISIBLE) != 0) { - set_object_flag(objectIndex, 0x00200000); + if (is_obj_flag_status_active(_objectIndex, VISIBLE) != 0) { + set_object_flag(_objectIndex, 0x00200000); if (something < 0x2711U) { - set_object_flag(objectIndex, 0x00000020); + set_object_flag(_objectIndex, 0x00000020); } else { - clear_object_flag(objectIndex, 0x00000020); + clear_object_flag(_objectIndex, 0x00000020); } if (something < 0x57E41U) { - set_object_flag(objectIndex, 0x00400000); + set_object_flag(_objectIndex, 0x00400000); } if (something < 0x52211U) { - OHedgehog::func_800555BC(objectIndex, cameraId); + OHedgehog::func_800555BC(_objectIndex, cameraId); } } } @@ -113,7 +119,7 @@ void OHedgehog::func_8004A870(s32 objectIndex, f32 arg1) { const char* sHedgehogTexList[] = { d_course_yoshi_valley_hedgehog }; -void OHedgehog::func_8008311C(s32 objectIndex, s32 arg1) { +void OHedgehog::func_8008311C(s32 objectIndex, s32 id) { Object* object; Vtx* vtx = (Vtx*) LOAD_ASSET_RAW(common_vtx_hedgehog); @@ -128,7 +134,7 @@ void OHedgehog::func_8008311C(s32 objectIndex, s32 arg1) { object_next_state(objectIndex); set_obj_origin_offset(objectIndex, 0.0f, 0.0f, 0.0f); set_obj_orientation(objectIndex, 0U, 0U, 0x8000U); - object->unk_034 = ((arg1 % 6) * 0.1) + 0.5; + object->unk_034 = ((id % 6) * 0.1) + 0.5; func_80086E70(objectIndex); set_object_flag(objectIndex, 0x04000600); object->boundingBoxSize = 2; @@ -168,12 +174,12 @@ void OHedgehog::func_80083248(s32 objectIndex) { } } -void OHedgehog::func_800833D0(s32 objectIndex, s32 arg1) { +void OHedgehog::func_800833D0(s32 objectIndex, s32 id) { switch (gObjectList[objectIndex].state) { case 0: break; case 1: - OHedgehog::func_8008311C(objectIndex, arg1); + OHedgehog::func_8008311C(objectIndex, id); break; case 2: func_80072D3C(objectIndex, 0, 1, 4, -1); @@ -193,3 +199,32 @@ void OHedgehog::func_80083474(s32 objectIndex) { func_80089F24(objectIndex); } } + +void OHedgehog::DrawEditorProperties() { + Object* obj = &gObjectList[_objectIndex]; + + ImGui::Text("Location"); + ImGui::SameLine(); + FVector location = FVector(obj->pos[0], obj->pos[1], obj->pos[2]); + if (ImGui::DragFloat3("##Location", (float*)&location)) { + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPos")) { + FVector location = FVector(0, 0, 0); + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::Text("Patrol Location"); + ImGui::SameLine(); + + if (ImGui::DragFloat2("##PatrolLoc", (float*)&PatrolEnd)) { + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPatrolLoc")) { + PatrolEnd = FVector2D(0.0f, 0.0f); + } +}
\ No newline at end of file diff --git a/src/engine/objects/Hedgehog.h b/src/engine/objects/Hedgehog.h index 2057d894d..7a90f9a91 100644 --- a/src/engine/objects/Hedgehog.h +++ b/src/engine/objects/Hedgehog.h @@ -20,11 +20,22 @@ extern "C" { /** * @arg pos FVector xyz spawn position * @arg patrolPoint FVector2D xz patrol to location. Actor automatically calculates the Y value - * @arg unk unknown. Likely actor type. + * @arg behaviour unknown, seems unused. */ class OHedgehog : public OObject { public: - explicit OHedgehog(const FVector& pos, const FVector2D& patrolPoint, s16 unk); + explicit OHedgehog(const SpawnParams& params); + + // This is simply a helper function to keep Spawning code clean + static inline OHedgehog* Spawn(const FVector& pos, const FVector2D& patrolPoint, s16 behaviour) { + SpawnParams params = { + .Name = "mk:hedgehog", + .Behaviour = behaviour, // Appears to be unused + .Location = pos, + .PatrolEnd = patrolPoint, + }; + return static_cast<OHedgehog*>(gWorldInstance.AddObject(new OHedgehog(params))); + } ~OHedgehog() { _count--; @@ -36,6 +47,8 @@ public: virtual void Tick() override; virtual void Draw(s32 cameraId) override; + virtual void SetSpawnParams(SpawnParams& params) override; + virtual void DrawEditorProperties() override; void func_800555BC(s32 objectIndex, s32 cameraId); void func_8004A870(s32 objectIndex, f32 arg1); @@ -47,7 +60,7 @@ public: private: - FVector _pos; + FVector2D PatrolEnd; static size_t _count; size_t _idx; }; diff --git a/src/engine/objects/HotAirBalloon.cpp b/src/engine/objects/HotAirBalloon.cpp index f1e367d8d..b30c8e3ed 100644 --- a/src/engine/objects/HotAirBalloon.cpp +++ b/src/engine/objects/HotAirBalloon.cpp @@ -14,9 +14,11 @@ extern "C" { #include "actors.h" } -OHotAirBalloon::OHotAirBalloon(const FVector& pos) { +OHotAirBalloon::OHotAirBalloon(const SpawnParams& params) : OObject(params) { Name = "Hot Air Balloon"; - _pos = pos; + ResourceName = "mk:hot_air_balloon"; + + SpawnPos = params.Location.value_or(FVector{0.0f, 0.0f, 0.0f}); D_80165898 = 0; @@ -30,9 +32,14 @@ OHotAirBalloon::OHotAirBalloon(const FVector& pos) { find_unused_obj_index(&_objectIndex); + init_object(_objectIndex, 0); } +void OHotAirBalloon::SetSpawnParams(SpawnParams& params) { + OObject::SetSpawnParams(params); +} + void OHotAirBalloon::Tick() { s32 objectIndex = _objectIndex; @@ -100,10 +107,10 @@ void OHotAirBalloon::init_hot_air_balloon(s32 objectIndex) { gObjectList[objectIndex].sizeScaling = 1.0f; gObjectList[objectIndex].model = (Gfx*)d_course_luigi_raceway_dl_F960; if (gGamestate != CREDITS_SEQUENCE) { - set_obj_origin_pos(objectIndex, xOrientation * _pos.x, _pos.y, _pos.z); + set_obj_origin_pos(objectIndex, xOrientation * SpawnPos.x, SpawnPos.y, SpawnPos.z); set_obj_origin_offset(objectIndex, 0.0f, 300.0f, 0.0f); } else { - set_obj_origin_pos(objectIndex, xOrientation * _pos.x, _pos.y, _pos.z); + set_obj_origin_pos(objectIndex, xOrientation * SpawnPos.x, SpawnPos.y, SpawnPos.z); set_obj_origin_offset(objectIndex, 0.0f, 300.0f, 0.0f); } func_8008B844(objectIndex); diff --git a/src/engine/objects/HotAirBalloon.h b/src/engine/objects/HotAirBalloon.h index 301e111c4..7199a7cf4 100644 --- a/src/engine/objects/HotAirBalloon.h +++ b/src/engine/objects/HotAirBalloon.h @@ -19,16 +19,27 @@ extern "C" { class OHotAirBalloon : public OObject { public: - explicit OHotAirBalloon(const FVector& pos); + explicit OHotAirBalloon(const SpawnParams& params); + + // This is simply a helper function to keep Spawning code clean + static inline OHotAirBalloon* Spawn(FVector pos) { + SpawnParams params = { + .Name = "mk:hot_air_balloon", + .Location = pos, + }; + return static_cast<OHotAirBalloon*>(gWorldInstance.AddObject(new OHotAirBalloon(params))); + } virtual void Tick() override; virtual void Draw(s32 cameraId) override; + virtual void SetSpawnParams(SpawnParams& params) override; + void func_80055CCC(s32 objectIndex, s32 cameraId); void init_hot_air_balloon(s32 objectIndex); void func_80085534(s32 objectIndex); void func_80085768(s32 objectIndex); private: - FVector _pos; + FVector Pos; bool *_visible; }; diff --git a/src/engine/objects/Object.cpp b/src/engine/objects/Object.cpp index 4e4fd6766..d7f9ac616 100644 --- a/src/engine/objects/Object.cpp +++ b/src/engine/objects/Object.cpp @@ -10,9 +10,25 @@ extern "C" { //GameActor() -OObject::OObject() {} +OObject::OObject() { +} +OObject::OObject(SpawnParams params) { + ResourceName = "mk:object"; // This needs to be overridden in derived classes + SpawnPos = params.Location.value_or(FVector{0.0f, 0.0f, 0.0f}); + SpawnRot = params.Rotation.value_or(IRotator{0, 0, 0}); + SpawnScale = params.Scale.value_or(FVector(0, 0, 0)); + Speed = params.Speed.value_or(0.0f); +} + +void OObject::SetSpawnParams(SpawnParams& params) { + params.Name = ResourceName; + params.Location = SpawnPos; + params.Rotation = SpawnRot; + params.Scale = SpawnScale; + params.Speed = Speed; +} - // Virtual functions to be overridden by derived classes +// Virtual functions to be overridden by derived classes void OObject::Tick() { } void OObject::Tick60fps() {} void OObject::Draw(s32 cameraId) { } @@ -21,3 +37,62 @@ void OObject::Destroy() { bPendingDestroy = true; } void OObject::Reset() { } + +FVector OObject::GetLocation() const { + if (_objectIndex != -1) { + Object* object = &gObjectList[_objectIndex]; + return FVector(object->pos[0], object->pos[1], object->pos[2]); + } + printf("Editor tried to get null OObject\n"); + return FVector(0, 0, 0); +}; + +IRotator OObject::GetRotation() const { + if (_objectIndex != -1) { + Object* object = &gObjectList[_objectIndex]; + return IRotator(object->orientation[0], object->orientation[1], object->orientation[2]); + } + printf("Editor tried to get null OObject\n"); + return IRotator(0, 0, 0); +} + +FVector OObject::GetScale() const { + if (_objectIndex != -1) { + Object* object = &gObjectList[_objectIndex]; + return FVector(object->sizeScaling, object->sizeScaling, object->sizeScaling); + } + printf("Editor tried to get null OObject\n"); + return FVector(0, 0, 0); +} + +void OObject::Translate(FVector pos) { + if (_objectIndex != -1) { + SpawnPos = pos; + + Object* object = &gObjectList[_objectIndex]; + + object->pos[0] = pos.x; + object->pos[1] = pos.y; + object->pos[2] = pos.z; + object->origin_pos[0] = pos.x; + object->origin_pos[1] = pos.y; + object->origin_pos[2] = pos.z; + } else { + printf("Editor tried to translate null OObject\n"); + } +} +void OObject::Rotate(IRotator rot) { + if (_objectIndex != -1) { + SpawnRot = rot; + Object* object = &gObjectList[_objectIndex]; + object->orientation[0] = rot.pitch; + object->orientation[1] = rot.yaw; + object->orientation[2] = rot.roll; + } else { + printf("Editor tried to rotate null OObject\n"); + } +} + +void OObject::SetScale(FVector scale) { + SpawnScale = scale; +} diff --git a/src/engine/objects/Object.h b/src/engine/objects/Object.h index 9dd44397b..2a2a51e21 100644 --- a/src/engine/objects/Object.h +++ b/src/engine/objects/Object.h @@ -1,6 +1,10 @@ #pragma once #include <libultraship.h> +#include "engine/SpawnParams.h" + +// Editor +#include "engine/editor/EditorMath.h" extern "C" { #include "camera.h" @@ -12,17 +16,34 @@ public: uint8_t uuid[16]; Object o; const char* Name = ""; + const char* ResourceName = ""; bool bPendingDestroy = false; s32 _objectIndex = -1; + const char* Model = ""; + + FVector SpawnPos = {0.0f, 0.0f, 0.0f}; + IRotator SpawnRot = {0, 0, 0}; + FVector SpawnScale = {1.0f, 1.0f, 1.0f}; + float Speed = 0.0f; + std::vector<Triangle> Triangles; virtual ~OObject() = default; explicit OObject(); + explicit OObject(SpawnParams params); + virtual void SetSpawnParams(SpawnParams& params); virtual void Tick(); virtual void Tick60fps(); virtual void Draw(s32 cameraId); virtual void Expire(); virtual void Destroy(); // Mark object for deletion at the start of the next frame virtual void Reset(); + FVector GetLocation() const; + IRotator GetRotation() const; + FVector GetScale() const; + virtual void Translate(FVector pos); + void Rotate(IRotator rot); + void SetScale(FVector scale); + virtual void DrawEditorProperties() { DrawDefaultEditorProperties(); }; }; diff --git a/src/engine/objects/Penguin.cpp b/src/engine/objects/Penguin.cpp index d283101b3..34c7248dd 100644 --- a/src/engine/objects/Penguin.cpp +++ b/src/engine/objects/Penguin.cpp @@ -33,12 +33,11 @@ extern "C" { extern s8 gPlayerCount; } - -OPenguin::OPenguin(FVector pos, u16 direction, PenguinType type, Behaviour behaviour) { +OPenguin::OPenguin(const SpawnParams& params) : OObject(params) { Name = "Penguin"; - _type = type; - _bhv = behaviour; - + ResourceName = "mk:penguin"; + FVector pos = params.Location.value_or(FVector(0, 0, 0)); + Speed = params.Speed.value_or(0); find_unused_obj_index(&_objectIndex); init_object(_objectIndex, 0); @@ -47,9 +46,9 @@ OPenguin::OPenguin(FVector pos, u16 direction, PenguinType type, Behaviour behav object->origin_pos[0] = pos.x * xOrientation; object->origin_pos[1] = pos.y; object->origin_pos[2] = pos.z; - object->unk_0C6 = direction; + object->unk_0C6 = params.Rotation.value_or(IRotator(0, 0, 0)).yaw; - switch(type) { + switch(static_cast<PenguinType>(Type)) { case PenguinType::CHICK: object->surfaceHeight = 5.0f; object->sizeScaling = 0.04f; @@ -76,7 +75,7 @@ void OPenguin::Tick(void) { s32 objectIndex = _objectIndex; if (gObjectList[objectIndex].state != 0) { - if (_type == PenguinType::EMPEROR) { + if (Type == PenguinType::EMPEROR) { OPenguin::EmperorPenguin(objectIndex); } else { OPenguin::OtherPenguin(objectIndex); @@ -144,7 +143,7 @@ void OPenguin::Behaviours(s32 objectIndex) { // func_800850B0 Object* object; object = &gObjectList[objectIndex]; - switch (_bhv) { + switch (SpawnBhv) { case 1: // emperor OPenguin::func_80085080(objectIndex); break; @@ -379,9 +378,9 @@ void OPenguin::InitOtherPenguin(s32 objectIndex) { // This code has been significantly refactored from the original func_800845C8 // Into a switch statement instead of checking for the index of the penguin - switch(_bhv) { + switch(SpawnBhv) { case Behaviour::CIRCLE: - object->unk_01C[1] = Diameter; + object->unk_01C[1] = Speed; if (_toggle) { object->unk_0C4 = 0x8000; @@ -400,7 +399,7 @@ void OPenguin::InitOtherPenguin(s32 objectIndex) { case Behaviour::STRUT: if (gIsMirrorMode) { - object->unk_0C6 = MirrorModeAngleOffset; + object->unk_0C6 = SpawnRot.roll; // Roll is used to save mirror mode angle offset; } set_obj_direction_angle(objectIndex, 0U, object->unk_0C6 + 0x8000, 0U); @@ -417,3 +416,108 @@ void OPenguin::InitOtherPenguin(s32 objectIndex) { void OPenguin::Reset() { _toggle = false; } + +void OPenguin::DrawEditorProperties() { + ImGui::Text("Location"); + ImGui::SameLine(); + FVector location = GetLocation(); + if (ImGui::DragFloat3("##Location", (float*)&location)) { + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPos")) { + FVector location = FVector(0, 0, 0); + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::Text("Rotation"); + ImGui::SameLine(); + + IRotator objRot = GetRotation(); + + // Convert to temporary int values (to prevent writing 32bit values to 16bit variables) + int rot[3] = { + objRot.pitch, + objRot.yaw, + objRot.roll + }; + + if (ImGui::DragInt3("##Rotation", rot, 5.0f)) { + for (size_t i = 0; i < 3; i++) { + // Wrap around 0–65535 + rot[i] = (rot[i] % 65536 + 65536) % 65536; + } + IRotator newRot; + newRot.Set( + static_cast<uint16_t>(rot[0]), + static_cast<uint16_t>(rot[1]), + static_cast<uint16_t>(rot[2]) + ); + Rotate(newRot); + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetRot")) { + IRotator rot = IRotator(0, 0, 0); + Rotate(rot); + } + + ImGui::Text("Spawn Mode"); + ImGui::SameLine(); + + int32_t type = static_cast<int32_t>(Type); + const char* items[] = { "CHICK", "ADULT", "CREDITS", "EMPEROR" }; + + if (ImGui::Combo("##Type", &type, items, IM_ARRAYSIZE(items))) { + Type = static_cast<PenguinType>(type); + + // Update type values + Object* object = &gObjectList[this->_objectIndex]; + switch(static_cast<PenguinType>(type)) { + case PenguinType::CHICK: + object->surfaceHeight = 5.0f; + object->sizeScaling = 0.04f; + object->boundingBoxSize = 4; + break; + case PenguinType::ADULT: + object->surfaceHeight = -80.0f; + object->sizeScaling = 0.08f; + object->boundingBoxSize = 4; + break; + case PenguinType::CREDITS: + object->surfaceHeight = -80.0f; + object->sizeScaling = 0.08f; + object->sizeScaling = 0.15f; + break; + case PenguinType::EMPEROR: + object->sizeScaling = 0.2f; + object->boundingBoxSize = 0x000C; + break; + } + } + + ImGui::Text("Spawn Mode"); + ImGui::SameLine(); + + int32_t behaviour = static_cast<int32_t>(SpawnBhv); + const char* items2[] = { "DISABLED", "STRUT", "CIRCLE", "SLIDE3", "SLIDE4", "UNK", "SLIDE6" }; + + if (ImGui::Combo("##Behaviour", &behaviour, items2, IM_ARRAYSIZE(items2))) { + SpawnBhv = static_cast<Behaviour>(behaviour); + } + + ImGui::Text("Diameter"); + ImGui::SameLine(); + + float speed = Speed; + if (ImGui::DragFloat("##Speed", &speed, 0.1f)) { + Speed = speed; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeed")) { + Speed = 0.0f; + } +} diff --git a/src/engine/objects/Penguin.h b/src/engine/objects/Penguin.h index 91d5687b9..4c9c04dd6 100644 --- a/src/engine/objects/Penguin.h +++ b/src/engine/objects/Penguin.h @@ -17,14 +17,14 @@ extern "C" { class OPenguin : public OObject { public: - enum PenguinType : uint32_t { + enum PenguinType : int16_t { CHICK, ADULT, CREDITS, EMPEROR }; - enum Behaviour : uint16_t { + enum Behaviour : int16_t { DISABLED, STRUT, // Emperor penguin CIRCLE, // Waddle in a circle @@ -35,14 +35,30 @@ public: }; public: - f32 Diameter = 0.0f; // Waddle in a circle around the spawn point at this diameter. - uint16_t MirrorModeAngleOffset; + explicit OPenguin(const SpawnParams& params); - explicit OPenguin(FVector pos, u16 direction, PenguinType type, Behaviour behaviour); + // This is simply a helper function to keep Spawning code clean + static inline OPenguin* Spawn(FVector pos, u16 direction, u16 mirrorModeAngleOffset, f32 diameter, PenguinType type, Behaviour behaviour) { + IRotator rot; + rot.Set(0, direction, mirrorModeAngleOffset); + SpawnParams params = { + .Name = "mk:penguin", + .Type = type, + .Behaviour = behaviour, + .Location = pos, + .Rotation = rot, + .Speed = diameter, // Diameter of the walking circle + }; + return static_cast<OPenguin*>(gWorldInstance.AddObject(new OPenguin(params))); + } + + PenguinType Type = PenguinType::CHICK; + Behaviour SpawnBhv = Behaviour::STRUT; virtual void Tick() override; virtual void Draw(s32 cameraId) override; virtual void Reset() override; + virtual void DrawEditorProperties() override; private: void Behaviours(s32 objectIndex); void EmperorPenguin(s32 objectIndex); @@ -55,6 +71,4 @@ private: void InitOtherPenguin(s32 objectIndex); static bool _toggle; - PenguinType _type; - Behaviour _bhv; }; diff --git a/src/engine/objects/Podium.cpp b/src/engine/objects/Podium.cpp index ec396ca53..d5e09d799 100644 --- a/src/engine/objects/Podium.cpp +++ b/src/engine/objects/Podium.cpp @@ -25,9 +25,10 @@ extern Vec3s D_800E634C[]; // { 0xf380, 0x0013, 0xfe14 }, // }; -OPodium::OPodium(const FVector& pos) { +OPodium::OPodium(const SpawnParams& params) : OObject(params) { Name = "Podium"; - _pos = pos; + ResourceName = "mk:podium"; + _pos = params.Location.value_or(FVector(0, 0, 0)); find_unused_obj_index(&_podium1Index); find_unused_obj_index(&_podium2Index); diff --git a/src/engine/objects/Podium.h b/src/engine/objects/Podium.h index fafdfe63d..f4f02fc8c 100644 --- a/src/engine/objects/Podium.h +++ b/src/engine/objects/Podium.h @@ -24,7 +24,16 @@ public: public: - explicit OPodium(const FVector& pos); + explicit OPodium(const SpawnParams& params); + + // This is simply a helper function to keep Spawning code clean + static inline OPodium* Spawn(const FVector& pos) { + SpawnParams params = { + .Name = "mk:podium", + .Location = pos, + }; + return static_cast<OPodium*>(gWorldInstance.AddObject(new OPodium(params))); + } virtual void Tick() override; virtual void Draw(s32 cameraId) override; diff --git a/src/engine/objects/Seagull.cpp b/src/engine/objects/Seagull.cpp index 17c35c07d..61a7f39ca 100644 --- a/src/engine/objects/Seagull.cpp +++ b/src/engine/objects/Seagull.cpp @@ -31,12 +31,11 @@ SplineData* D_800E633C[] = { &D_800E6034, &D_800E60F0, &D_800E61B4, &D_800E6280 size_t OSeagull::_count = 0; -OSeagull::OSeagull(FVector pos) { +OSeagull::OSeagull(const SpawnParams& params) : OObject(params) { Name = "Seagull"; + ResourceName = "mk:seagull"; _idx = _count; - _pos.x = pos.x; - _pos.y = pos.y; - _pos.z = pos.z; + FVector pos = params.Location.value_or(FVector(0, 0, 0)); s16 randZ; s16 randX; @@ -49,6 +48,11 @@ OSeagull::OSeagull(FVector pos) { init_object(_objectIndex, 0); + Object* object = &gObjectList[_objectIndex]; + + object->pos[0] = pos.x; + object->pos[1] = pos.y; + object->pos[2] = pos.z; set_obj_origin_pos(_objectIndex, pos.x, pos.y, pos.z); if (_idx < (NUM_SEAGULLS / 2)) { @@ -171,7 +175,8 @@ void OSeagull::func_8008241C(s32 objectIndex, s32 arg1) { randY = random_int(0x0014); randZ = random_int(0x00C8) + -100.0; - set_obj_origin_pos(objectIndex, (randX + _pos.x) * xOrientation, randY + _pos.y, randZ + _pos.z); + FVector pos = SpawnPos; + set_obj_origin_pos(objectIndex, (randX + pos.x) * xOrientation, randY + pos.y, randZ + pos.z); set_obj_direction_angle(objectIndex, 0U, 0U, 0U); gObjectList[objectIndex].unk_034 = 1.0f; func_80086EF0(objectIndex); diff --git a/src/engine/objects/Seagull.h b/src/engine/objects/Seagull.h index 42474159e..73d06a900 100644 --- a/src/engine/objects/Seagull.h +++ b/src/engine/objects/Seagull.h @@ -19,7 +19,7 @@ extern "C" { //! @todo unk_0D5 needs to be a struct variable probably. What does it do? Behaviour? class OSeagull : public OObject { public: - explicit OSeagull(FVector pos); + explicit OSeagull(const SpawnParams& params); ~OSeagull() { _count--; @@ -29,6 +29,15 @@ public: return _count; } + // This is simply a helper function to keep Spawning code clean + static inline OSeagull* Spawn(const FVector& pos) { + SpawnParams params = { + .Name = "mk:seagull", + .Location = pos, + }; + return static_cast<OSeagull*>(gWorldInstance.AddObject(new OSeagull(params))); + } + virtual void Tick() override; virtual void Draw(s32 cameraId) override; @@ -38,7 +47,6 @@ public: void func_8008241C(s32 objectIndex, s32 arg1); void func_80082714(s32 objectIndex, s32 arg1); private: - FVector _pos; static size_t _count; s32 _idx; bool _toggle; diff --git a/src/engine/objects/Snowman.cpp b/src/engine/objects/Snowman.cpp index cbb708fbf..dd98e2df0 100644 --- a/src/engine/objects/Snowman.cpp +++ b/src/engine/objects/Snowman.cpp @@ -17,31 +17,32 @@ static const char* sSnowmanHeadList[] = { d_course_frappe_snowland_snowman_head size_t OSnowman::_count = 0; -OSnowman::OSnowman(const FVector& pos) { +OSnowman::OSnowman(const SpawnParams& params) : OObject(params) { Name = "Snowman"; + ResourceName = "mk:snowman"; _idx = _count; - _pos = pos; + Pos = params.Location.value_or(FVector(0, 0, 0)); find_unused_obj_index(&_headIndex); init_object(_headIndex, 0); _objectIndex = _headIndex; - gObjectList[_headIndex].origin_pos[0] = pos.x * xOrientation; - gObjectList[_headIndex].origin_pos[1] = pos.y + 5.0 + 3.0; - gObjectList[_headIndex].origin_pos[2] = pos.z; - gObjectList[_headIndex].pos[0] = pos.x * xOrientation; - gObjectList[_headIndex].pos[1] = pos.y + 5.0 + 3.0; - gObjectList[_headIndex].pos[2] = pos.z; + gObjectList[_headIndex].origin_pos[0] = Pos.x * xOrientation; + gObjectList[_headIndex].origin_pos[1] = Pos.y + 5.0 + 3.0; + gObjectList[_headIndex].origin_pos[2] = Pos.z; + gObjectList[_headIndex].pos[0] = Pos.x * xOrientation; + gObjectList[_headIndex].pos[1] = Pos.y + 5.0 + 3.0; + gObjectList[_headIndex].pos[2] = Pos.z; find_unused_obj_index(&_bodyIndex); init_object(_bodyIndex, 0); - gObjectList[_bodyIndex].origin_pos[0] = pos.x * xOrientation; - gObjectList[_bodyIndex].origin_pos[1] = pos.y + 3.0; - gObjectList[_bodyIndex].origin_pos[2] = pos.z; + gObjectList[_bodyIndex].origin_pos[0] = Pos.x * xOrientation; + gObjectList[_bodyIndex].origin_pos[1] = Pos.y + 3.0; + gObjectList[_bodyIndex].origin_pos[2] = Pos.z; gObjectList[_bodyIndex].unk_0D5 = 0; // Section Id no longer used. - gObjectList[_bodyIndex].pos[0] = pos.x * xOrientation; - gObjectList[_bodyIndex].pos[1] = pos.y + 3.0; - gObjectList[_bodyIndex].pos[2] = pos.z; + gObjectList[_bodyIndex].pos[0] = Pos.x * xOrientation; + gObjectList[_bodyIndex].pos[1] = Pos.y + 3.0; + gObjectList[_bodyIndex].pos[2] = Pos.z; _count++; } @@ -348,6 +349,31 @@ void OSnowman::func_80083B0C(s32 objectIndex) { set_object_flag(objectIndex, 0x04000210); } +void OSnowman::Translate(FVector pos) { + if ((_objectIndex != -1) && (_bodyIndex != -1)) { + SpawnPos = pos; + + Object* object = &gObjectList[_objectIndex]; + + object->pos[0] = pos.x; + object->pos[1] = pos.y; + object->pos[2] = pos.z; + object->origin_pos[0] = pos.x; + object->origin_pos[1] = pos.y; + object->origin_pos[2] = pos.z; + + object = &gObjectList[_bodyIndex]; + object->pos[0] = pos.x; + object->pos[1] = pos.y - 5.0; + object->pos[2] = pos.z; + object->origin_pos[0] = pos.x; + object->origin_pos[1] = pos.y - 5.0; + object->origin_pos[2] = pos.z; + } else { + printf("Editor tried to translate null OObject\n"); + } +} + void OSnowman::func_80083538(s32 objectIndex, Vec3f arg1, s32 arg2, s32 arg3) { Object* object; diff --git a/src/engine/objects/Snowman.h b/src/engine/objects/Snowman.h index f33ee3b53..42217f040 100644 --- a/src/engine/objects/Snowman.h +++ b/src/engine/objects/Snowman.h @@ -19,7 +19,16 @@ extern "C" { class OSnowman : public OObject { public: - explicit OSnowman(const FVector& pos); + // This is simply a helper function to keep Spawning code clean + static inline OSnowman* Spawn(FVector pos) { + SpawnParams params = { + .Name = "mk:snowman", + .Location = FVector(pos.x, pos.y, pos.z), + }; + return static_cast<OSnowman*>(gWorldInstance.AddObject(new OSnowman(params))); + } + + explicit OSnowman(const SpawnParams& params); ~OSnowman() { _count--; @@ -31,6 +40,7 @@ public: virtual void Tick() override; virtual void Draw(s32 cameraId) override; + virtual void Translate(FVector pos) override; void DrawHead(s32); void DrawBody(s32); @@ -47,7 +57,7 @@ public: void func_8008379C(s32 objectIndex); private: - FVector _pos; + FVector Pos; static size_t _count; size_t _idx; s32 _headIndex; diff --git a/src/engine/objects/Thwomp.cpp b/src/engine/objects/Thwomp.cpp index 2f8281cb6..b597f26ba 100644 --- a/src/engine/objects/Thwomp.cpp +++ b/src/engine/objects/Thwomp.cpp @@ -2,6 +2,8 @@ #include <libultra/gbi.h> #include "Thwomp.h" #include <vector> +#include "engine/courses/Course.h" +#include "engine/World.h" #include "port/Game.h" #include "port/interpolation/FrameInterpolation.h" @@ -48,32 +50,56 @@ s16 D_800E597C[] = { 0x0000, 0x0000, 0x4000, 0x8000, 0x8000, 0xc000 }; size_t OThwomp::_count = 0; size_t OThwomp::_rand = 0; -OThwomp::OThwomp(s16 x, s16 z, s16 direction, f32 scale, s16 behaviour, s16 primAlpha, u16 boundingBoxSize) { +OThwomp::OThwomp(const SpawnParams& params) : OObject(params) { // s16 x, s16 z, s16 direction, f32 scale, s16 behaviour, s16 primAlpha, u16 boundingBoxSize) { + FVector loc = params.Location.value_or(FVector{0, 0, 0}); + IRotator rot = params.Rotation.value_or(IRotator{0, 0, 0}); + BoundingBoxSize = params.BoundingBoxSize.value_or(0); + Behaviour = static_cast<OThwomp::States>(params.Behaviour.value_or(1)); + PrimAlpha = params.PrimAlpha.value_or(0); + FVector scale = params.Scale.value_or(FVector(0, 0, 0)); + Name = "Thwomp"; + ResourceName = "mk:thwomp"; + Model = "d_course_bowsers_castle_dl_thwomp"; _idx = _count; - _faceDirection = direction; - _boundingBoxSize = boundingBoxSize; - State = (States)behaviour; + _faceDirection = rot.yaw; find_unused_obj_index(&_objectIndex); s32 objectId = _objectIndex; init_object(objectId, 0); - gObjectList[objectId].origin_pos[0] = x * xOrientation; - gObjectList[objectId].origin_pos[2] = z; - gObjectList[objectId].unk_0D5 = behaviour; - gObjectList[objectId].primAlpha = primAlpha; - gObjectList[objectId].boundingBoxSize = boundingBoxSize + 5; - - if (scale == 0.0f) { - scale = 1.0f; + gObjectList[objectId].origin_pos[0] = loc.x * xOrientation; + gObjectList[objectId].origin_pos[2] = loc.z; + gObjectList[objectId].unk_0D5 = Behaviour; + gObjectList[objectId].primAlpha = PrimAlpha; + gObjectList[objectId].boundingBoxSize = BoundingBoxSize + 5; + + if (scale.y == 0.0f) { + scale.y = 1.0f; } - gObjectList[objectId].sizeScaling = scale; + gObjectList[objectId].sizeScaling = scale.y; _count++; } +void OThwomp::SetSpawnParams(SpawnParams& params) { + Object* object = &gObjectList[_objectIndex]; + params.Name = std::string(ResourceName); + params.Location = FVector( + object->origin_pos[0], + object->origin_pos[1], + object->origin_pos[2] + ); + IRotator rot; rot.Set(0, object->orientation[1], 0); + params.Rotation = rot; + params.Scale = FVector(0, object->sizeScaling, 0); + params.Behaviour = Behaviour; + params.PrimAlpha = PrimAlpha; + params.BoundingBoxSize = BoundingBoxSize; + +} + void OThwomp::Tick60fps() { // func_80081210 Player* player; s32 objectIndex; @@ -94,23 +120,23 @@ void OThwomp::Tick60fps() { // func_80081210 } if (gObjectList[_objectIndex].state != 0) { - switch (State) { - case STATIONARY: + switch(Behaviour) { + case States::STATIONARY: OThwomp::StationaryBehaviour(_objectIndex); break; - case MOVE_AND_ROTATE: + case States::MOVE_AND_ROTATE: OThwomp::MoveAndRotateBehaviour(_objectIndex); break; - case MOVE_FAR: + case States::MOVE_FAR: OThwomp::MoveFarBehaviour(_objectIndex); break; - case STATIONARY_FAST: + case States::STATIONARY_FAST: OThwomp::StationaryFastBehaviour(_objectIndex); break; - case JAILED: + case States::JAILED: OThwomp::JailedBehaviour(_objectIndex); break; - case SLIDE: + case States::SLIDE: OThwomp::SlidingBehaviour(_objectIndex); break; } @@ -138,8 +164,6 @@ void OThwomp::Tick60fps() { // func_80081210 OThwomp::AddParticles(_objectIndex); } - - if (_idx == 0) { for (var_s4 = 0; var_s4 < gObjectParticle2_SIZE; var_s4++) { // @port: Tag the transform. @@ -641,7 +665,7 @@ void OThwomp::func_80080B28(s32 objectIndex, s32 playerId) { } } else if ((temp_f0 <= 17.5) && (func_80072320(objectIndex, 1) != 0) && (is_within_horizontal_distance_of_player(objectIndex, player, - (player->speed * 0.5) + _boundingBoxSize) != 0)) { + (player->speed * 0.5) + BoundingBoxSize) != 0)) { if ((player->type & 0x8000) && !(player->type & 0x100)) { if (is_obj_flag_status_active(objectIndex, 0x04000000) != 0) { func_80072180(); @@ -677,8 +701,8 @@ void OThwomp::Draw(s32 cameraId) { Camera* camera; Object* object; - camera = &camera1[cameraId]; - if (cameraId == PLAYER_ONE) { + camera = &cameras[cameraId]; + if (cameraId == PLAYER_ONE || cameraId == 4) { // 4 == freecam clear_object_flag(objectIndex, 0x00070000); func_800722CC(objectIndex, 0x00000110); } @@ -704,7 +728,7 @@ void OThwomp::Draw(s32 cameraId) { objectIndex = gObjectParticle3[i]; if (objectIndex != NULL_OBJECT_ID) { object = &gObjectList[objectIndex]; - if ((object->state > 0) && (State == MOVE_FAR) && (gMatrixHudCount <= MTX_HUD_POOL_SIZE_MAX)) { + if ((object->state > 0) && (Behaviour == States::MOVE_FAR)) { rsp_set_matrix_transformation(object->pos, object->orientation, object->sizeScaling); gSPVertex(gDisplayListHead++, (uintptr_t) D_0D005C00, 3, 0); gSPDisplayList(gDisplayListHead++, (Gfx*) D_0D006930); @@ -723,7 +747,7 @@ void OThwomp::Draw(s32 cameraId) { objectIndex = gObjectParticle2[i]; if (objectIndex != NULL_OBJECT_ID) { object = &gObjectList[objectIndex]; - if ((object->state >= 2) && (State == MOVE_AND_ROTATE) && (gMatrixHudCount <= MTX_HUD_POOL_SIZE_MAX)) { + if ((object->state >= 2) && (Behaviour == States::MOVE_AND_ROTATE)) { func_8004B138(0x000000FF, 0x000000FF, 0x000000FF, (s32) object->primAlpha); D_80183E80[1] = func_800418AC(object->pos[0], object->pos[2], camera->pos); func_800431B0(object->pos, D_80183E80, object->sizeScaling, (Vtx*) D_0D005AE0); @@ -734,6 +758,7 @@ void OThwomp::Draw(s32 cameraId) { void OThwomp::DrawModel(s32 objectIndex) { if ((gObjectList[objectIndex].state >= 2) && (func_80072354(objectIndex, 0x00000040) != 0)) { + FrameInterpolation_RecordOpenChild("Thwomp_Main", (uintptr_t) TAG_THWOMP(this)); func_8004A7AC(objectIndex, 1.75f); rsp_set_matrix_transformation(gObjectList[objectIndex].pos, gObjectList[objectIndex].orientation, gObjectList[objectIndex].sizeScaling); @@ -743,6 +768,7 @@ void OThwomp::DrawModel(s32 objectIndex) { gDPLoadTLUT_pal256(gDisplayListHead++, d_course_bowsers_castle_thwomp_tlut); rsp_load_texture_mask((u8*) gObjectList[objectIndex].activeTexture, 0x00000010, 0x00000040, 4); gSPDisplayList(gDisplayListHead++, gObjectList[objectIndex].model); + FrameInterpolation_RecordCloseChild(); } } @@ -1448,3 +1474,108 @@ void OThwomp::func_8007E63C(s32 objectIndex) { break; } } + +void OThwomp::DrawEditorProperties() { + ImGui::Text("Behaviour"); + ImGui::SameLine(); + + int32_t behaviour = static_cast<int32_t>(Behaviour); + const char* items[] = { "Disabled", "Stationary", "Move and Rotate", "Move Far", "Stationary Fast", "Slide", "Jailed" }; + + if (ImGui::Combo("##Behaviour", &behaviour, items, IM_ARRAYSIZE(items))) { + Behaviour = static_cast<OThwomp::States>(behaviour); + gObjectList[_objectIndex].unk_0D5 = static_cast<uint8_t>(behaviour); + gObjectList[_objectIndex].state = behaviour; + } + + ImGui::Text("Location"); + ImGui::SameLine(); + FVector location = GetLocation(); + if (ImGui::DragFloat3("##Location", (float*)&location)) { + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPos")) { + FVector location = FVector(0, 0, 0); + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::Text("Rotation"); + ImGui::SameLine(); + + IRotator objRot = GetRotation(); + + // Convert to temporary int values (to prevent writing 32bit values to 16bit variables) + int rot[3] = { + objRot.pitch, + objRot.yaw, + objRot.roll + }; + + if (ImGui::DragInt3("##Rotation", rot, 5.0f)) { + for (size_t i = 0; i < 3; i++) { + // Wrap around 0–65535 + rot[i] = (rot[i] % 65536 + 65536) % 65536; + } + IRotator newRot; + newRot.Set( + static_cast<uint16_t>(rot[0]), + static_cast<uint16_t>(rot[1]), + static_cast<uint16_t>(rot[2]) + ); + Rotate(newRot); + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetRot")) { + IRotator rot = IRotator(0, 0, 0); + Rotate(rot); + } + + FVector scale = GetScale(); + ImGui::Text("Scale "); + ImGui::SameLine(); + + if (ImGui::DragFloat3("##Scale", (float*)&scale, 0.1f)) { + SetScale(scale); + gObjectList[_objectIndex].sizeScaling = scale.y; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetScale")) { + FVector scale = FVector(1.0f, 1.0f, 1.0f); + SetScale(scale); + gObjectList[_objectIndex].sizeScaling = 1.0f; + } + + int32_t primAlpha = PrimAlpha; + ImGui::Text("Prim Alpha"); + ImGui::SameLine(); + + if (ImGui::InputInt("##PrimAlpha", (int*)&primAlpha)) { + PrimAlpha = static_cast<int16_t>(primAlpha); + gObjectList[_objectIndex].primAlpha = static_cast<int16_t>(primAlpha); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPrimAlpha")) { + PrimAlpha = 0; + gObjectList[_objectIndex].primAlpha = 0; + } + + int32_t boundingBoxSize = static_cast<int32_t>(BoundingBoxSize); + ImGui::Text("Bounding Box Size"); + ImGui::SameLine(); + + if (ImGui::InputInt("##BoundingBoxSize", (int*)&boundingBoxSize)) { + if (boundingBoxSize < 0) boundingBoxSize = 0; + BoundingBoxSize = static_cast<OThwomp::States>(boundingBoxSize); + gObjectList[_objectIndex].boundingBoxSize = static_cast<uint16_t>(boundingBoxSize); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetBoundingBoxSize")) { + BoundingBoxSize = 0; + gObjectList[_objectIndex].boundingBoxSize = 0; + } +} diff --git a/src/engine/objects/Thwomp.h b/src/engine/objects/Thwomp.h index 6822b4e31..a76872093 100644 --- a/src/engine/objects/Thwomp.h +++ b/src/engine/objects/Thwomp.h @@ -2,10 +2,15 @@ #include <libultraship.h> #include <vector> - #include "engine/World.h" +#include "engine/SpawnParams.h" +#include "engine/CoreMath.h" + #include "engine/objects/Object.h" +class World; +extern World gWorldInstance; + extern "C" { #include "macros.h" #include "main.h" @@ -42,9 +47,24 @@ public: JAILED // Has no collision }; - States State = States::DISABLED; + // This is simply a helper function to keep Spawning code clean + static inline OThwomp* Spawn(s16 x, s16 z, s16 direction, f32 scale, s16 behaviour, s16 primAlpha, u16 boundingBoxSize = 7) { + IRotator rot; + rot.Set(0, direction, 0); + + SpawnParams params = { + .Name = "mk:thwomp", + .Behaviour = behaviour, + .Location = FVector(x, 0, z), + .Rotation = rot, + .Scale = FVector(0, scale, 0), + .PrimAlpha = primAlpha, + .BoundingBoxSize = boundingBoxSize + }; + return static_cast<OThwomp*>(gWorldInstance.AddObject(new OThwomp(params))); + } - explicit OThwomp(s16 x, s16 z, s16 direction, f32 scale, s16 behaviour, s16 primAlpha, u16 boundingBoxSize = 7); + explicit OThwomp(const SpawnParams& params); ~OThwomp() { _count--; @@ -54,8 +74,10 @@ public: return _count; } + virtual void SetSpawnParams(SpawnParams& params) override; virtual void Tick60fps() override; virtual void Draw(s32 cameraId) override; + virtual void DrawEditorProperties() override; void SetVisibility(s32 objectIndex); void func_80080B28(s32 objectIndex, s32 playerId); void DrawModel(s32); @@ -109,6 +131,10 @@ public: void func_8008078C(s32 objectIndex); void func_8007E63C(s32 objectIndex); + + u16 BoundingBoxSize; + OThwomp::States Behaviour; + int16_t PrimAlpha; private: static size_t _count; static size_t _rand; @@ -116,5 +142,4 @@ private: s16 _faceDirection; //! @todo Write this better. This effects the squish size and the bounding box size. // We should probably return to the programmer the pointer to the actor so they can do thwomp->squishSize = value. - u16 _boundingBoxSize; }; diff --git a/src/engine/objects/TrashBin.cpp b/src/engine/objects/TrashBin.cpp index 57c702275..871151b8f 100644 --- a/src/engine/objects/TrashBin.cpp +++ b/src/engine/objects/TrashBin.cpp @@ -21,12 +21,13 @@ extern "C" { #define DEGREES_FLOAT_TO_SHORT(Degrees) ((s16)((Degrees) * (0x8000 / 180.0f))) -OTrashBin::OTrashBin(const FVector& pos, const IRotator& rotation, f32 scale, OTrashBin::Behaviour bhv) { - Name = "Trashbin"; - _pos = pos; - _rot = rotation; - _scale = scale; - _bhv = bhv; +OTrashBin::OTrashBin(const SpawnParams& params) : OObject(params) { + Name = "Trash Bin"; + ResourceName = "mk:trash_bin"; + _pos = params.Location.value_or(FVector(0, 0, 0)); + _rot = params.Rotation.value_or(IRotator(0, 0, 0)); + _scale = params.Scale.value_or(FVector(0, 0, 0)).y; // Only y + _bhv = static_cast<Behaviour>(params.Behaviour.value_or(0)); find_unused_obj_index(&_objectIndex); diff --git a/src/engine/objects/TrashBin.h b/src/engine/objects/TrashBin.h index 3e878e1c0..4f2ea4132 100644 --- a/src/engine/objects/TrashBin.h +++ b/src/engine/objects/TrashBin.h @@ -20,11 +20,24 @@ extern "C" { class OTrashBin : public OObject { public: - enum Behaviour { + enum Behaviour : int16_t { STATIC, // The lid stays shut MUNCHING // The lid opens/closes in a scary munching manner }; - explicit OTrashBin(const FVector& pos, const IRotator& rotation, f32 scale, OTrashBin::Behaviour bhv); + + // This is simply a helper function to keep Spawning code clean + static inline OTrashBin* Spawn(const FVector& pos, const IRotator& rot, f32 scale, OTrashBin::Behaviour bhv) { + SpawnParams params = { + .Name = "mk:trash_bin", + .Behaviour = bhv, + .Location = pos, + .Rotation = rot, + .Scale = FVector(0, scale, 0), + }; + return static_cast<OTrashBin*>(gWorldInstance.AddObject(new OTrashBin(params))); + } + + explicit OTrashBin(const SpawnParams& params); virtual void Tick() override; virtual void Draw(s32 cameraId) override; diff --git a/src/engine/objects/Trophy.cpp b/src/engine/objects/Trophy.cpp index e3713158d..ba5435953 100644 --- a/src/engine/objects/Trophy.cpp +++ b/src/engine/objects/Trophy.cpp @@ -20,12 +20,14 @@ extern "C" { #include "menu_items.h" } -OTrophy::OTrophy(const FVector& pos, TrophyType trophy, Behaviour bhv) { +OTrophy::OTrophy(const SpawnParams& params) : OObject(params) { Name = "Trophy"; - _trophy = trophy; - _spawnPos = pos; - _spawnPos.y += 16.0f; // Adjust the height so the trophy sits on the surface when positioned to 0,0,0 - _bhv = bhv; + ResourceName = "mk:trophy"; + _type = static_cast<TrophyType>(params.Type.value_or(0)); + FVector spawnPos = params.Location.value_or(FVector(0, 0, 0)); + spawnPos.y += 16.0f; // Adjust the height so the trophy sits on the surface when positioned to 0,0,0 + SpawnPos = spawnPos; // Don't save the + 16.0f adjustment + _bhv = static_cast<Behaviour>(params.Behaviour.value_or(0)); find_unused_obj_index(&_objectIndex); @@ -34,10 +36,10 @@ OTrophy::OTrophy(const FVector& pos, TrophyType trophy, Behaviour bhv) { // Thus this will need to be changed if that's not desired. gTrophyIndex = _objectIndex; - if (bhv == OTrophy::Behaviour::PODIUM_CEREMONY) { + if (_bhv == OTrophy::Behaviour::PODIUM_CEREMONY) { _toggleVisibility = &D_801658CE; } else { - _toggle = 1; + _toggle = true; _toggleVisibility = &_toggle; _isMod = true; } @@ -47,7 +49,7 @@ OTrophy::OTrophy(const FVector& pos, TrophyType trophy, Behaviour bhv) { init_object(_objectIndex, 0); } - switch (trophy) { + switch (_type) { case TrophyType::GOLD: gObjectList[_objectIndex].model = (Gfx*)gold_trophy_dl10; break; @@ -83,16 +85,23 @@ OTrophy::OTrophy(const FVector& pos, TrophyType trophy, Behaviour bhv) { } Object *object = &gObjectList[_objectIndex]; - object->origin_pos[0] = _spawnPos.x; - object->origin_pos[1] = _spawnPos.y; - object->origin_pos[2] = _spawnPos.z; - object->pos[0] = _spawnPos.x; - object->pos[1] = _spawnPos.y; - object->pos[2] = _spawnPos.z; + object->origin_pos[0] = spawnPos.x; + object->origin_pos[1] = spawnPos.y; + object->origin_pos[2] = spawnPos.z; + object->pos[0] = spawnPos.x; + object->pos[1] = spawnPos.y; + object->pos[2] = spawnPos.z; _emitter = reinterpret_cast<StarEmitter*>(gWorldInstance.AddEmitter(new StarEmitter())); } +void OTrophy::SetSpawnParams(SpawnParams& params) { + OObject::SetSpawnParams(params); + Object *object = &gObjectList[_objectIndex]; + params.Type = _type; + params.Behaviour = _bhv; +} + void OTrophy::Tick() { // func_80086D80 s32 objectIndex = _objectIndex; s32 var_s0; @@ -123,8 +132,9 @@ void OTrophy::Tick() { // func_80086D80 case OTrophy::Behaviour::STATIONARY: if (gObjectList[objectIndex].state != 0) { gObjectList[objectIndex].sizeScaling = 0.025f; - set_obj_origin_pos(objectIndex, _spawnPos.x, - _spawnPos.y + 16.0, _spawnPos.z); + + set_obj_origin_pos(objectIndex, SpawnPos.x, + SpawnPos.y + 16.0, SpawnPos.z); set_obj_origin_offset(objectIndex, 0.0f, 0.0f, 0.0f); set_obj_direction_angle(objectIndex, 0U, 0U, 0U); gObjectList[objectIndex].unk_084[1] = 0x0200; @@ -221,9 +231,14 @@ void OTrophy::Draw(s32 cameraId) { if (*_toggleVisibility == true) { object = &gObjectList[listIndex]; if (object->state >= 2) { - gSPMatrix(gDisplayListHead++, GetPerspMatrix(0), + // Prevents a perspective glitch + if (CVarGetInteger("gFreecam", 0) == true) { + cameraId = CAMERA_FREECAM; + } + + gSPMatrix(gDisplayListHead++, GetPerspMatrix(cameraId), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); - gSPMatrix(gDisplayListHead++, GetLookAtMatrix(0), + gSPMatrix(gDisplayListHead++, GetLookAtMatrix(cameraId), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); mtxf_set_matrix_transformation(someMatrix1, object->pos, object->direction_angle, object->sizeScaling); //convert_to_fixed_point_matrix(&gGfxPool->mtxHud[gMatrixHudCount], someMatrix1); @@ -234,7 +249,7 @@ void OTrophy::Draw(s32 cameraId) { gSPDisplayList(gDisplayListHead++, (Gfx*)D_0D0077A0); gSPDisplayList(gDisplayListHead++, object->model); - gSPMatrix(gDisplayListHead++, GetLookAtMatrix(0), + gSPMatrix(gDisplayListHead++, GetLookAtMatrix(cameraId), G_MTX_NOPUSH | G_MTX_MUL | G_MTX_PROJECTION); mtxf_identity(someMatrix2); render_set_position(someMatrix2, 0); @@ -355,3 +370,61 @@ void OTrophy::func_80086C6C(s32 objectIndex) { _emitter->Emit(sp24, D_801658F4); } } + +void OTrophy::DrawEditorProperties() { + ImGui::Text("Location"); + ImGui::SameLine(); + FVector location = GetLocation(); + if (ImGui::DragFloat3("##Location", (float*)&location)) { + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPos")) { + FVector location = FVector(0, 0, 0); + Translate(location); + gEditor.eObjectPicker.eGizmo.Pos = location; + } + + ImGui::Text("Cup"); + ImGui::SameLine(); + + int32_t type = static_cast<int32_t>(_type); + const char* items[] = { "Bronze", "Silver", "Gold", "Bronze 150", "Silver 150", "Gold 150" }; + + if (ImGui::Combo("##Type", &type, items, IM_ARRAYSIZE(items))) { + _type = static_cast<TrophyType>(type); + + switch (_type) { + case TrophyType::GOLD: + gObjectList[_objectIndex].model = (Gfx*)gold_trophy_dl10; + break; + case TrophyType::SILVER: + gObjectList[_objectIndex].model = (Gfx*)gold_trophy_dl12; + break; + case TrophyType::BRONZE: + gObjectList[_objectIndex].model = (Gfx*)gold_trophy_dl14; + break; + case TrophyType::GOLD_150: + gObjectList[_objectIndex].model = (Gfx*)gold_trophy_dl11; + break; + case TrophyType::SILVER_150: + gObjectList[_objectIndex].model = (Gfx*)gold_trophy_dl13; + break; + case TrophyType::BRONZE_150: + gObjectList[_objectIndex].model = (Gfx*)gold_trophy_dl15; + break; + } + } + + ImGui::Text("Behaviour"); + ImGui::SameLine(); + + int32_t behaviour = static_cast<int32_t>(_bhv); + const char* items2[] = { "Podium Ceremony", "Stationary", "Opposing Dual-axis Rotation", "Single-axis Rotation", "Go Fish" }; + + if (ImGui::Combo("##Behaviour", &behaviour, items2, IM_ARRAYSIZE(items2))) { + _bhv = static_cast<Behaviour>(behaviour); + } +} diff --git a/src/engine/objects/Trophy.h b/src/engine/objects/Trophy.h index 016115dcd..628248edc 100644 --- a/src/engine/objects/Trophy.h +++ b/src/engine/objects/Trophy.h @@ -19,7 +19,7 @@ extern "C" { class OTrophy : public OObject { public: - enum TrophyType { + enum TrophyType : int16_t { BRONZE, SILVER, GOLD, @@ -28,7 +28,7 @@ public: GOLD_150, }; - enum Behaviour { + enum Behaviour : int16_t { PODIUM_CEREMONY, STATIONARY, ROTATE, // A dual-axis opposing rotation @@ -36,10 +36,23 @@ public: GO_FISH, }; - explicit OTrophy(const FVector& pos, TrophyType trophy, Behaviour bhv); + // This is simply a helper function to keep Spawning code clean + static inline OTrophy* Spawn(const FVector& pos, TrophyType trophy, Behaviour bhv) { + SpawnParams params = { + .Name = "mk:trophy", + .Type = trophy, + .Behaviour = bhv, + .Location = pos, + }; + return static_cast<OTrophy*>(gWorldInstance.AddObject(new OTrophy(params))); + } + explicit OTrophy(const SpawnParams& params); + + virtual void SetSpawnParams(SpawnParams& params) override; virtual void Tick() override; virtual void Draw(s32 cameraId) override; + virtual void DrawEditorProperties() override; void func_80086700(s32 objectIndex); void func_80086940(s32 objectIndex); void func_80086C14(s32 objectIndex); @@ -48,8 +61,7 @@ public: private: StarEmitter* _emitter; - TrophyType _trophy; - FVector _spawnPos; + TrophyType _type; Behaviour _bhv; int8_t _toggle; int8_t *_toggleVisibility; diff --git a/src/engine/vehicles/Boat.cpp b/src/engine/vehicles/Boat.cpp index 44e68da1a..5a74dc664 100644 --- a/src/engine/vehicles/Boat.cpp +++ b/src/engine/vehicles/Boat.cpp @@ -1,6 +1,8 @@ #include <libultraship.h> #include "Boat.h" #include <vector> +#include "Utils.h" +#include "port/Game.h" extern "C" { #include "macros.h" @@ -15,19 +17,35 @@ extern s8 gPlayerCount; } size_t ABoat::_count = 0; +std::map<uint32_t, std::vector<uint32_t>> ABoat::BoatCounts; -ABoat::ABoat(f32 speed, u32 waypoint) { +ABoat::ABoat(const SpawnParams& params) : AActor(params) { Name = "Paddle Steam Boat"; - Path2D* temp_a2; - u16 waypointOffset; + ResourceName = "mk:paddle_boat"; + BoundingBoxSize = 2.0f; + TrackPathPoint* temp_a2; Index = _count; - Speed = speed; + Speed = params.Speed.value_or(0); // Set to the default value std::fill(SmokeParticles, SmokeParticles + 128, NULL_OBJECT_ID); - waypointOffset = waypoint; - temp_a2 = &gVehicle2DPathPoint[waypointOffset]; + ABoat::SpawnMode spawnMode = static_cast<SpawnMode>(params.Type.value_or(SpawnMode::POINT)); + uint32_t pathIndex = params.PathIndex.value_or(0); + uint32_t pathPoint = 0; + + switch(spawnMode) { + case SpawnMode::POINT: // Spawn train at a specific path point + pathPoint = params.PathPoint.value_or(0); + BoatCounts[pathIndex].push_back(pathPoint); + break; + case SpawnMode::AUTO: // Automatically distribute trains based on a specific path point + pathPoint = GetVehiclePathPointDistributed(BoatCounts[pathIndex], gVehiclePathSize); + BoatCounts[pathIndex].push_back(pathPoint); + break; + } + + temp_a2 = &gVehicle2DPathPoint[pathPoint]; Position[0] = temp_a2->x; Position[1] = D_80162EB2; Position[2] = temp_a2->z; @@ -54,6 +72,14 @@ ABoat::ABoat(f32 speed, u32 waypoint) { _count++; } +void ABoat::SetSpawnParams(SpawnParams& params) { + AActor::SetSpawnParams(params); + params.Type = static_cast<uint16_t>(SpawnType); + params.Speed = Speed; + params.PathIndex = PathIndex; + params.PathPoint = PathPoint; +} + void ABoat::Draw(Camera* camera) { } @@ -62,7 +88,7 @@ bool ABoat::IsMod() { } void ABoat::Tick() { - Path2D* waypoint; + TrackPathPoint* waypoint; struct Actor* paddleBoatActor; f32 temp_f26; f32 temp_f28; @@ -106,7 +132,7 @@ void ABoat::Tick() { sp94[0] = temp_f26; sp94[1] = temp_f28; sp94[2] = temp_f30; - waypoint = &gVehicle2DPathPoint[(WaypointIndex + 5) % gVehicle2DPathLength]; + waypoint = &gVehicle2DPathPoint[(WaypointIndex + 5) % gVehiclePathSize]; sp88[0] = (f32) waypoint->x; sp88[1] = (f32) D_80162EB0; sp88[2] = (f32) waypoint->z; @@ -194,4 +220,56 @@ s32 ABoat::AddSmoke(size_t ferryIndex, Vec3f pos, f32 velocity) { } return objectIndex; -}
\ No newline at end of file +} + +void ABoat::DrawEditorProperties() { + ImGui::Text("Spawn Mode"); + ImGui::SameLine(); + + int32_t type = static_cast<int32_t>(SpawnType); + const char* items[] = { "POINT", "AUTO" }; + + if (ImGui::Combo("##Type", &type, items, IM_ARRAYSIZE(items))) { + SpawnType = static_cast<ABoat::SpawnMode>(type); + } + + if (SpawnType == ABoat::SpawnMode::POINT) { + ImGui::Text("Path Index"); + ImGui::SameLine(); + + int pathIndex = static_cast<int>(PathIndex); + if (ImGui::InputInt("##PathIndex", &pathIndex)) { + if (pathIndex < 0) pathIndex = 0; + PathIndex = static_cast<uint32_t>(pathIndex); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathIndex")) { + PathIndex = 0; + } + + ImGui::Text("Path Point"); + ImGui::SameLine(); + + int pathPoint = static_cast<int>(PathPoint); + if (ImGui::InputInt("##PathPoint", &pathPoint)) { + if (pathPoint < 0) pathPoint = 0; + PathPoint = static_cast<uint32_t>(pathPoint); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathPoint")) { + PathPoint = 0; + } + } + + ImGui::Text("Speed"); + ImGui::SameLine(); + + float speed = Speed; + if (ImGui::DragFloat("##Speed", &speed, 0.1f)) { + Speed = speed; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeed")) { + Speed = 0.0f; + } +} diff --git a/src/engine/vehicles/Boat.h b/src/engine/vehicles/Boat.h index dcdd89977..a82679faf 100644 --- a/src/engine/vehicles/Boat.h +++ b/src/engine/vehicles/Boat.h @@ -3,6 +3,9 @@ #include <libultraship.h> #include "Actor.h" #include <vector> +#include "engine/SpawnParams.h" +#include "engine/CoreMath.h" +#include "engine/World.h" extern "C" { #include "main.h" @@ -12,7 +15,12 @@ extern "C" { class ABoat : public AActor { public: - const char* Type = "mk:boat"; + enum SpawnMode : uint16_t { + POINT, // Spawn boat at a specific path point + AUTO, // Automatically distribute boats based on a specific path point + }; + + const char* Type = "mk:paddle_boat"; size_t Index; bool IsActive; // The paddle wheel boat only shows up if the number of players is < 3 Vec3f Position; @@ -28,7 +36,7 @@ class ABoat : public AActor { int16_t AnotherSmokeTimer = 0; int16_t SmokeTimer = 0; - explicit ABoat(f32 speed, uint32_t waypoint); + explicit ABoat(const SpawnParams& params); ~ABoat() { _count--; @@ -38,12 +46,30 @@ class ABoat : public AActor { return _count; } + // This is simply a helper function to keep Spawning code clean + static inline ABoat* Spawn(f32 speed, uint32_t pathIndex, uint32_t pathPoint, ABoat::SpawnMode spawnMode) { + SpawnParams params = { + .Name = "mk:paddle_boat", + .Type = static_cast<uint16_t>(spawnMode), + .PathIndex = pathIndex, + .PathPoint = pathPoint, + .Speed = speed, + }; + return static_cast<ABoat*>(gWorldInstance.AddActor(new ABoat(params))); + } + + ABoat::SpawnMode SpawnType = ABoat::SpawnMode::AUTO; + uint32_t PathIndex = 0; + uint32_t PathPoint = 0; + + virtual void SetSpawnParams(SpawnParams& params) override; virtual void Tick() override; virtual void Draw(Camera* camera) override; virtual void VehicleCollision(s32 playerId, Player* player) override; virtual s32 AddSmoke(size_t, Vec3f, f32); virtual bool IsMod() override; + virtual void DrawEditorProperties() override; private: static size_t _count; - -};
\ No newline at end of file + static std::map<uint32_t, std::vector<uint32_t>> BoatCounts; +}; diff --git a/src/engine/vehicles/Bus.cpp b/src/engine/vehicles/Bus.cpp index 57ff16bff..07057d7b0 100644 --- a/src/engine/vehicles/Bus.cpp +++ b/src/engine/vehicles/Bus.cpp @@ -1,6 +1,8 @@ #include <libultraship.h> #include "Bus.h" #include <vector> +#include "engine/vehicles/Utils.h" +#include "port/Game.h" extern "C" { #include "macros.h" @@ -17,20 +19,39 @@ extern s8 gPlayerCount; } size_t ABus::_count = 0; +std::map<uint32_t, std::vector<uint32_t>> ABus::BusCounts; -ABus::ABus(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint) { +ABus::ABus(const SpawnParams& params) : AActor(params) { Name = "Bus"; + ResourceName = "mk:bus"; + BoundingBoxSize = 2.0f; TrackPathPoint* temp_v0; u16 waypointOffset; s32 numWaypoints = gPathCountByPathIndex[0]; Index = _count; + PathIndex = params.PathIndex.value_or(0); + PathPoint = 0; - waypointOffset = waypoint; - temp_v0 = &path[waypointOffset]; - Position[0] = (f32) temp_v0->posX; - Position[1] = (f32) temp_v0->posY; - Position[2] = (f32) temp_v0->posZ; + SpawnType = static_cast<ABus::SpawnMode>(params.Type.value_or(0)); + switch(SpawnType) { + case SpawnMode::POINT: // Spawn bus at a specific path point + PathPoint = params.PathPoint.value_or(0); + BusCounts[PathIndex].push_back(PathPoint); + break; + case SpawnMode::AUTO: // Automatically distribute buses based on a specific path point + printf("vehicle path size %d\n", gVehiclePathSize); + PathPoint = GetVehiclePathPointDistributed(BusCounts[PathIndex], gVehiclePathSize); + BusCounts[PathIndex].push_back(PathPoint); + printf("train spawn path point: %d\n", PathPoint); + break; + } + + waypointOffset = PathPoint; + temp_v0 = &gTrackPaths[PathIndex][PathPoint]; + Position[0] = (f32) temp_v0->x; + Position[1] = (f32) temp_v0->y; + Position[2] = (f32) temp_v0->z; ActorIndex = -1; WaypointIndex = waypointOffset; Velocity[0] = 0.0f; @@ -43,9 +64,9 @@ ABus::ABus(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint) { } SomeMultiplierTheSequel = (f32) ((f64) (f32) (SomeType - 1) * 0.6); if (((gCCSelection > CC_50) || (gModeSelection == TIME_TRIALS)) && (SomeType == 2)) { - Speed = speedA; + Speed = params.Speed.value_or(0); } else { - Speed = speedB; + Speed = params.SpeedB.value_or(0); } Rotation[0] = 0; Rotation[2] = 0; @@ -62,6 +83,15 @@ ABus::ABus(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint) { _count++; } +void ABus::SetSpawnParams(SpawnParams& params) { + params.Name = ResourceName; + params.Type = static_cast<uint16_t>(SpawnType); + params.PathIndex = PathIndex; + params.PathPoint = PathPoint; + params.Speed = Speed; + params.SpeedB = SpeedB; +} + bool ABus::IsMod() { return true; } @@ -280,3 +310,67 @@ void ABus::VehicleCollision(s32 playerId, Player* player) { } } } + +void ABus::DrawEditorProperties() { + ImGui::Text("Spawn Mode"); + ImGui::SameLine(); + + int32_t type = static_cast<int32_t>(SpawnType); + const char* items[] = { "POINT", "AUTO" }; + + if (ImGui::Combo("##Type", &type, items, IM_ARRAYSIZE(items))) { + SpawnType = static_cast<ABus::SpawnMode>(type); + } + + if (SpawnType == ABus::SpawnMode::POINT) { + ImGui::Text("Path Index"); + ImGui::SameLine(); + + int pathIndex = static_cast<int>(PathIndex); + if (ImGui::InputInt("##PathIndex", &pathIndex)) { + if (pathIndex < 0) pathIndex = 0; + PathIndex = static_cast<uint32_t>(pathIndex); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathIndex")) { + PathIndex = 0; + } + + ImGui::Text("Path Point"); + ImGui::SameLine(); + + int pathPoint = static_cast<int>(PathPoint); + if (ImGui::InputInt("##PathPoint", &pathPoint)) { + if (pathPoint < 0) pathPoint = 0; + PathPoint = static_cast<uint32_t>(pathPoint); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathPoint")) { + PathPoint = 0; + } + } + + ImGui::Text("Speed"); + ImGui::SameLine(); + + float speed = Speed; + if (ImGui::DragFloat("##Speed", &speed, 0.1f)) { + Speed = speed; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeed")) { + Speed = 0.0f; + } + + ImGui::Text("SpeedB"); + ImGui::SameLine(); + + float speed2 = SpeedB; + if (ImGui::DragFloat("##SpeedB", &speed2, 0.1f)) { + SpeedB = speed2; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeedB")) { + SpeedB = 0.0f; + } +}
\ No newline at end of file diff --git a/src/engine/vehicles/Bus.h b/src/engine/vehicles/Bus.h index e1920dd7f..28603c522 100644 --- a/src/engine/vehicles/Bus.h +++ b/src/engine/vehicles/Bus.h @@ -3,6 +3,9 @@ #include <libultraship.h> #include "Actor.h" #include <vector> +#include "engine/SpawnParams.h" +#include "engine/CoreMath.h" +#include "engine/World.h" extern "C" { #include "main.h" @@ -13,6 +16,11 @@ extern "C" { class ABus : public AActor { public: + enum SpawnMode : uint16_t { + POINT, // Spawn car at a specific path point + AUTO, // Automatically distribute cars based on a specific path point + }; + const char* Type; size_t Index; f32 Speed; @@ -30,7 +38,20 @@ class ABus : public AActor { f32 SomeArg4 = 12.5f; u32 SoundBits = SOUND_ARG_LOAD(0x51, 0x01, 0x80, 0x03); - explicit ABus(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint); + // This is simply a helper function to keep Spawning code clean + static inline ABus* Spawn(f32 speedA, f32 speedB, uint32_t pathIndex, uint32_t pathPoint, ABus::SpawnMode spawnMode) { + SpawnParams params = { + .Name = "mk:bus", + .Type = static_cast<uint16_t>(spawnMode), + .PathIndex = pathIndex, + .PathPoint = pathPoint, + .Speed = speedA, + .SpeedB = speedB + }; + return static_cast<ABus*>(gWorldInstance.AddActor(new ABus(params))); + } + + explicit ABus(const SpawnParams& params); ~ABus() { _count--; @@ -40,11 +61,19 @@ class ABus : public AActor { return _count; } + ABus::SpawnMode SpawnType = ABus::SpawnMode::AUTO; + float SpeedB = 0.0f; + uint32_t PathIndex = 0; + uint32_t PathPoint = 0; + + virtual void SetSpawnParams(SpawnParams& params) override; virtual void Tick() override; virtual void Draw(Camera* camera) override; virtual void VehicleCollision(s32 playerId, Player* player) override; virtual bool IsMod() override; + virtual void DrawEditorProperties() override; private: static size_t _count; + static std::map<uint32_t, std::vector<uint32_t>> BusCounts; };
\ No newline at end of file diff --git a/src/engine/vehicles/Car.cpp b/src/engine/vehicles/Car.cpp index f5a9b6a4a..8873d5248 100644 --- a/src/engine/vehicles/Car.cpp +++ b/src/engine/vehicles/Car.cpp @@ -1,6 +1,8 @@ #include <libultraship.h> #include "Car.h" #include <vector> +#include "engine/vehicles/Utils.h" +#include "port/Game.h" extern "C" { #include "macros.h" @@ -17,20 +19,40 @@ extern s8 gPlayerCount; } size_t ACar::_count = 0; +// pathIndex, array of spawn points +std::map<uint32_t, std::vector<uint32_t>> ACar::CarCounts; -ACar::ACar(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint) { +ACar::ACar(const SpawnParams& params) : AActor(params) { Name = "Car"; + ResourceName = "mk:car"; + BoundingBoxSize = 2.0f; TrackPathPoint* temp_v0; u16 waypointOffset; s32 numWaypoints = gPathCountByPathIndex[0]; Index = _count; + PathIndex = params.PathIndex.value_or(0); + PathPoint = 0; - waypointOffset = waypoint; - temp_v0 = &path[waypointOffset]; - Position[0] = (f32) temp_v0->posX; - Position[1] = (f32) temp_v0->posY; - Position[2] = (f32) temp_v0->posZ; + SpawnType = static_cast<ACar::SpawnMode>(params.Type.value_or(0)); + switch(SpawnType) { + case SpawnMode::POINT: // Spawn car at a specific path point + PathPoint = params.PathPoint.value_or(0); + CarCounts[PathIndex].push_back(PathPoint); + break; + case SpawnMode::AUTO: // Automatically distribute cars based on a specific path point + printf("vehicle path size %d\n", gVehiclePathSize); + PathPoint = GetVehiclePathPointDistributed(CarCounts[PathIndex], gVehiclePathSize); + CarCounts[PathIndex].push_back(PathPoint); + printf("train spawn path point: %d\n", PathPoint); + break; + } + + waypointOffset = PathPoint; + temp_v0 = &gTrackPaths[PathIndex][PathPoint]; + Position[0] = (f32) temp_v0->x; + Position[1] = (f32) temp_v0->y; + Position[2] = (f32) temp_v0->z; ActorIndex = -1; WaypointIndex = waypointOffset; Velocity[0] = 0.0f; @@ -43,9 +65,9 @@ ACar::ACar(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint) { } SomeMultiplierTheSequel = (f32) ((f64) (f32) (SomeType - 1) * 0.6); if (((gCCSelection > CC_50) || (gModeSelection == TIME_TRIALS)) && (SomeType == 2)) { - Speed = speedA; + Speed = params.Speed.value_or(0); } else { - Speed = speedB; + Speed = params.SpeedB.value_or(0); } Rotation[0] = 0; Rotation[2] = 0; @@ -62,6 +84,15 @@ ACar::ACar(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint) { _count++; } +void ACar::SetSpawnParams(SpawnParams& params) { + params.Name = "mk:car"; + params.Type = static_cast<uint16_t>(SpawnType); + params.PathIndex = PathIndex; + params.PathPoint = PathPoint; + params.Speed = Speed; + params.SpeedB = SpeedB; +} + bool ACar::IsMod() { return true; } @@ -279,4 +310,68 @@ void ACar::VehicleCollision(s32 playerId, Player* player) { } } } -}
\ No newline at end of file +} + +void ACar::DrawEditorProperties() { + ImGui::Text("Spawn Mode"); + ImGui::SameLine(); + + int32_t type = static_cast<int32_t>(SpawnType); + const char* items[] = { "POINT", "AUTO" }; + + if (ImGui::Combo("##Type", &type, items, IM_ARRAYSIZE(items))) { + SpawnType = static_cast<ACar::SpawnMode>(type); + } + + if (type == ACar::SpawnMode::POINT) { + ImGui::Text("Path Index"); + ImGui::SameLine(); + + int pathIndex = static_cast<int>(PathIndex); + if (ImGui::InputInt("##PathIndex", &pathIndex)) { + if (pathIndex < 0) pathIndex = 0; + PathIndex = static_cast<uint32_t>(pathIndex); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathIndex")) { + PathIndex = 0; + } + + ImGui::Text("Path Point"); + ImGui::SameLine(); + + int pathPoint = static_cast<int>(PathPoint); + if (ImGui::InputInt("##PathPoint", &pathPoint)) { + if (pathPoint < 0) pathPoint = 0; + PathPoint = static_cast<uint32_t>(pathPoint); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathPoint")) { + PathPoint = 0; + } + } + + ImGui::Text("Speed"); + ImGui::SameLine(); + + float speed = Speed; + if (ImGui::DragFloat("##Speed", &speed, 0.1f)) { + Speed = speed; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeed")) { + Speed = 0.0f; + } + + ImGui::Text("SpeedB"); + ImGui::SameLine(); + + float speed2 = SpeedB; + if (ImGui::DragFloat("##SpeedB", &speed2, 0.1f)) { + SpeedB = speed2; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeedB")) { + SpeedB = 0.0f; + } +} diff --git a/src/engine/vehicles/Car.h b/src/engine/vehicles/Car.h index 24badabb3..f14af00ac 100644 --- a/src/engine/vehicles/Car.h +++ b/src/engine/vehicles/Car.h @@ -3,6 +3,9 @@ #include <libultraship.h> #include "Actor.h" #include <vector> +#include "engine/SpawnParams.h" +#include "engine/CoreMath.h" +#include "engine/World.h" extern "C" { #include "main.h" @@ -13,7 +16,12 @@ extern "C" { class ACar : public AActor { public: - explicit ACar(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint); + enum SpawnMode : uint16_t { + POINT, // Spawn car at a specific path point + AUTO, // Automatically distribute cars based on a specific path point + }; + + explicit ACar(const SpawnParams& params); ~ACar() { _count--; @@ -23,6 +31,19 @@ class ACar : public AActor { return _count; } + // This is simply a helper function to keep Spawning code clean + static inline ACar* Spawn(f32 speedA, f32 speedB, uint32_t pathIndex, uint32_t pathPoint, ACar::SpawnMode spawnMode) { + SpawnParams params = { + .Name = "mk:car", + .Type = static_cast<uint16_t>(spawnMode), + .PathIndex = pathIndex, + .PathPoint = pathPoint, + .Speed = speedA, + .SpeedB = speedB + }; + return static_cast<ACar*>(gWorldInstance.AddActor(new ACar(params))); + } + const char* Type; size_t Index; f32 Speed; @@ -40,11 +61,19 @@ class ACar : public AActor { f32 SomeArg4 = 8.5f; u32 SoundBits = SOUND_ARG_LOAD(0x51, 0x01, 0x80, 0x05); + ACar::SpawnMode SpawnType = ACar::SpawnMode::AUTO; + float SpeedB = 0.0f; + uint32_t PathIndex = 0; + uint32_t PathPoint = 0; + + virtual void SetSpawnParams(SpawnParams& params) override; virtual void Tick() override; virtual void Draw(Camera*) override; virtual void VehicleCollision(s32 playerId, Player* player) override; virtual bool IsMod() override; + virtual void DrawEditorProperties() override; private: static size_t _count; + static std::map<uint32_t, std::vector<uint32_t>> CarCounts; };
\ No newline at end of file diff --git a/src/engine/vehicles/TankerTruck.cpp b/src/engine/vehicles/TankerTruck.cpp index ed28b0d66..f8a3d3d2a 100644 --- a/src/engine/vehicles/TankerTruck.cpp +++ b/src/engine/vehicles/TankerTruck.cpp @@ -1,6 +1,8 @@ #include <libultraship.h> #include "TankerTruck.h" #include <vector> +#include "engine/vehicles/Utils.h" +#include "port/Game.h" extern "C" { #include "macros.h" @@ -17,20 +19,39 @@ extern s8 gPlayerCount; } size_t ATankerTruck::_count = 0; +std::map<uint32_t, std::vector<uint32_t>> ATankerTruck::TruckCounts; -ATankerTruck::ATankerTruck(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint) { +ATankerTruck::ATankerTruck(const SpawnParams& params) : AActor(params) { Name = "Tanker Truck"; + ResourceName = "mk:tanker_truck"; + BoundingBoxSize = 2.0f; TrackPathPoint* temp_v0; u16 waypointOffset; s32 numWaypoints = gPathCountByPathIndex[0]; Index = _count; + PathIndex = params.PathIndex.value_or(0); + PathPoint = 0; - waypointOffset = waypoint; - temp_v0 = &path[waypointOffset]; - Position[0] = (f32) temp_v0->posX; - Position[1] = (f32) temp_v0->posY; - Position[2] = (f32) temp_v0->posZ; + ATankerTruck::SpawnMode spawnMode = static_cast<ATankerTruck::SpawnMode>(params.Type.value_or(0)); + switch(spawnMode) { + case SpawnMode::POINT: // Spawn truck at a specific path point + PathPoint = params.PathPoint.value_or(0); + TruckCounts[PathIndex].push_back(PathPoint); + break; + case SpawnMode::AUTO: // Automatically distribute trucks based on a specific path point + printf("vehicle path size %d\n", gVehiclePathSize); + PathPoint = GetVehiclePathPointDistributed(TruckCounts[PathIndex], gVehiclePathSize); + TruckCounts[PathIndex].push_back(PathPoint); + printf("train spawn path point: %d\n", PathPoint); + break; + } + + waypointOffset = PathPoint; + temp_v0 = &gTrackPaths[PathIndex][PathPoint]; + Position[0] = (f32) temp_v0->x; + Position[1] = (f32) temp_v0->y; + Position[2] = (f32) temp_v0->z; ActorIndex = -1; WaypointIndex = waypointOffset; Velocity[0] = 0.0f; @@ -43,9 +64,9 @@ ATankerTruck::ATankerTruck(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_ } SomeMultiplierTheSequel = (f32) ((f64) (f32) (SomeType - 1) * 0.6); if (((gCCSelection > CC_50) || (gModeSelection == TIME_TRIALS)) && (SomeType == 2)) { - Speed = speedA; + Speed = params.Speed.value_or(0); } else { - Speed = speedB; + Speed = params.SpeedB.value_or(0); } Rotation[0] = 0; Rotation[2] = 0; @@ -62,6 +83,15 @@ ATankerTruck::ATankerTruck(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_ _count++; } +void ATankerTruck::SetSpawnParams(SpawnParams& params) { + params.Name = ResourceName; + params.Type = static_cast<uint16_t>(SpawnType); + params.PathIndex = PathIndex; + params.PathPoint = PathPoint; + params.Speed = Speed; + params.SpeedB = SpeedB; +} + bool ATankerTruck::IsMod() { return true; } @@ -280,3 +310,67 @@ void ATankerTruck::VehicleCollision(s32 playerId, Player* player) { } } } + +void ATankerTruck::DrawEditorProperties() { + ImGui::Text("Spawn Mode"); + ImGui::SameLine(); + + int32_t type = static_cast<int32_t>(SpawnType); + const char* items[] = { "POINT", "AUTO" }; + + if (ImGui::Combo("##Type", &type, items, IM_ARRAYSIZE(items))) { + SpawnType = static_cast<ATankerTruck::SpawnMode>(type); + } + + if (SpawnType == ATankerTruck::SpawnMode::POINT) { + ImGui::Text("Path Index"); + ImGui::SameLine(); + + int pathIndex = static_cast<int>(PathIndex); + if (ImGui::InputInt("##PathIndex", &pathIndex)) { + if (pathIndex < 0) pathIndex = 0; + PathIndex = static_cast<uint32_t>(pathIndex); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathIndex")) { + PathIndex = 0; + } + + ImGui::Text("Path Point"); + ImGui::SameLine(); + + int pathPoint = static_cast<int>(PathPoint); + if (ImGui::InputInt("##PathPoint", &pathPoint)) { + if (pathPoint < 0) pathPoint = 0; + PathPoint = static_cast<uint32_t>(pathPoint); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathPoint")) { + PathPoint = 0; + } + } + + ImGui::Text("Speed"); + ImGui::SameLine(); + + float speed = Speed; + if (ImGui::DragFloat("##Speed", &speed, 0.1f)) { + Speed = speed; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeed")) { + Speed = 0.0f; + } + + ImGui::Text("SpeedB"); + ImGui::SameLine(); + + float speed2 = SpeedB; + if (ImGui::DragFloat("##SpeedB", &speed2, 0.1f)) { + SpeedB = speed2; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeedB")) { + SpeedB = 0.0f; + } +}
\ No newline at end of file diff --git a/src/engine/vehicles/TankerTruck.h b/src/engine/vehicles/TankerTruck.h index 85b501ead..e8fdb7448 100644 --- a/src/engine/vehicles/TankerTruck.h +++ b/src/engine/vehicles/TankerTruck.h @@ -3,6 +3,9 @@ #include <libultraship.h> #include "Actor.h" #include <vector> +#include "engine/SpawnParams.h" +#include "engine/CoreMath.h" +#include "engine/World.h" extern "C" { #include "main.h" @@ -13,6 +16,11 @@ extern "C" { class ATankerTruck : public AActor { public: + enum SpawnMode : uint16_t { + POINT, // Spawn car at a specific path point + AUTO, // Automatically distribute cars based on a specific path point + }; + const char* Type; size_t Index; f32 Speed; @@ -30,7 +38,20 @@ class ATankerTruck : public AActor { f32 SomeArg4 = 12.5f; u32 SoundBits = SOUND_ARG_LOAD(0x51, 0x01, 0x80, 0x03); - explicit ATankerTruck(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint); + // This is simply a helper function to keep Spawning code clean + static inline ATankerTruck* Spawn(f32 speedA, f32 speedB, uint32_t pathIndex, uint32_t pathPoint, ATankerTruck::SpawnMode spawnMode) { + SpawnParams params = { + .Name = "mk:tanker_truck", + .Type = static_cast<uint16_t>(spawnMode), + .PathIndex = pathIndex, + .PathPoint = pathPoint, + .Speed = speedA, + .SpeedB = speedB + }; + return static_cast<ATankerTruck*>(gWorldInstance.AddActor(new ATankerTruck(params))); + } + + explicit ATankerTruck(const SpawnParams& params); ~ATankerTruck() { _count--; @@ -40,11 +61,19 @@ class ATankerTruck : public AActor { return _count; } + ATankerTruck::SpawnMode SpawnType = ATankerTruck::SpawnMode::AUTO; + float SpeedB = 0.0f; + uint32_t PathIndex = 0; + uint32_t PathPoint = 0; + + virtual void SetSpawnParams(SpawnParams& params) override; virtual void Tick() override; virtual void Draw(Camera* camera) override; virtual void VehicleCollision(s32 playerId, Player* player) override; virtual bool IsMod() override; + virtual void DrawEditorProperties() override; private: static size_t _count; + static std::map<uint32_t, std::vector<uint32_t>> TruckCounts; };
\ No newline at end of file diff --git a/src/engine/vehicles/Train.cpp b/src/engine/vehicles/Train.cpp index ae249be81..6e0738033 100644 --- a/src/engine/vehicles/Train.cpp +++ b/src/engine/vehicles/Train.cpp @@ -4,6 +4,11 @@ #include "Train.h" #include <vector> +#include "engine/courses/Course.h" +#include "engine/vehicles/Utils.h" +#include "engine/World.h" +#include "port/Game.h" + extern "C" { #include "macros.h" #include "main.h" @@ -22,59 +27,77 @@ extern "C" { // #include "common_structs.h" } +// The two counts are so we can spawn trains at specific points or use auto distribution size_t ATrain::_count = 0; +// pathIndex, array of spawn points +std::map<uint32_t, std::vector<uint32_t>> ATrain::TrainCounts; -ATrain::ATrain(ATrain::TenderStatus tender, size_t numCarriages, f32 speed, uint32_t waypoint) { +ATrain::ATrain(const SpawnParams& params) : AActor(params) { Name = "Train"; - u16 waypointOffset; + ResourceName = "mk:train"; + BoundingBoxSize = 2.0f; TrainCarStuff* ptr1; - Path2D* pos; + TrackPathPoint* pos; Index = _count; - Speed = speed; + + PassengerCarsCount = params.Count.value_or(0); + bool tender = params.Bool.value_or(true); + + // The path to spawn the train at + uint32_t pathIndex = params.PathIndex.value_or(0); + // The point along the path to spawn the train at + uint32_t pathPoint = 0; + + SpawnType = static_cast<SpawnMode>(params.Type.value_or(SpawnMode::POINT)); + + switch(SpawnType) { + case SpawnMode::POINT: // Spawn train at a specific path point + pathPoint = params.PathPoint.value_or(0); + TrainCounts[pathIndex].push_back(pathPoint); + break; + case SpawnMode::AUTO: // Automatically distribute trains based on a specific path point + printf("vehicle path size %d\n", gVehiclePathSize); + pathPoint = GetVehiclePathPointDistributed(TrainCounts[pathIndex], gVehiclePathSize); + TrainCounts[pathIndex].push_back(pathPoint); + printf("train spawn path point: %d\n", pathPoint); + break; + } // Set to the default value std::fill(SmokeParticles, SmokeParticles + 128, NULL_OBJECT_ID); - for (size_t i = 0; i < numCarriages; i++) { + for (size_t i = 0; i < PassengerCarsCount; i++) { PassengerCars.push_back(TrainCarStuff()); } - // outputs 160 or 392 depending on the train. - // Wraps the value around to always output a valid waypoint. - waypointOffset = waypoint; - // 120.0f is about the maximum usable value for (size_t i = 0; i < PassengerCars.size(); i++) { - waypointOffset += 4; + pathPoint += 4; ptr1 = &PassengerCars[i]; - pos = &gVehicle2DPathPoint[waypointOffset]; - set_vehicle_pos_path_point(ptr1, pos, waypointOffset); + pos = &gVehicle2DPathPoint[pathPoint]; + set_vehicle_pos_path_point(ptr1, pos, pathPoint); } // Smaller offset for the tender - waypointOffset += 3; - pos = &gVehicle2DPathPoint[waypointOffset]; - set_vehicle_pos_path_point(&this->Tender, pos, waypointOffset); - waypointOffset += 4; - pos = &gVehicle2DPathPoint[waypointOffset]; - set_vehicle_pos_path_point(&Locomotive, pos, waypointOffset); + pathPoint += 3; + pos = &gVehicle2DPathPoint[pathPoint]; + set_vehicle_pos_path_point(&this->Tender, pos, pathPoint); + pathPoint += 4; + pos = &gVehicle2DPathPoint[pathPoint]; + set_vehicle_pos_path_point(&Locomotive, pos, pathPoint); // Only use locomotive unless overwritten below. - NumCars = LOCOMOTIVE_ONLY; - // Fall back in-case someone tries to spawn a train with carriages but no tender; not allowed. - if (numCarriages > 0) { + if (PassengerCarsCount > 0) { tender = HAS_TENDER; } Tender.isActive = static_cast<bool>(tender); - for (size_t i = 0; i < numCarriages; i++) { + for (size_t i = 0; i < PassengerCarsCount; i++) { PassengerCars[i].isActive = 1; } - NumCars = NUM_TENDERS + numCarriages; - AnotherSmokeTimer = 0; TrainCarStuff* tempLocomotive; @@ -126,6 +149,17 @@ ATrain::ATrain(ATrain::TenderStatus tender, size_t numCarriages, f32 speed, uint _count++; } +void ATrain::SetSpawnParams(SpawnParams& params) { + AActor::SetSpawnParams(params); + params.Name = "mk:train"; + params.Type = static_cast<uint16_t>(SpawnType); + params.Bool = Tender.isActive; + params.Speed = Speed; + params.Count = PassengerCarsCount; + params.PathIndex = PathIndex; + params.PathPoint = PathPoint; +} + bool ATrain::IsMod() { return true; } @@ -144,29 +178,31 @@ void ATrain::SyncComponents(TrainCarStuff* trainCar, s16 orientationY) { trainCarActor->rot[1] = orientationY; } trainCarActor->velocity[0] = trainCar->velocity[0]; + trainCarActor->velocity[1] = trainCar->velocity[1]; trainCarActor->velocity[2] = trainCar->velocity[2]; } void ATrain::Tick() { - f32 temp_f20; TrainCarStuff* car; u16 oldWaypointIndex; s16 orientationYUpdate; - f32 temp_f22; s32 j; Vec3f smokePos; + FVector temp_f20 = { + Locomotive.position[0], + Locomotive.position[1], + Locomotive.position[2] + }; AnotherSmokeTimer += 1; oldWaypointIndex = (u16) Locomotive.waypointIndex; - temp_f20 = Locomotive.position[0]; - temp_f22 = Locomotive.position[2]; - orientationYUpdate = update_vehicle_following_path(Locomotive.position, (s16*) &Locomotive.waypointIndex, Speed); - Locomotive.velocity[0] = Locomotive.position[0] - temp_f20; - Locomotive.velocity[2] = Locomotive.position[2] - temp_f22; + Locomotive.velocity[0] = Locomotive.position[0] - temp_f20.x; + Locomotive.velocity[1] = Locomotive.position[1] - temp_f20.y; + Locomotive.velocity[2] = Locomotive.position[2] - temp_f20.z; sync_train_components(&Locomotive, orientationYUpdate); @@ -191,23 +227,27 @@ void ATrain::Tick() { car = &Tender; if (car->isActive == 1) { - temp_f20 = car->position[0]; - temp_f22 = car->position[2]; + temp_f20.x = car->position[0]; + temp_f20.y = car->position[1]; + temp_f20.z = car->position[2]; orientationYUpdate = update_vehicle_following_path(car->position, (s16*) &car->waypointIndex, Speed); - car->velocity[0] = car->position[0] - temp_f20; - car->velocity[2] = car->position[2] - temp_f22; + car->velocity[0] = car->position[0] - temp_f20.x; + car->velocity[1] = car->position[1] - temp_f20.y; + car->velocity[2] = car->position[2] - temp_f20.z; sync_train_components(car, orientationYUpdate); } for (j = 0; j < PassengerCars.size(); j++) { car = &PassengerCars[j]; if (car->isActive == 1) { - temp_f20 = car->position[0]; - temp_f22 = car->position[2]; + temp_f20.x = car->position[0]; + temp_f20.y = car->position[1]; + temp_f20.z = car->position[2]; orientationYUpdate = update_vehicle_following_path(car->position, (s16*) &car->waypointIndex, Speed); - car->velocity[0] = car->position[0] - temp_f20; - car->velocity[2] = car->position[2] - temp_f22; + car->velocity[0] = car->position[0] - temp_f20.x; + car->velocity[1] = car->position[1] - temp_f20.y; + car->velocity[2] = car->position[2] - temp_f20.z; sync_train_components(car, orientationYUpdate); } } @@ -274,3 +314,81 @@ s32 ATrain::AddSmoke(s32 trainIndex, Vec3f pos, f32 velocity) { } return objectIndex; } + +void ATrain::DrawEditorProperties() { + ImGui::Text("Passenger Cars"); + ImGui::SameLine(); + + int count = static_cast<int>(PassengerCarsCount); + if (ImGui::InputInt("##Count", &count)) { + // Clamp to uint32_t range (only lower bound needed if assuming positive values) + if (count < 0) count = 0; + PassengerCarsCount = static_cast<uint32_t>(count); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetCount")) { + PassengerCarsCount = 0; + } + + ImGui::Text("Spawn Mode"); + ImGui::SameLine(); + + int32_t type = static_cast<int32_t>(SpawnType); + const char* items[] = { "POINT", "AUTO" }; + + if (ImGui::Combo("##Type", &type, items, IM_ARRAYSIZE(items))) { + SpawnType = static_cast<ATrain::SpawnMode>(type); + } + + if (type == ATrain::SpawnMode::POINT) { + ImGui::Text("Path Index"); + ImGui::SameLine(); + + int pathIndex = static_cast<int>(PathIndex); + if (ImGui::InputInt("##PathIndex", &pathIndex)) { + if (pathIndex < 0) pathIndex = 0; + PathIndex = static_cast<uint32_t>(pathIndex); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathIndex")) { + PathIndex = 0; + } + + ImGui::Text("Path Point"); + ImGui::SameLine(); + + int pathPoint = static_cast<int>(PathPoint); + if (ImGui::InputInt("##PathPoint", &pathPoint)) { + if (pathPoint < 0) pathPoint = 0; + PathPoint = static_cast<uint32_t>(pathPoint); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathPoint")) { + PathPoint = 0; + } + } + + ImGui::Text("Has Tender"); + ImGui::SameLine(); + + bool theBool = HasTender; + if (ImGui::Checkbox("##Bool", &theBool)) { + HasTender = static_cast<TenderStatus>(theBool); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetBool")) { + HasTender = TenderStatus::NO_TENDER; + } + + ImGui::Text("Speed"); + ImGui::SameLine(); + + float speed = Speed; + if (ImGui::DragFloat("##Speed", &speed, 0.1f)) { + Speed = speed; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeed")) { + Speed = 0.0f; + } +} diff --git a/src/engine/vehicles/Train.h b/src/engine/vehicles/Train.h index b5100586e..1013ed4e4 100644 --- a/src/engine/vehicles/Train.h +++ b/src/engine/vehicles/Train.h @@ -1,8 +1,15 @@ #pragma once #include <libultraship.h> -#include "Actor.h" #include <vector> +#include "engine/SpawnParams.h" +#include "engine/CoreMath.h" +#include "engine/World.h" + +#include "Actor.h" + +class World; +extern World gWorldInstance; extern "C" { #include "main.h" @@ -16,6 +23,11 @@ extern "C" { class ATrain : public AActor { public: + enum SpawnMode : uint16_t { + POINT, // Spawn train at a specific path point + AUTO, // Automatically distribute trains based on a specific path point + }; + enum TenderStatus { NO_TENDER, HAS_TENDER, @@ -24,12 +36,14 @@ class ATrain : public AActor { TrainCarStuff Locomotive; TrainCarStuff Tender; std::vector<TrainCarStuff> PassengerCars; - f32 Speed; // 120.0f is about the maximum usable value + size_t PassengerCarsCount = 0; + ATrain::SpawnMode SpawnType = ATrain::SpawnMode::AUTO; + uint32_t PathIndex = 0; + uint32_t PathPoint = 0; + TenderStatus HasTender = TenderStatus::NO_TENDER; s32 SomeFlags; f32 SomeMultiplier; - size_t NumCars; // Non-locomotive car count? - const char* Type = "mk:train"; size_t Index; // Spawns the train in halves of the train path int32_t SmokeParticles[128]; @@ -37,7 +51,7 @@ class ATrain : public AActor { int16_t AnotherSmokeTimer = 0; int16_t SmokeTimer = 0; - explicit ATrain(ATrain::TenderStatus tender, size_t numCarriages, f32 speed, uint32_t waypoint); + explicit ATrain(const SpawnParams& params); ~ATrain() { _count--; @@ -47,13 +61,31 @@ class ATrain : public AActor { return _count; } + // This is simply a helper function to keep Spawning code clean + static inline ATrain* Spawn(ATrain::TenderStatus tender, size_t numCarriages, f32 speed, uint32_t pathIndex, uint32_t pathPoint, ATrain::SpawnMode spawnMode) { + SpawnParams params = { + .Name = "mk:train", + .Type = static_cast<int16_t>(spawnMode), + .Count = numCarriages, + .PathIndex = pathIndex, + .PathPoint = pathPoint, + .Bool = tender, + .Speed = speed, // 120.0f is about the maximum usable value + }; + return static_cast<ATrain*>(gWorldInstance.AddActor(new ATrain(params))); + } + + virtual void SetSpawnParams(SpawnParams& params); virtual void Tick() override; virtual void Draw(Camera* camera) override; virtual void VehicleCollision(s32 playerId, Player* player) override; virtual bool IsMod() override; s32 AddSmoke(s32 trainIndex, Vec3f pos, f32 velocity); void SyncComponents(TrainCarStuff* trainCar, s16 orientationY); + virtual void DrawEditorProperties() override; private: - static size_t _count; -};
\ No newline at end of file + static size_t _count; // Total number of spawned trains +// pathIndex, array of spawn points + static std::map<uint32_t, std::vector<uint32_t>> TrainCounts; +}; diff --git a/src/engine/vehicles/Truck.cpp b/src/engine/vehicles/Truck.cpp index 2c7147d3e..76914418b 100644 --- a/src/engine/vehicles/Truck.cpp +++ b/src/engine/vehicles/Truck.cpp @@ -1,6 +1,8 @@ #include <libultraship.h> #include "Truck.h" #include <vector> +#include "engine/vehicles/Utils.h" +#include "port/Game.h" extern "C" { #include "macros.h" @@ -17,20 +19,39 @@ extern s8 gPlayerCount; } size_t ATruck::_count = 0; +std::map<uint32_t, std::vector<uint32_t>> ATruck::TruckCounts; -ATruck::ATruck(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint) { +ATruck::ATruck(const SpawnParams& params) : AActor(params) { Name = "Truck"; + ResourceName = "mk:truck"; + BoundingBoxSize = 2.0f; TrackPathPoint* temp_v0; u16 waypointOffset; s32 numWaypoints = gPathCountByPathIndex[0]; Index = _count; + PathIndex = params.PathIndex.value_or(0); + PathPoint = 0; - waypointOffset = waypoint; - temp_v0 = &path[waypointOffset]; - Position[0] = (f32) temp_v0->posX; - Position[1] = (f32) temp_v0->posY; - Position[2] = (f32) temp_v0->posZ; + SpawnType = static_cast<ATruck::SpawnMode>(params.Type.value_or(0)); + switch(SpawnType) { + case SpawnMode::POINT: // Spawn truck at a specific path point + PathPoint = params.PathPoint.value_or(0); + TruckCounts[PathIndex].push_back(PathPoint); + break; + case SpawnMode::AUTO: // Automatically distribute trucks based on a specific path point + printf("vehicle path size %d\n", gVehiclePathSize); + PathPoint = GetVehiclePathPointDistributed(TruckCounts[PathIndex], gVehiclePathSize); + TruckCounts[PathIndex].push_back(PathPoint); + printf("train spawn path point: %d\n", PathPoint); + break; + } + + waypointOffset = PathPoint; + temp_v0 = &gTrackPaths[PathIndex][PathPoint]; + Position[0] = (f32) temp_v0->x; + Position[1] = (f32) temp_v0->y; + Position[2] = (f32) temp_v0->z; ActorIndex = -1; WaypointIndex = waypointOffset; Velocity[0] = 0.0f; @@ -43,9 +64,9 @@ ATruck::ATruck(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint) } SomeMultiplierTheSequel = (f32) ((f64) (f32) (SomeType - 1) * 0.6); if (((gCCSelection > CC_50) || (gModeSelection == TIME_TRIALS)) && (SomeType == 2)) { - Speed = speedA; + Speed = params.Speed.value_or(0); } else { - Speed = speedB; + Speed = params.SpeedB.value_or(0); } Rotation[0] = 0; Rotation[2] = 0; @@ -62,6 +83,15 @@ ATruck::ATruck(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint) _count++; } +void ATruck::SetSpawnParams(SpawnParams& params) { + params.Name = "mk:truck"; + params.Type = static_cast<uint16_t>(SpawnType); + params.PathIndex = PathIndex; + params.PathPoint = PathPoint; + params.Speed = Speed; + params.SpeedB = SpeedB; +} + bool ATruck::IsMod() { return true; } @@ -280,3 +310,67 @@ void ATruck::VehicleCollision(s32 playerId, Player* player) { } } } + +void ATruck::DrawEditorProperties() { + ImGui::Text("Spawn Mode"); + ImGui::SameLine(); + + int32_t type = static_cast<int32_t>(SpawnType); + const char* items[] = { "POINT", "AUTO" }; + + if (ImGui::Combo("##Type", &type, items, IM_ARRAYSIZE(items))) { + SpawnType = static_cast<ATruck::SpawnMode>(type); + } + + if (SpawnType == ATruck::SpawnMode::POINT) { + ImGui::Text("Path Index"); + ImGui::SameLine(); + + int pathIndex = static_cast<int>(PathIndex); + if (ImGui::InputInt("##PathIndex", &pathIndex)) { + if (pathIndex < 0) pathIndex = 0; + PathIndex = static_cast<uint32_t>(pathIndex); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathIndex")) { + PathIndex = 0; + } + + ImGui::Text("Path Point"); + ImGui::SameLine(); + + int pathPoint = static_cast<int>(PathPoint); + if (ImGui::InputInt("##PathPoint", &pathPoint)) { + if (pathPoint < 0) pathPoint = 0; + PathPoint = static_cast<uint32_t>(pathPoint); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetPathPoint")) { + PathPoint = 0; + } + } + + ImGui::Text("Speed"); + ImGui::SameLine(); + + float speed = Speed; + if (ImGui::DragFloat("##Speed", &speed, 0.1f)) { + Speed = speed; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeed")) { + Speed = 0.0f; + } + + ImGui::Text("SpeedB"); + ImGui::SameLine(); + + float speed2 = SpeedB; + if (ImGui::DragFloat("##SpeedB", &speed2, 0.1f)) { + SpeedB = speed2; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##ResetSpeedB")) { + SpeedB = 0.0f; + } +} diff --git a/src/engine/vehicles/Truck.h b/src/engine/vehicles/Truck.h index c9e6cb5ac..c91fe54d8 100644 --- a/src/engine/vehicles/Truck.h +++ b/src/engine/vehicles/Truck.h @@ -3,6 +3,9 @@ #include <libultraship.h> #include "Actor.h" #include <vector> +#include "engine/SpawnParams.h" +#include "engine/CoreMath.h" +#include "engine/World.h" extern "C" { #include "main.h" @@ -13,6 +16,11 @@ extern "C" { class ATruck : public AActor { public: + enum SpawnMode : uint16_t { + POINT, // Spawn car at a specific path point + AUTO, // Automatically distribute cars based on a specific path point + }; + const char* Type; size_t Index; f32 Speed; @@ -30,7 +38,25 @@ class ATruck : public AActor { f32 SomeArg4 = 12.5f; u32 SoundBits = SOUND_ARG_LOAD(0x51, 0x01, 0x80, 0x03); - explicit ATruck(f32 speedA, f32 speedB, TrackPathPoint* path, uint32_t waypoint); + float SpeedB = 0.0f; + ATruck::SpawnMode SpawnType = ATruck::SpawnMode::AUTO; + uint32_t PathIndex = 0; + uint32_t PathPoint = 0; + + // This is simply a helper function to keep Spawning code clean + static inline ATruck* Spawn(f32 speedA, f32 speedB, uint32_t pathIndex, uint32_t pathPoint, ATruck::SpawnMode spawnMode) { + SpawnParams params = { + .Name = "mk:truck", + .Type = static_cast<uint16_t>(spawnMode), + .PathIndex = pathIndex, + .PathPoint = pathPoint, + .Speed = speedA, + .SpeedB = speedB + }; + return static_cast<ATruck*>(gWorldInstance.AddActor(new ATruck(params))); + } + + explicit ATruck(const SpawnParams& params); ~ATruck() { _count--; @@ -40,11 +66,14 @@ class ATruck : public AActor { return _count; } + virtual void SetSpawnParams(SpawnParams& params) override; virtual void Tick() override; virtual void Draw(Camera* camera) override; virtual void VehicleCollision(s32 playerId, Player* player) override; virtual bool IsMod() override; + virtual void DrawEditorProperties() override; private: static size_t _count; + static std::map<uint32_t, std::vector<uint32_t>> TruckCounts; };
\ No newline at end of file diff --git a/src/engine/vehicles/Utils.cpp b/src/engine/vehicles/Utils.cpp index d2a3ece57..6f51ea731 100644 --- a/src/engine/vehicles/Utils.cpp +++ b/src/engine/vehicles/Utils.cpp @@ -10,3 +10,33 @@ extern "C" { uint32_t CalculateWaypointDistribution(size_t i, uint32_t numVehicles, size_t numWaypoints, uint32_t centerWaypoint) { return (uint32_t)(((i * numWaypoints) / numVehicles) + centerWaypoint) % numWaypoints; } + +uint32_t GetVehiclePathPointDistributed(std::vector<uint32_t>& existingTrains, uint32_t numWaypoints) { + if (existingTrains.empty()) { + return 0; // first train at start + } + + // Sort trains along path + std::sort(existingTrains.begin(), existingTrains.end()); + + if (existingTrains.size() == 1) { + // Place train halfway around the path + return (existingTrains[0] + numWaypoints / 2) % numWaypoints; + } + + uint32_t bestGap = 0; + uint32_t bestPos = 0; + + for (size_t i = 0; i < existingTrains.size(); i++) { + uint32_t start = existingTrains[i]; + uint32_t end = existingTrains[(i + 1) % existingTrains.size()]; + uint32_t gap = (end + numWaypoints - start) % numWaypoints; + + if (gap > bestGap) { + bestGap = gap; + bestPos = (start + gap / 2) % numWaypoints; + } + } + + return bestPos; +} diff --git a/src/engine/vehicles/Utils.h b/src/engine/vehicles/Utils.h index e71c4a288..e30fbed0b 100644 --- a/src/engine/vehicles/Utils.h +++ b/src/engine/vehicles/Utils.h @@ -3,3 +3,4 @@ #include <libultraship.h> uint32_t CalculateWaypointDistribution(size_t i, uint32_t numVehicles, size_t numWaypoints, uint32_t centerWaypoint); +uint32_t GetVehiclePathPointDistributed(std::vector<uint32_t>& existingTrains, uint32_t numWaypoints); |
