feat: Dragonmaw (#1562)

* rigid as heck

* abstract physics creation to separate function

* loading

Update FvRacePillarDServer.cpp

consolidate abcd pillar logic

modularization

Update SimplePhysicsComponent.cpp

Update EntityManager.cpp

Update MovingPlatformComponent.cpp

still need another pass

* geiser works

* columns working finally

* consolidate logic

* constiness

* Update PhantomPhysicsComponent.cpp

* Update PhysicsComponent.cpp

* revert testing code

* add versions info

---------

Co-authored-by: Aaron Kimbre <aronwk.aaron@gmail.com>
This commit is contained in:
David Markowitz 2024-05-10 07:22:26 -07:00 committed by GitHub
parent 07cb19cc30
commit 2ca61c3e57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 611 additions and 214 deletions

View File

@ -1,6 +1,6 @@
PROJECT_VERSION_MAJOR=1 PROJECT_VERSION_MAJOR=1
PROJECT_VERSION_MINOR=1 PROJECT_VERSION_MINOR=2
PROJECT_VERSION_PATCH=1 PROJECT_VERSION_PATCH=0
# Debugging # Debugging
# Set DYNAMIC to 1 to enable the -rdynamic flag for the linker, yielding some symbols in crashlogs. # Set DYNAMIC to 1 to enable the -rdynamic flag for the linker, yielding some symbols in crashlogs.

View File

@ -1840,6 +1840,12 @@ const NiPoint3& Entity::GetPosition() const {
return vehicel->GetPosition(); return vehicel->GetPosition();
} }
auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>();
if (rigidBodyPhantomPhysicsComponent != nullptr) {
return rigidBodyPhantomPhysicsComponent->GetPosition();
}
return NiPoint3Constant::ZERO; return NiPoint3Constant::ZERO;
} }
@ -1868,6 +1874,12 @@ const NiQuaternion& Entity::GetRotation() const {
return vehicel->GetRotation(); return vehicel->GetRotation();
} }
auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>();
if (rigidBodyPhantomPhysicsComponent != nullptr) {
return rigidBodyPhantomPhysicsComponent->GetRotation();
}
return NiQuaternionConstant::IDENTITY; return NiQuaternionConstant::IDENTITY;
} }
@ -1896,6 +1908,12 @@ void Entity::SetPosition(const NiPoint3& position) {
vehicel->SetPosition(position); vehicel->SetPosition(position);
} }
auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>();
if (rigidBodyPhantomPhysicsComponent != nullptr) {
rigidBodyPhantomPhysicsComponent->SetPosition(position);
}
Game::entityManager->SerializeEntity(this); Game::entityManager->SerializeEntity(this);
} }
@ -1924,6 +1942,12 @@ void Entity::SetRotation(const NiQuaternion& rotation) {
vehicel->SetRotation(rotation); vehicel->SetRotation(rotation);
} }
auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>();
if (rigidBodyPhantomPhysicsComponent != nullptr) {
rigidBodyPhantomPhysicsComponent->SetRotation(rotation);
}
Game::entityManager->SerializeEntity(this); Game::entityManager->SerializeEntity(this);
} }

View File

@ -47,7 +47,7 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : PhysicsCompon
m_Direction = NiPoint3(); // * m_DirectionalMultiplier m_Direction = NiPoint3(); // * m_DirectionalMultiplier
if (m_Parent->GetVar<bool>(u"create_physics")) { if (m_Parent->GetVar<bool>(u"create_physics")) {
CreatePhysics(); m_dpEntity = CreatePhysicsLnv(m_Scale, ComponentType);
} }
if (m_Parent->GetVar<bool>(u"respawnVol")) { if (m_Parent->GetVar<bool>(u"respawnVol")) {
@ -89,105 +89,9 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : PhysicsCompon
m_RespawnRot = m_Rotation; m_RespawnRot = m_Rotation;
} }
/* if (!m_dpEntity) {
for (LDFBaseData* data : settings) { m_dpEntity = CreatePhysicsEntity(ComponentType);
if (data) { if (!m_dpEntity) return;
if (data->GetKey() == u"create_physics") {
if (bool(std::stoi(data->GetValueAsString()))) {
CreatePhysics(settings);
}
}
if (data->GetKey() == u"respawnVol") {
if (bool(std::stoi(data->GetValueAsString()))) {
m_IsRespawnVolume = true;
}
}
if (m_IsRespawnVolume) {
if (data->GetKey() == u"rspPos") {
//Joy, we get to split strings!
std::stringstream test(data->GetValueAsString());
std::string segment;
std::vector<std::string> seglist;
while (std::getline(test, segment, '\x1f')) {
seglist.push_back(segment);
}
m_RespawnPos = NiPoint3(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2]));
}
if (data->GetKey() == u"rspRot") {
//Joy, we get to split strings!
std::stringstream test(data->GetValueAsString());
std::string segment;
std::vector<std::string> seglist;
while (std::getline(test, segment, '\x1f')) {
seglist.push_back(segment);
}
m_RespawnRot = NiQuaternion(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2]), std::stof(seglist[3]));
}
}
if (m_Parent->GetLOT() == 4945) // HF - RespawnPoints
{
m_IsRespawnVolume = true;
m_RespawnPos = m_Position;
m_RespawnRot = m_Rotation;
}
}
}
*/
if (!m_HasCreatedPhysics) {
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), eReplicaComponentType::PHANTOM_PHYSICS);
CDPhysicsComponentTable* physComp = CDClientManager::GetTable<CDPhysicsComponentTable>();
if (physComp == nullptr) return;
auto* info = physComp->GetByID(componentID);
if (info == nullptr || info->physicsAsset == "" || info->physicsAsset == "NO_PHYSICS") return;
//temp test
if (info->physicsAsset == "miscellaneous\\misc_phys_10x1x5.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 10.0f, 5.0f, 1.0f);
} else if (info->physicsAsset == "miscellaneous\\misc_phys_640x640.hkx") {
// TODO Fix physics simulation to do simulation at high velocities due to bullet through paper problem...
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 1638.4f, 13.521004f * 2.0f, 1638.4f);
// Move this down by 13.521004 units so it is still effectively at the same height as before
m_Position = m_Position - NiPoint3Constant::UNIT_Y * 13.521004f;
} else if (info->physicsAsset == "env\\trigger_wall_tall.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 10.0f, 25.0f, 1.0f);
} else if (info->physicsAsset == "env\\env_gen_placeholderphysics.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 20.0f, 20.0f, 20.0f);
} else if (info->physicsAsset == "env\\POI_trigger_wall.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 1.0f, 12.5f, 20.0f); // Not sure what the real size is
} else if (info->physicsAsset == "env\\NG_NinjaGo\\env_ng_gen_gate_chamber_puzzle_ceiling_tile_falling_phantom.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 18.0f, 5.0f, 15.0f);
m_Position += m_Rotation.GetForwardVector() * 7.5f;
} else if (info->physicsAsset == "env\\NG_NinjaGo\\ng_flamejet_brick_phantom.HKX") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 1.0f, 1.0f, 12.0f);
m_Position += m_Rotation.GetForwardVector() * 6.0f;
} else if (info->physicsAsset == "env\\Ring_Trigger.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 6.0f, 6.0f, 6.0f);
} else if (info->physicsAsset == "env\\vfx_propertyImaginationBall.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 4.5f);
} else if (info->physicsAsset == "env\\env_won_fv_gas-blocking-volume.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 390.496826f, 111.467964f, 600.821534f, true);
m_Position.y -= (111.467964f * m_Scale) / 2;
} else {
// LOG_DEBUG("This one is supposed to have %s", info->physicsAsset.c_str());
//add fallback cube:
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 2.0f, 2.0f, 2.0f);
}
m_dpEntity->SetScale(m_Scale); m_dpEntity->SetScale(m_Scale);
m_dpEntity->SetRotation(m_Rotation); m_dpEntity->SetRotation(m_Rotation);
m_dpEntity->SetPosition(m_Position); m_dpEntity->SetPosition(m_Position);
@ -201,69 +105,6 @@ PhantomPhysicsComponent::~PhantomPhysicsComponent() {
} }
} }
void PhantomPhysicsComponent::CreatePhysics() {
unsigned char alpha;
unsigned char red;
unsigned char green;
unsigned char blue;
int type = -1;
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
float width = 0.0f; //aka "radius"
float height = 0.0f;
if (m_Parent->HasVar(u"primitiveModelType")) {
type = m_Parent->GetVar<int32_t>(u"primitiveModelType");
x = m_Parent->GetVar<float>(u"primitiveModelValueX");
y = m_Parent->GetVar<float>(u"primitiveModelValueY");
z = m_Parent->GetVar<float>(u"primitiveModelValueZ");
} else {
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), eReplicaComponentType::PHANTOM_PHYSICS);
CDPhysicsComponentTable* physComp = CDClientManager::GetTable<CDPhysicsComponentTable>();
if (physComp == nullptr) return;
auto info = physComp->GetByID(componentID);
if (info == nullptr) return;
type = info->pcShapeType;
width = info->playerRadius;
height = info->playerHeight;
}
switch (type) {
case 1: { //Make a new box shape
NiPoint3 boxSize(x, y, z);
if (x == 0.0f) {
//LU has some weird values, so I think it's best to scale them down a bit
if (height < 0.5f) height = 2.0f;
if (width < 0.5f) width = 2.0f;
//Scale them:
width = width * m_Scale;
height = height * m_Scale;
boxSize = NiPoint3(width, height, width);
}
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), boxSize);
break;
}
}
if (!m_dpEntity) return;
m_dpEntity->SetPosition({ m_Position.x, m_Position.y - (height / 2), m_Position.z });
dpWorld::AddEntity(m_dpEntity);
m_HasCreatedPhysics = true;
}
void PhantomPhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { void PhantomPhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate); PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate);
@ -308,8 +149,9 @@ void ApplyCollisionEffect(const LWOOBJID& target, const ePhysicsEffectType effec
controllablePhysicsComponent->SetGravityScale(effectScale); controllablePhysicsComponent->SetGravityScale(effectScale);
GameMessages::SendSetGravityScale(target, effectScale, targetEntity->GetSystemAddress()); GameMessages::SendSetGravityScale(target, effectScale, targetEntity->GetSystemAddress());
} }
break;
} }
// The other types are not handled by the server
case ePhysicsEffectType::ATTRACT: case ePhysicsEffectType::ATTRACT:
case ePhysicsEffectType::FRICTION: case ePhysicsEffectType::FRICTION:
case ePhysicsEffectType::PUSH: case ePhysicsEffectType::PUSH:
@ -317,6 +159,7 @@ void ApplyCollisionEffect(const LWOOBJID& target, const ePhysicsEffectType effec
default: default:
break; break;
} }
// The other types are not handled by the server and are here to handle all cases of the enum.
} }
void PhantomPhysicsComponent::Update(float deltaTime) { void PhantomPhysicsComponent::Update(float deltaTime) {
@ -356,24 +199,12 @@ void PhantomPhysicsComponent::SetDirection(const NiPoint3& pos) {
m_IsDirectional = true; m_IsDirectional = true;
} }
void PhantomPhysicsComponent::SpawnVertices() { void PhantomPhysicsComponent::SpawnVertices() const {
if (!m_dpEntity) return; if (!m_dpEntity) {
LOG("No dpEntity to spawn vertices for %llu:%i", m_Parent->GetObjectID(), m_Parent->GetLOT());
LOG("%llu", m_Parent->GetObjectID()); return;
auto box = static_cast<dpShapeBox*>(m_dpEntity->GetShape());
for (auto vert : box->GetVertices()) {
LOG("%f, %f, %f", vert.x, vert.y, vert.z);
EntityInfo info;
info.lot = 33;
info.pos = vert;
info.spawner = nullptr;
info.spawnerID = m_Parent->GetObjectID();
info.spawnerNodeID = 0;
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
Game::entityManager->ConstructEntity(newEntity);
} }
PhysicsComponent::SpawnVertices(m_dpEntity);
} }
void PhantomPhysicsComponent::SetDirectionalMultiplier(float mul) { void PhantomPhysicsComponent::SetDirectionalMultiplier(float mul) {

View File

@ -18,6 +18,7 @@ class LDFBaseData;
class Entity; class Entity;
class dpEntity; class dpEntity;
enum class ePhysicsEffectType : uint32_t ; enum class ePhysicsEffectType : uint32_t ;
enum class eReplicaComponentType : uint32_t;
/** /**
* Allows the creation of phantom physics for an entity: a physics object that is generally invisible but can be * Allows the creation of phantom physics for an entity: a physics object that is generally invisible but can be
@ -34,11 +35,6 @@ public:
void Update(float deltaTime) override; void Update(float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
/**
* Creates the physics shape for this entity based on LDF data
*/
void CreatePhysics();
/** /**
* Sets the direction this physics object is pointed at * Sets the direction this physics object is pointed at
* @param pos the direction to set * @param pos the direction to set
@ -109,7 +105,7 @@ public:
/** /**
* Spawns an object at each of the vertices for debugging purposes * Spawns an object at each of the vertices for debugging purposes
*/ */
void SpawnVertices(); void SpawnVertices() const;
/** /**
* Legacy stuff no clue what this does * Legacy stuff no clue what this does
@ -166,11 +162,6 @@ private:
*/ */
dpEntity* m_dpEntity; dpEntity* m_dpEntity;
/**
* Whether or not the physics object has been created yet
*/
bool m_HasCreatedPhysics = false;
/** /**
* Whether or not this physics object represents an object that updates the respawn pos of an entity that crosses it * Whether or not this physics object represents an object that updates the respawn pos of an entity that crosses it
*/ */

View File

@ -1,5 +1,19 @@
#include "PhysicsComponent.h" #include "PhysicsComponent.h"
#include "eReplicaComponentType.h"
#include "NiPoint3.h"
#include "NiQuaternion.h"
#include "CDComponentsRegistryTable.h"
#include "CDPhysicsComponentTable.h"
#include "dpEntity.h"
#include "dpWorld.h"
#include "dpShapeBox.h"
#include "dpShapeSphere.h"
#include "EntityInfo.h"
PhysicsComponent::PhysicsComponent(Entity* parent) : Component(parent) { PhysicsComponent::PhysicsComponent(Entity* parent) : Component(parent) {
m_Position = NiPoint3Constant::ZERO; m_Position = NiPoint3Constant::ZERO;
m_Rotation = NiQuaternionConstant::IDENTITY; m_Rotation = NiQuaternionConstant::IDENTITY;
@ -19,3 +33,190 @@ void PhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitia
if (!bIsInitialUpdate) m_DirtyPosition = false; if (!bIsInitialUpdate) m_DirtyPosition = false;
} }
} }
dpEntity* PhysicsComponent::CreatePhysicsEntity(eReplicaComponentType type) {
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), type);
CDPhysicsComponentTable* physComp = CDClientManager::GetTable<CDPhysicsComponentTable>();
if (physComp == nullptr) return nullptr;
auto* info = physComp->GetByID(componentID);
if (info == nullptr || info->physicsAsset == "" || info->physicsAsset == "NO_PHYSICS") return nullptr;
dpEntity* toReturn;
if (info->physicsAsset == "miscellaneous\\misc_phys_10x1x5.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 10.0f, 5.0f, 1.0f);
} else if (info->physicsAsset == "miscellaneous\\misc_phys_640x640.hkx") {
// TODO Fix physics simulation to do simulation at high velocities due to bullet through paper problem...
toReturn = new dpEntity(m_Parent->GetObjectID(), 1638.4f, 13.521004f * 2.0f, 1638.4f);
// Move this down by 13.521004 units so it is still effectively at the same height as before
m_Position = m_Position - NiPoint3Constant::UNIT_Y * 13.521004f;
} else if (info->physicsAsset == "env\\trigger_wall_tall.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 10.0f, 25.0f, 1.0f);
} else if (info->physicsAsset == "env\\env_gen_placeholderphysics.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 20.0f, 20.0f, 20.0f);
} else if (info->physicsAsset == "env\\POI_trigger_wall.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 1.0f, 12.5f, 20.0f); // Not sure what the real size is
} else if (info->physicsAsset == "env\\NG_NinjaGo\\env_ng_gen_gate_chamber_puzzle_ceiling_tile_falling_phantom.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 18.0f, 5.0f, 15.0f);
m_Position += m_Rotation.GetForwardVector() * 7.5f;
} else if (info->physicsAsset == "env\\NG_NinjaGo\\ng_flamejet_brick_phantom.HKX") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 1.0f, 1.0f, 12.0f);
m_Position += m_Rotation.GetForwardVector() * 6.0f;
} else if (info->physicsAsset == "env\\Ring_Trigger.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 6.0f, 6.0f, 6.0f);
} else if (info->physicsAsset == "env\\vfx_propertyImaginationBall.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 4.5f);
} else if (info->physicsAsset == "env\\env_won_fv_gas-blocking-volume.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 390.496826f, 111.467964f, 600.821534f, true);
m_Position.y -= (111.467964f * m_Parent->GetDefaultScale()) / 2;
} else {
// LOG_DEBUG("This one is supposed to have %s", info->physicsAsset.c_str());
//add fallback cube:
toReturn = new dpEntity(m_Parent->GetObjectID(), 2.0f, 2.0f, 2.0f);
}
return toReturn;
}
dpEntity* PhysicsComponent::CreatePhysicsLnv(const float scale, const eReplicaComponentType type) const {
int pcShapeType = -1;
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
float width = 0.0f; //aka "radius"
float height = 0.0f;
dpEntity* toReturn = nullptr;
if (m_Parent->HasVar(u"primitiveModelType")) {
pcShapeType = m_Parent->GetVar<int32_t>(u"primitiveModelType");
x = m_Parent->GetVar<float>(u"primitiveModelValueX");
y = m_Parent->GetVar<float>(u"primitiveModelValueY");
z = m_Parent->GetVar<float>(u"primitiveModelValueZ");
} else {
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), type);
CDPhysicsComponentTable* physComp = CDClientManager::GetTable<CDPhysicsComponentTable>();
if (physComp == nullptr) return nullptr;
auto info = physComp->GetByID(componentID);
if (info == nullptr) return nullptr;
pcShapeType = info->pcShapeType;
width = info->playerRadius;
height = info->playerHeight;
}
switch (pcShapeType) {
case 0: { // HKX type
break;
}
case 1: { //Make a new box shape
NiPoint3 boxSize(x, y, z);
if (x == 0.0f) {
//LU has some weird values, so I think it's best to scale them down a bit
if (height < 0.5f) height = 2.0f;
if (width < 0.5f) width = 2.0f;
//Scale them:
width = width * scale;
height = height * scale;
boxSize = NiPoint3(width, height, width);
}
toReturn = new dpEntity(m_Parent->GetObjectID(), boxSize);
toReturn->SetPosition({ m_Position.x, m_Position.y - (height / 2), m_Position.z });
break;
}
case 2: { //Make a new cylinder shape
break;
}
case 3: { //Make a new sphere shape
auto [x, y, z] = m_Position;
toReturn = new dpEntity(m_Parent->GetObjectID(), width);
toReturn->SetPosition({ x, y, z });
break;
}
case 4: { //Make a new capsule shape
break;
}
}
if (toReturn) dpWorld::AddEntity(toReturn);
return toReturn;
}
void PhysicsComponent::SpawnVertices(dpEntity* entity) const {
if (!entity) return;
LOG("Spawning vertices for %llu", m_Parent->GetObjectID());
EntityInfo info;
info.lot = 33;
info.spawner = nullptr;
info.spawnerID = m_Parent->GetObjectID();
info.spawnerNodeID = 0;
// These don't use overloaded methods as dPhysics does not link with dGame at the moment.
auto box = dynamic_cast<dpShapeBox*>(entity->GetShape());
if (box) {
for (auto vert : box->GetVertices()) {
LOG("Vertex at %f, %f, %f", vert.x, vert.y, vert.z);
info.pos = vert;
Entity* newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
}
}
auto sphere = dynamic_cast<dpShapeSphere*>(entity->GetShape());
if (sphere) {
auto [x, y, z] = entity->GetPosition(); // Use shapes position instead of the parent's position in case it's different
float plusX = x + sphere->GetRadius();
float minusX = x - sphere->GetRadius();
float plusY = y + sphere->GetRadius();
float minusY = y - sphere->GetRadius();
float plusZ = z + sphere->GetRadius();
float minusZ = z - sphere->GetRadius();
auto radius = sphere->GetRadius();
LOG("Radius: %f", radius);
LOG("Plus Vertices %f %f %f", plusX, plusY, plusZ);
LOG("Minus Vertices %f %f %f", minusX, minusY, minusZ);
info.pos = NiPoint3{ x, plusY, z };
Entity* newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
info.pos = NiPoint3{ x, minusY, z };
newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
info.pos = NiPoint3{ plusX, y, z };
newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
info.pos = NiPoint3{ minusX, y, z };
newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
info.pos = NiPoint3{ x, y, plusZ };
newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
info.pos = NiPoint3{ x, y, minusZ };
newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
info.pos = NiPoint3{ x, y, z };
newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
}
}

View File

@ -9,6 +9,10 @@ namespace Raknet {
class BitStream; class BitStream;
}; };
enum class eReplicaComponentType : uint32_t;
class dpEntity;
class PhysicsComponent : public Component { class PhysicsComponent : public Component {
public: public:
PhysicsComponent(Entity* parent); PhysicsComponent(Entity* parent);
@ -22,6 +26,12 @@ public:
const NiQuaternion& GetRotation() const { return m_Rotation; } const NiQuaternion& GetRotation() const { return m_Rotation; }
virtual void SetRotation(const NiQuaternion& rot) { if (m_Rotation == rot) return; m_Rotation = rot; m_DirtyPosition = true; } virtual void SetRotation(const NiQuaternion& rot) { if (m_Rotation == rot) return; m_Rotation = rot; m_DirtyPosition = true; }
protected: protected:
dpEntity* CreatePhysicsEntity(eReplicaComponentType type);
dpEntity* CreatePhysicsLnv(const float scale, const eReplicaComponentType type) const;
void SpawnVertices(dpEntity* entity) const;
NiPoint3 m_Position; NiPoint3 m_Position;
NiQuaternion m_Rotation; NiQuaternion m_Rotation;

View File

@ -817,8 +817,10 @@ void RacingControlComponent::Update(float deltaTime) {
// Some offset up to make they don't fall through the terrain on a // Some offset up to make they don't fall through the terrain on a
// respawn, seems to fix itself to the track anyhow // respawn, seems to fix itself to the track anyhow
player.respawnPosition = position + NiPoint3Constant::UNIT_Y * 5; if (waypoint.racing.isResetNode) {
player.respawnRotation = vehicle->GetRotation(); player.respawnPosition = position + NiPoint3Constant::UNIT_Y * 5;
player.respawnRotation = vehicle->GetRotation();
}
player.respawnIndex = respawnIndex; player.respawnIndex = respawnIndex;
// Reached the start point, lapped // Reached the start point, lapped

View File

@ -1,16 +1,57 @@
/* // Darkflame Universe
* Darkflame Universe // Copyright 2024
* Copyright 2023
*/
#include "RigidbodyPhantomPhysicsComponent.h" #include "RigidbodyPhantomPhysicsComponent.h"
#include "Entity.h" #include "Entity.h"
#include "dpEntity.h"
#include "CDComponentsRegistryTable.h"
#include "CDPhysicsComponentTable.h"
#include "dpWorld.h"
#include "dpShapeBox.h"
#include "dpShapeSphere.h"
#include"EntityInfo.h"
RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent) : PhysicsComponent(parent) { RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent) : PhysicsComponent(parent) {
m_Position = m_Parent->GetDefaultPosition(); m_Position = m_Parent->GetDefaultPosition();
m_Rotation = m_Parent->GetDefaultRotation(); m_Rotation = m_Parent->GetDefaultRotation();
m_Scale = m_Parent->GetDefaultScale();
if (m_Parent->GetVar<bool>(u"create_physics")) {
m_dpEntity = CreatePhysicsLnv(m_Scale, ComponentType);
if (!m_dpEntity) {
m_dpEntity = CreatePhysicsEntity(ComponentType);
if (!m_dpEntity) return;
m_dpEntity->SetScale(m_Scale);
m_dpEntity->SetRotation(m_Rotation);
m_dpEntity->SetPosition(m_Position);
dpWorld::AddEntity(m_dpEntity);
}
}
} }
void RigidbodyPhantomPhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { void RigidbodyPhantomPhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate); PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate);
} }
void RigidbodyPhantomPhysicsComponent::Update(const float deltaTime) {
if (!m_dpEntity) return;
//Process enter events
for (const auto id : m_dpEntity->GetNewObjects()) {
m_Parent->OnCollisionPhantom(id);
}
//Process exit events
for (const auto id : m_dpEntity->GetRemovedObjects()) {
m_Parent->OnCollisionLeavePhantom(id);
}
}
void RigidbodyPhantomPhysicsComponent::SpawnVertices() const {
if (!m_dpEntity) {
LOG("No dpEntity to spawn vertices for %llu:%i", m_Parent->GetObjectID(), m_Parent->GetLOT());
return;
}
PhysicsComponent::SpawnVertices(m_dpEntity);
}

View File

@ -1,10 +1,8 @@
/* // Darkflame Universe
* Darkflame Universe // Copyright 2024
* Copyright 2023
*/
#ifndef __RIGIDBODYPHANTOMPHYSICS_H__ #ifndef RIGIDBODYPHANTOMPHYSICS_H
#define __RIGIDBODYPHANTOMPHYSICS_H__ #define RIGIDBODYPHANTOMPHYSICS_H
#include "BitStream.h" #include "BitStream.h"
#include "dCommonVars.h" #include "dCommonVars.h"
@ -13,6 +11,8 @@
#include "PhysicsComponent.h" #include "PhysicsComponent.h"
#include "eReplicaComponentType.h" #include "eReplicaComponentType.h"
class dpEntity;
/** /**
* Component that handles rigid bodies that can be interacted with, mostly client-side rendered. An example is the * Component that handles rigid bodies that can be interacted with, mostly client-side rendered. An example is the
* bananas that fall from trees in GF. * bananas that fall from trees in GF.
@ -23,7 +23,15 @@ public:
RigidbodyPhantomPhysicsComponent(Entity* parent); RigidbodyPhantomPhysicsComponent(Entity* parent);
void Update(const float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void SpawnVertices() const;
private:
float m_Scale{};
dpEntity* m_dpEntity{};
}; };
#endif // __RIGIDBODYPHANTOMPHYSICS_H__ #endif // RIGIDBODYPHANTOMPHYSICS_H

View File

@ -369,8 +369,8 @@ void GameMessages::SendPlatformResync(Entity* entity, const SystemAddress& sysAd
const auto lot = entity->GetLOT(); const auto lot = entity->GetLOT();
if (lot == 12341 || lot == 5027 || lot == 5028 || lot == 14335 || lot == 14447 || lot == 14449) { if (lot == 12341 || lot == 5027 || lot == 5028 || lot == 14335 || lot == 14447 || lot == 14449 || lot == 11306 || lot == 11308) {
iDesiredWaypointIndex = 0; iDesiredWaypointIndex = (lot == 11306 || lot == 11308) ? 1 : 0;
iIndex = 0; iIndex = 0;
nextIndex = 0; nextIndex = 0;
bStopAtDesiredWaypoint = true; bStopAtDesiredWaypoint = true;

View File

@ -41,6 +41,7 @@
#include "ScriptedActivityComponent.h" #include "ScriptedActivityComponent.h"
#include "SkillComponent.h" #include "SkillComponent.h"
#include "TriggerComponent.h" #include "TriggerComponent.h"
#include "RigidbodyPhantomPhysicsComponent.h"
// Enums // Enums
#include "eGameMasterLevel.h" #include "eGameMasterLevel.h"
@ -1129,8 +1130,13 @@ namespace DEVGMCommands {
void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args) { void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
//Go tell physics to spawn all the vertices: //Go tell physics to spawn all the vertices:
auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PHANTOM_PHYSICS); auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PHANTOM_PHYSICS);
for (auto en : entities) { for (const auto* en : entities) {
auto phys = static_cast<PhantomPhysicsComponent*>(en->GetComponent(eReplicaComponentType::PHANTOM_PHYSICS)); const auto* phys = static_cast<PhantomPhysicsComponent*>(en->GetComponent(eReplicaComponentType::PHANTOM_PHYSICS));
if (phys)
phys->SpawnVertices();
}
for (const auto* en : Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS)) {
const auto* phys = en->GetComponent<RigidbodyPhantomPhysicsComponent>();
if (phys) if (phys)
phys->SpawnVertices(); phys->SpawnVertices();
} }

View File

@ -1,3 +1,6 @@
set(DSCRIPTS_SOURCES_02_SERVER_MAP_FV_RACING set(DSCRIPTS_SOURCES_02_SERVER_MAP_FV_RACING
"RaceFireballs.cpp"
"RaceMaelstromGeiser.cpp" "RaceMaelstromGeiser.cpp"
"RaceShipLapColumnsServer.cpp"
"FvRacingColumns.cpp"
PARENT_SCOPE) PARENT_SCOPE)

View File

@ -0,0 +1,15 @@
#include "FvRacingColumns.h"
#include "MovingPlatformComponent.h"
void FvRacingColumns::OnStartup(Entity* self) {
auto* movingPlatformComponent = self->GetComponent<MovingPlatformComponent>();
if (!movingPlatformComponent) return;
movingPlatformComponent->StopPathing();
movingPlatformComponent->SetSerialized(true);
int32_t pathStart = 0;
if (self->HasVar(u"attached_path_start")) {
pathStart = self->GetVar<uint32_t>(u"attached_path_start");
}
movingPlatformComponent->WarpToWaypoint(pathStart);
}

View File

@ -0,0 +1,6 @@
#include "CppScripts.h"
class FvRacingColumns : public CppScripts::Script {
public:
void OnStartup(Entity* self) override;
};

View File

@ -0,0 +1,15 @@
#include "RaceFireballs.h"
#include "SkillComponent.h"
void RaceFireballs::OnStartup(Entity* self) {
self->AddTimer("fire", GeneralUtils::GenerateRandomNumber<float>(3.0f, 10.0f));
}
void RaceFireballs::OnTimerDone(Entity* self, std::string timerName) {
if (timerName == "fire") {
auto* skillComponent = self->GetComponent<SkillComponent>();
if (skillComponent) skillComponent->CastSkill(894);
self->AddTimer("fire", GeneralUtils::GenerateRandomNumber<float>(3.0f, 10.0f));
}
}

View File

@ -0,0 +1,9 @@
#pragma once
#include "CppScripts.h"
class RaceFireballs : public CppScripts::Script
{
public:
void OnStartup(Entity* self) override;
void OnTimerDone(Entity* self, std::string timerName) override;
};

View File

@ -0,0 +1,47 @@
#include "RaceShipLapColumnsServer.h"
#include "RacingControlComponent.h"
#include "MovingPlatformComponent.h"
void RaceShipLapColumnsServer::OnStartup(Entity* self) {
self->SetVar(u"Lap2Complete", false);
self->SetVar(u"Lap3Complete", false);
}
void SetMovingToWaypoint(const int32_t waypointIndex, const std::string group) {
const auto entities = Game::entityManager->GetEntitiesInGroup(group);
if (entities.empty()) return;
auto* entity = entities[0];
entity->SetIsGhostingCandidate(false);
auto* movingPlatfromComponent = entity->GetComponent<MovingPlatformComponent>();
if (!movingPlatfromComponent) return;
movingPlatfromComponent->SetSerialized(true);
movingPlatfromComponent->GotoWaypoint(waypointIndex);
Game::entityManager->SerializeEntity(entity);
}
void RaceShipLapColumnsServer::OnCollisionPhantom(Entity* self, Entity* target) {
if (!target) return;
const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL);
if (racingControllers.empty()) return;
auto* racingControlComponent = racingControllers[0]->GetComponent<RacingControlComponent>();
if (!racingControlComponent) return;
const auto* player = racingControlComponent->GetPlayerData(target->GetObjectID());
if (!player) return;
if (player->lap == 1 && !self->GetVar<bool>(u"Lap2Complete")) {
self->SetVar(u"Lap2Complete", true);
SetMovingToWaypoint(1, "Lap2Column");
SetMovingToWaypoint(0, "Lap2Ramp");
} else if (player->lap == 2 && !self->GetVar<bool>(u"Lap3Complete")) {
self->SetVar(u"Lap3Complete", true);
SetMovingToWaypoint(1, "Lap3Column");
SetMovingToWaypoint(0, "Lap3Ramp");
}
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "CppScripts.h"
class RaceShipLapColumnsServer : public CppScripts::Script {
public:
void OnStartup(Entity* self) override;
void OnCollisionPhantom(Entity* self, Entity* target) override;
};

View File

@ -154,6 +154,11 @@
#include "FvBounceOverWall.h" #include "FvBounceOverWall.h"
#include "FvFong.h" #include "FvFong.h"
#include "FvMaelstromGeyser.h" #include "FvMaelstromGeyser.h"
#include "FvRaceDragon.h"
#include "FvRacePillarABCServer.h"
#include "FvRacePillarDServer.h"
#include "RaceFireballs.h"
#include "RaceShipLapColumnsServer.h"
// FB Scripts // FB Scripts
#include "AgJetEffectServer.h" #include "AgJetEffectServer.h"
@ -179,6 +184,7 @@
#include "RaceMaelstromGeiser.h" #include "RaceMaelstromGeiser.h"
#include "FvRaceSmashEggImagineServer.h" #include "FvRaceSmashEggImagineServer.h"
#include "RaceSmashServer.h" #include "RaceSmashServer.h"
#include "FvRacingColumns.h"
// NT Scripts // NT Scripts
#include "NtSentinelWalkwayServer.h" #include "NtSentinelWalkwayServer.h"
@ -622,9 +628,25 @@ CppScripts::Script* const CppScripts::GetScript(Entity* parent, const std::strin
script = new FvBounceOverWall(); script = new FvBounceOverWall();
else if (scriptName == "scripts\\02_server\\Map\\FV\\L_NPC_FONG.lua") else if (scriptName == "scripts\\02_server\\Map\\FV\\L_NPC_FONG.lua")
script = new FvFong(); script = new FvFong();
else if (scriptName == "scripts\\ai\\FV\\L_FV_MAELSTROM_GEYSER.lua") { else if (scriptName == "scripts\\ai\\FV\\L_FV_MAELSTROM_GEYSER.lua")
script = new FvMaelstromGeyser(); script = new FvMaelstromGeyser();
} else if (scriptName == "scripts\\02_server\\Map\\FV\\Racing\\RACE_SHIP_LAP_COLUMNS_SERVER.lua")
script = new RaceShipLapColumnsServer();
// yes we know the lap numbers dont match the file name or anim. thats what they desgined it as.
else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP1_SERVER.lua")
script = new FvRaceDragon("lap_01", 2);
else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP2_SERVER.lua")
script = new FvRaceDragon("lap_02", 0);
else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP3_SERVER.lua")
script = new FvRaceDragon("lap_03", 1);
else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_PILLAR_ABC_SERVER.lua")
script = new FvRacePillarABCServer();
else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_PILLAR_D_SERVER.lua")
script = new FvRacePillarDServer();
else if (scriptName == "scripts\\02_server\\Map\\FV\\Racing\\RACE_FIREBALLS.lua")
script = new RaceFireballs();
//Misc: //Misc:
if (scriptName == "scripts\\02_server\\Map\\General\\L_EXPLODING_ASSET.lua") if (scriptName == "scripts\\02_server\\Map\\General\\L_EXPLODING_ASSET.lua")
@ -661,6 +683,8 @@ CppScripts::Script* const CppScripts::GetScript(Entity* parent, const std::strin
script = new RaceMaelstromGeiser(); script = new RaceMaelstromGeiser();
else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_SMASH_EGG_IMAGINE_SERVER.lua") else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_SMASH_EGG_IMAGINE_SERVER.lua")
script = new FvRaceSmashEggImagineServer(); script = new FvRaceSmashEggImagineServer();
else if (scriptName == "scripts\\02_server\\Map\\FV\\Racing\\FV_RACING_COLUMNS.lua")
script = new FvRacingColumns();
else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\RACE_SMASH_SERVER.lua") else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\RACE_SMASH_SERVER.lua")
script = new RaceSmashServer(); script = new RaceSmashServer();

View File

@ -1,6 +1,10 @@
set(DSCRIPTS_SOURCES_AI_RACING_OBJECTS set(DSCRIPTS_SOURCES_AI_RACING_OBJECTS
"RaceImagineCrateServer.cpp" "RaceImagineCrateServer.cpp"
"RaceImaginePowerup.cpp" "RaceImaginePowerup.cpp"
"FvRaceDragon.cpp"
"FvRacePillarServer.cpp"
"FvRacePillarABCServer.cpp"
"FvRacePillarDServer.cpp"
"FvRaceSmashEggImagineServer.cpp" "FvRaceSmashEggImagineServer.cpp"
"RaceSmashServer.cpp" "RaceSmashServer.cpp"
PARENT_SCOPE) PARENT_SCOPE)

View File

@ -0,0 +1,30 @@
#include "FvRaceDragon.h"
#include "RenderComponent.h"
#include "RacingControlComponent.h"
void FvRaceDragon::OnCollisionPhantom(Entity* self, Entity* target) {
if (!target) return;
const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL);
if (racingControllers.empty()) return;
auto* racingControlComponent = racingControllers[0]->GetComponent<RacingControlComponent>();
if (!racingControlComponent) return;
const auto* player = racingControlComponent->GetPlayerData(target->GetObjectID());
if (!player) return;
if (player->lap != m_Lap) return;
const auto dragons = Game::entityManager->GetEntitiesInGroup("dragon");
for (const auto& dragon : dragons) {
if (!dragon || dragon->GetLOT() != this->m_Dragon) continue;
auto* renderComponent = dragon->GetComponent<RenderComponent>();
if (!renderComponent) continue;
renderComponent->PlayAnimation(dragon, m_LapAnimName);
}
Game::entityManager->DestroyEntity(self);
}

View File

@ -0,0 +1,15 @@
#pragma once
#include "CppScripts.h"
#include <string>
#include <string_view>
class FvRaceDragon : public CppScripts::Script {
public:
FvRaceDragon(const std::string_view lapAnimName, const int32_t lap) : m_LapAnimName(lapAnimName), m_Lap(lap) {}
private:
void OnCollisionPhantom(Entity* self, Entity* target) override;
const std::string m_LapAnimName;
const int32_t m_Lap;
const LOT m_Dragon = 11898;
};

View File

@ -0,0 +1,34 @@
#include "FvRacePillarABCServer.h"
#include "RenderComponent.h"
#include "RacingControlComponent.h"
void FvRacePillarABCServer::OnCollisionPhantom(Entity* self, Entity* target) {
if (!target) return;
const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL);
if (racingControllers.empty()) return;
auto* racingControlComponent = racingControllers[0]->GetComponent<RacingControlComponent>();
if (!racingControlComponent) return;
const auto* player = racingControlComponent->GetPlayerData(target->GetObjectID());
if (!player || player->lap != 1) return;
PlayAnimation("crumble", "pillars", m_PillarA);
PlayAnimation("roar", "dragon", m_Dragon);
self->AddTimer("PillarBFall", 2.5f);
self->AddTimer("PillarCFall", 3.7f);
self->AddTimer("DeleteObject", 3.8f);
}
void FvRacePillarABCServer::OnTimerDone(Entity* self, std::string timerName) {
if (timerName == "PillarBFall") {
PlayAnimation("crumble", "pillars", m_PillarB);
} else if (timerName == "PillarCFall") {
PlayAnimation("crumble", "pillars", m_PillarC);
} else if (timerName == "DeleteObject") {
Game::entityManager->DestroyEntity(self);
}
}

View File

@ -0,0 +1,13 @@
#pragma once
#include "CppScripts.h"
#include "FvRacePillarServer.h"
class FvRacePillarABCServer : public FvRacePillarServer {
void OnCollisionPhantom(Entity* self, Entity* target) override;
void OnTimerDone(Entity* self, std::string timerName) override;
private:
const LOT m_PillarA = 11946;
const LOT m_PillarB = 11947;
const LOT m_PillarC = 11948;
const LOT m_Dragon = 11898;
};

View File

@ -0,0 +1,21 @@
#include "FvRacePillarDServer.h"
#include "RenderComponent.h"
#include "RacingControlComponent.h"
void FvRacePillarDServer::OnCollisionPhantom(Entity* self, Entity* target) {
if (!target) return;
const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL);
if (racingControllers.empty()) return;
auto* racingControlComponent = racingControllers[0]->GetComponent<RacingControlComponent>();
if (!racingControlComponent) return;
const auto* player = racingControlComponent->GetPlayerData(target->GetObjectID());
if (!player) return;
if (player->lap == 2) {
PlayAnimation("crumble", "pillars", m_PillarD);
PlayAnimation("roar", "dragon", m_Dragon);
}
}

View File

@ -0,0 +1,10 @@
#pragma once
#include "CppScripts.h"
#include "FvRacePillarServer.h"
class FvRacePillarDServer : public FvRacePillarServer {
void OnCollisionPhantom(Entity* self, Entity* target) override;
private:
const LOT m_PillarD = 11949;
const LOT m_Dragon = 11898;
};

View File

@ -0,0 +1,15 @@
#include "FvRacePillarServer.h"
#include "Game.h"
#include "EntityManager.h"
#include "RenderComponent.h"
void FvRacePillarServer::PlayAnimation(const std::string animName, const std::string group, const LOT lot) {
const auto entities = Game::entityManager->GetEntitiesInGroup(group);
for (const auto& entity : entities) {
if (!entity || entity->GetLOT() != lot) continue;
auto* renderComponent = entity->GetComponent<RenderComponent>();
if (!renderComponent) continue;
renderComponent->PlayAnimation(entity, animName);
}
}

View File

@ -0,0 +1,12 @@
#ifndef FVRACEPILLARSERVER__H
#define FVRACEPILLARSERVER__H
#include "CppScripts.h"
class FvRacePillarServer : public virtual CppScripts::Script {
protected:
// Plays an animation on all entities in a group with a specific LOT
void PlayAnimation(const std::string animName, const std::string group, const LOT lot);
};
#endif // FVRACEPILLARSERVER__H

View File

@ -1,3 +1,5 @@
1.2 - Dragonmaw functional
1.1 - Whole lot of fixed bugs and implemented features
1.0 - Final cleanup and bug fixing for public release 1.0 - Final cleanup and bug fixing for public release
0.9 - Includes BBB without the need for a UGC server, cannon cove minigame, and bug fixes. 0.9 - Includes BBB without the need for a UGC server, cannon cove minigame, and bug fixes.
0.8 - Added Ninjago! and it's various features + frakjaw minigame. AG survival now works. 0.8 - Added Ninjago! and it's various features + frakjaw minigame. AG survival now works.
@ -7,4 +9,4 @@
0.4 - Added Havok to replace Bullet, Instancing, Quickbuilds, rockets, and a ton more fixes and additions. 0.4 - Added Havok to replace Bullet, Instancing, Quickbuilds, rockets, and a ton more fixes and additions.
0.3 - FrostBurgh, Snowdrift and Snowman's Land testing version. Includes bodged systems. 0.3 - FrostBurgh, Snowdrift and Snowman's Land testing version. Includes bodged systems.
0.2 - Transfer to VS2019 & Bullet 0.2 - Transfer to VS2019 & Bullet
0.1 - Initial transfer from NixLU, up until BehaviorManager inclusion 0.1 - Initial transfer from NixLU, up until BehaviorManager inclusion