summaryrefslogtreecommitdiff
path: root/src/engine
diff options
context:
space:
mode:
authorMegaMech <MegaMech@users.noreply.github.com>2025-11-09 19:07:44 -0700
committerGitHub <noreply@github.com>2025-11-09 19:07:44 -0700
commitde9c5d510175bba11ab8704e67be3a3a94f9149e (patch)
tree34c42dbfe9f2a6eb5fbce5f6a31b532ff2a3f243 /src/engine
parent760fa3bf5226c2a6052a5d4d53fd0e27f2911e60 (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')
-rw-r--r--src/engine/Actor.cpp67
-rw-r--r--src/engine/Actor.h37
-rw-r--r--src/engine/AllActors.h3
-rw-r--r--src/engine/CoreMath.h23
-rw-r--r--src/engine/GarbageCollector.cpp29
-rw-r--r--src/engine/HM_Intro.cpp2
-rw-r--r--src/engine/Matrix.cpp51
-rw-r--r--src/engine/Matrix.h1
-rw-r--r--src/engine/RegisteredActors.cpp372
-rw-r--r--src/engine/RegisteredActors.h3
-rw-r--r--src/engine/Registry.cpp91
-rw-r--r--src/engine/Registry.h39
-rw-r--r--src/engine/Rulesets.cpp33
-rw-r--r--src/engine/SpawnParams.h132
-rw-r--r--src/engine/StaticMeshActor.cpp2
-rw-r--r--src/engine/StaticMeshActor.h1
-rw-r--r--src/engine/World.cpp48
-rw-r--r--src/engine/World.h16
-rw-r--r--src/engine/actors/Banana.cpp15
-rw-r--r--src/engine/actors/Banana.h2
-rw-r--r--src/engine/actors/BowserStatue.cpp1
-rw-r--r--src/engine/actors/BowserStatue.h2
-rw-r--r--src/engine/actors/Cloud.cpp95
-rw-r--r--src/engine/actors/Cloud.h28
-rw-r--r--src/engine/actors/FallingRock.cpp231
-rw-r--r--src/engine/actors/FallingRock.h50
-rw-r--r--src/engine/actors/Finishline.cpp33
-rw-r--r--src/engine/actors/Finishline.h21
-rw-r--r--src/engine/actors/MarioSign.cpp36
-rw-r--r--src/engine/actors/MarioSign.h21
-rw-r--r--src/engine/actors/Ship.cpp125
-rw-r--r--src/engine/actors/Ship.h27
-rw-r--r--src/engine/actors/SpaghettiShip.cpp21
-rw-r--r--src/engine/actors/SpaghettiShip.h17
-rw-r--r--src/engine/actors/Starship.cpp122
-rw-r--r--src/engine/actors/Starship.h25
-rw-r--r--src/engine/actors/Text.cpp588
-rw-r--r--src/engine/actors/Text.h149
-rw-r--r--src/engine/actors/Tree.cpp1
-rw-r--r--src/engine/actors/WarioSign.cpp28
-rw-r--r--src/engine/actors/WarioSign.h17
-rw-r--r--src/engine/courses/BansheeBoardwalk.cpp30
-rw-r--r--src/engine/courses/BigDonut.cpp16
-rw-r--r--src/engine/courses/BlockFort.cpp16
-rw-r--r--src/engine/courses/BowsersCastle.cpp81
-rw-r--r--src/engine/courses/ChocoMountain.cpp21
-rw-r--r--src/engine/courses/Course.cpp50
-rw-r--r--src/engine/courses/Course.h17
-rw-r--r--src/engine/courses/DKJungle.cpp18
-rw-r--r--src/engine/courses/DoubleDeck.cpp16
-rw-r--r--src/engine/courses/FrappeSnowland.cpp54
-rw-r--r--src/engine/courses/Harbour.cpp81
-rw-r--r--src/engine/courses/KalimariDesert.cpp24
-rw-r--r--src/engine/courses/KoopaTroopaBeach.cpp43
-rw-r--r--src/engine/courses/LuigiRaceway.cpp22
-rw-r--r--src/engine/courses/MarioRaceway.cpp36
-rw-r--r--src/engine/courses/MooMooFarm.cpp16
-rw-r--r--src/engine/courses/PodiumCeremony.cpp33
-rw-r--r--src/engine/courses/RainbowRoad.cpp16
-rw-r--r--src/engine/courses/RoyalRaceway.cpp27
-rw-r--r--src/engine/courses/SherbetLand.cpp78
-rw-r--r--src/engine/courses/Skyscraper.cpp16
-rw-r--r--src/engine/courses/TestCourse.cpp83
-rw-r--r--src/engine/courses/ToadsTurnpike.cpp33
-rw-r--r--src/engine/courses/WarioStadium.cpp30
-rw-r--r--src/engine/courses/YoshiValley.cpp61
-rw-r--r--src/engine/editor/Collision.cpp43
-rw-r--r--src/engine/editor/Collision.h6
-rw-r--r--src/engine/editor/Editor.cpp78
-rw-r--r--src/engine/editor/Editor.h11
-rw-r--r--src/engine/editor/EditorMath.cpp16
-rw-r--r--src/engine/editor/GameObject.cpp29
-rw-r--r--src/engine/editor/GameObject.h30
-rw-r--r--src/engine/editor/Gizmo.cpp341
-rw-r--r--src/engine/editor/Gizmo.h11
-rw-r--r--src/engine/editor/Handles.cpp2
-rw-r--r--src/engine/editor/Handles.h2
-rw-r--r--src/engine/editor/Light.cpp29
-rw-r--r--src/engine/editor/Light.h5
-rw-r--r--src/engine/editor/ObjectPicker.cpp178
-rw-r--r--src/engine/editor/ObjectPicker.h7
-rw-r--r--src/engine/editor/SceneManager.cpp142
-rw-r--r--src/engine/editor/SceneManager.h24
-rw-r--r--src/engine/objects/Bat.cpp7
-rw-r--r--src/engine/objects/Bat.h13
-rw-r--r--src/engine/objects/BombKart.cpp222
-rw-r--r--src/engine/objects/BombKart.h37
-rw-r--r--src/engine/objects/Boos.cpp80
-rw-r--r--src/engine/objects/Boos.h23
-rw-r--r--src/engine/objects/ChainChomp.cpp3
-rw-r--r--src/engine/objects/CheepCheep.cpp61
-rw-r--r--src/engine/objects/CheepCheep.h26
-rw-r--r--src/engine/objects/Crab.cpp47
-rw-r--r--src/engine/objects/Crab.h15
-rw-r--r--src/engine/objects/Flagpole.cpp24
-rw-r--r--src/engine/objects/Flagpole.h18
-rw-r--r--src/engine/objects/GrandPrixBalloons.cpp6
-rw-r--r--src/engine/objects/GrandPrixBalloons.h11
-rw-r--r--src/engine/objects/Hedgehog.cpp99
-rw-r--r--src/engine/objects/Hedgehog.h19
-rw-r--r--src/engine/objects/HotAirBalloon.cpp15
-rw-r--r--src/engine/objects/HotAirBalloon.h15
-rw-r--r--src/engine/objects/Object.cpp79
-rw-r--r--src/engine/objects/Object.h21
-rw-r--r--src/engine/objects/Penguin.cpp128
-rw-r--r--src/engine/objects/Penguin.h28
-rw-r--r--src/engine/objects/Podium.cpp5
-rw-r--r--src/engine/objects/Podium.h11
-rw-r--r--src/engine/objects/Seagull.cpp15
-rw-r--r--src/engine/objects/Seagull.h12
-rw-r--r--src/engine/objects/Snowman.cpp54
-rw-r--r--src/engine/objects/Snowman.h14
-rw-r--r--src/engine/objects/Thwomp.cpp185
-rw-r--r--src/engine/objects/Thwomp.h33
-rw-r--r--src/engine/objects/TrashBin.cpp13
-rw-r--r--src/engine/objects/TrashBin.h17
-rw-r--r--src/engine/objects/Trophy.cpp111
-rw-r--r--src/engine/objects/Trophy.h22
-rw-r--r--src/engine/vehicles/Boat.cpp96
-rw-r--r--src/engine/vehicles/Boat.h34
-rw-r--r--src/engine/vehicles/Bus.cpp110
-rw-r--r--src/engine/vehicles/Bus.h31
-rw-r--r--src/engine/vehicles/Car.cpp113
-rw-r--r--src/engine/vehicles/Car.h31
-rw-r--r--src/engine/vehicles/TankerTruck.cpp110
-rw-r--r--src/engine/vehicles/TankerTruck.h31
-rw-r--r--src/engine/vehicles/Train.cpp196
-rw-r--r--src/engine/vehicles/Train.h46
-rw-r--r--src/engine/vehicles/Truck.cpp110
-rw-r--r--src/engine/vehicles/Truck.h31
-rw-r--r--src/engine/vehicles/Utils.cpp30
-rw-r--r--src/engine/vehicles/Utils.h1
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, &centerRot);
- // 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);