From 135aec8112d2aab24ff70785255be6e8eb9e16c8 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 20 Jun 2026 15:10:00 -0700 Subject: [PATCH] feat: hatchlings (#2008) tested that hatchlings now function fixes #759 Update MovementAIComponent.cpp Update MovementAIComponent.cpp Update HatchlingPets.cpp --- dGame/Entity.cpp | 1 + dGame/dComponents/MovementAIComponent.cpp | 29 +++++++ dGame/dComponents/MovementAIComponent.h | 14 ++-- dScripts/02_server/CMakeLists.txt | 1 + dScripts/02_server/Objects/CMakeLists.txt | 11 ++- .../Objects/Hatchlings/CMakeLists.txt | 3 + .../Objects/Hatchlings/HatchlingPets.cpp | 83 +++++++++++++++++++ .../Objects/Hatchlings/HatchlingPets.h | 14 ++++ dScripts/CppScripts.cpp | 2 + 9 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 dScripts/02_server/Objects/Hatchlings/CMakeLists.txt create mode 100644 dScripts/02_server/Objects/Hatchlings/HatchlingPets.cpp create mode 100644 dScripts/02_server/Objects/Hatchlings/HatchlingPets.h diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index c4265470..bad48b71 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -2237,6 +2237,7 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::RequestServerObjectInfo& r objectInfo.PushDebug("Template ID(LOT)") = GetLOT(); objectInfo.PushDebug("Object ID") = std::to_string(GetObjectID()); objectInfo.PushDebug("Spawner's Object ID") = std::to_string(GetSpawnerID()); + objectInfo.PushDebug("Owner override") = std::to_string(m_OwnerOverride); auto& componentDetails = objectInfo.PushDebug("Component Information"); for (const auto [id, component] : m_Components) { diff --git a/dGame/dComponents/MovementAIComponent.cpp b/dGame/dComponents/MovementAIComponent.cpp index c02d3a00..8a10df25 100644 --- a/dGame/dComponents/MovementAIComponent.cpp +++ b/dGame/dComponents/MovementAIComponent.cpp @@ -189,6 +189,15 @@ void MovementAIComponent::Update(const float deltaTime) { } } else { Stop(); + if (m_FollowedTarget != LWOOBJID_EMPTY) { + GameMessages::GetPosition getPos; + if (!getPos.Send(m_FollowedTarget)) { + LOG("Target %llu does not exist anymore to follow", m_FollowedTarget); + m_FollowedTarget = LWOOBJID_EMPTY; + } else { + SetDestination(getPos.pos); + } + } return; } } else { @@ -555,5 +564,25 @@ bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInf pathCopy.pop(); } + movementInfo.PushDebug("Followed Target") = std::to_string(m_FollowedTarget); + return true; } + +void MovementAIComponent::FollowTarget(const LWOOBJID target) { + if (target == LWOOBJID_EMPTY) { + m_FollowedTarget = target; + return; + } + GameMessages::GetPosition getPos; + if (!getPos.Send(target)) { + LOG("Tried to follow target %llu but they don't exist", target); + m_FollowedTarget = LWOOBJID_EMPTY; + return; + } + + m_FollowedTarget = target; + SetMaxSpeed(1.0f); + m_CurrentSpeed = 1.0f; + SetDestination(getPos.pos); +} diff --git a/dGame/dComponents/MovementAIComponent.h b/dGame/dComponents/MovementAIComponent.h index af7da7f0..eeb871cf 100644 --- a/dGame/dComponents/MovementAIComponent.h +++ b/dGame/dComponents/MovementAIComponent.h @@ -31,27 +31,27 @@ struct MovementAIInfo { /** * The radius that the entity can wander in */ - float wanderRadius; + float wanderRadius{}; /** * The speed at which the entity wanders */ - float wanderSpeed; + float wanderSpeed{}; /** * This is only used for the emotes */ - float wanderChance; + float wanderChance{}; /** * The min amount of delay before wandering */ - float wanderDelayMin; + float wanderDelayMin{}; /** * The max amount of delay before wandering */ - float wanderDelayMax; + float wanderDelayMax{}; }; /** @@ -214,6 +214,8 @@ public: bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo); bool HasPath() const { return m_Path != nullptr; } + + void FollowTarget(const LWOOBJID target); private: /** @@ -337,6 +339,8 @@ private: // The number of waypoints that were on the path in the call to SetPath uint32_t m_CurrentPathWaypointCount{ 0 }; + + LWOOBJID m_FollowedTarget{ LWOOBJID_EMPTY }; }; #endif // MOVEMENTAICOMPONENT_H diff --git a/dScripts/02_server/CMakeLists.txt b/dScripts/02_server/CMakeLists.txt index 8114b226..048139a0 100644 --- a/dScripts/02_server/CMakeLists.txt +++ b/dScripts/02_server/CMakeLists.txt @@ -37,6 +37,7 @@ target_include_directories(dScriptsServerBase PUBLIC "." "Minigame" "Minigame/General" "Objects" + "Objects/Hatchlings" ) target_precompile_headers(dScriptsServerBase REUSE_FROM dScriptsBase) diff --git a/dScripts/02_server/Objects/CMakeLists.txt b/dScripts/02_server/Objects/CMakeLists.txt index 1b96d79f..da7e9b60 100644 --- a/dScripts/02_server/Objects/CMakeLists.txt +++ b/dScripts/02_server/Objects/CMakeLists.txt @@ -1,4 +1,11 @@ +add_subdirectory(Hatchlings) + set(DSCRIPTS_SOURCES_02_SERVER_OBJECTS "AgSurvivalBuffStation.cpp" - "StinkyFishTarget.cpp" - PARENT_SCOPE) + "StinkyFishTarget.cpp") + +foreach(file ${DSCRIPTS_SOURCES_02_SERVER_OBJECTS_HATCHLINGS}) + set(DSCRIPTS_SOURCES_02_SERVER_OBJECTS ${DSCRIPTS_SOURCES_02_SERVER_OBJECTS} "Hatchlings/${file}") +endforeach() + +set(DSCRIPTS_SOURCES_02_SERVER_OBJECTS ${DSCRIPTS_SOURCES_02_SERVER_OBJECTS} PARENT_SCOPE) diff --git a/dScripts/02_server/Objects/Hatchlings/CMakeLists.txt b/dScripts/02_server/Objects/Hatchlings/CMakeLists.txt new file mode 100644 index 00000000..0655ec64 --- /dev/null +++ b/dScripts/02_server/Objects/Hatchlings/CMakeLists.txt @@ -0,0 +1,3 @@ +set(DSCRIPTS_SOURCES_02_SERVER_OBJECTS_HATCHLINGS + "HatchlingPets.cpp" + PARENT_SCOPE) diff --git a/dScripts/02_server/Objects/Hatchlings/HatchlingPets.cpp b/dScripts/02_server/Objects/Hatchlings/HatchlingPets.cpp new file mode 100644 index 00000000..bc29ae8d --- /dev/null +++ b/dScripts/02_server/Objects/Hatchlings/HatchlingPets.cpp @@ -0,0 +1,83 @@ +#include "HatchlingPets.h" + +#include "Entity.h" +#include "MovementAIComponent.h" + +void HatchlingPets::OnStartup(Entity* self) { + self->SetVar(u"follow", false); + + self->SetProximityRadius(5, "StopFollow"); + self->SetProximityRadius(15, "Wander"); + self->SetProximityRadius(50, "Teleport"); + + Wander(*self, *self->GetOwner()); + self->AddComponent(-1, MovementAIInfo{ .wanderRadius = 2.5f }); +} + +void HatchlingPets::OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) { + auto* const parent = self->GetOwner(); + if (!entering || !entering->IsPlayer() || parent->GetObjectID() != entering->GetObjectID()) return; + + if (name == "StopFollow") { + if (status == "ENTER") { + if (self->GetVar(u"follow")) { + const auto randomWanderTime = GeneralUtils::GenerateRandomNumber(4, 9); + self->AddTimer("StartWander", randomWanderTime); + // stop following the player + auto* const movementAI = self->GetComponent(); + if (movementAI) { + movementAI->Stop(); + movementAI->FollowTarget(LWOOBJID_EMPTY); + } + self->SetVar(u"follow", false); + } + } + } else if (name == "Wander") { + if (status == "LEAVE") { + self->CancelAllTimers(); + // follow the player + auto* const movementAI = self->GetComponent(); + if (movementAI) { + movementAI->Stop(); + movementAI->FollowTarget(entering->GetObjectID()); + } + self->SetVar(u"follow", true); + } + } else if (name == "Teleport") { + if (status == "LEAVE") { + // stop following the player + auto* const movementAI = self->GetComponent(); + if (movementAI) { + movementAI->Stop(); + movementAI->FollowTarget(LWOOBJID_EMPTY); + } + GameMessages::GetPosition getPos; + getPos.Send(entering->GetObjectID()); + getPos.pos.z += 5.0f; + self->SetPosition(getPos.pos); + Game::entityManager->SerializeEntity(*self); + } + } +} + +void HatchlingPets::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == "StartWander") { + Wander(*self, *self->GetOwner()); + } +} + +void HatchlingPets::Wander(Entity& self, Entity& player) { + GameMessages::GetPosition getPos; + if (!getPos.Send(player.GetObjectID())) { + LOG("Failed to get position for %llu", player.GetObjectID()); + return; + } + + const auto xWander = GeneralUtils::GenerateRandomNumber(0, 20) - 10.0f; + const auto zWander = GeneralUtils::GenerateRandomNumber(0, 20) - 10.0f; + getPos.pos.x += xWander; + getPos.pos.z += zWander; + auto* const movementAI = self.GetComponent(); + if (movementAI) movementAI->SetDestination(getPos.pos); + self.AddTimer("StartWander", GeneralUtils::GenerateRandomNumber(4, 9)); +} diff --git a/dScripts/02_server/Objects/Hatchlings/HatchlingPets.h b/dScripts/02_server/Objects/Hatchlings/HatchlingPets.h new file mode 100644 index 00000000..0dd2d5b6 --- /dev/null +++ b/dScripts/02_server/Objects/Hatchlings/HatchlingPets.h @@ -0,0 +1,14 @@ +#ifndef HATCHLINGPETS_H +#define HATCHLINGPETS_H + +#include "CppScripts.h" +#include "NiPoint3.h" + +class HatchlingPets : public CppScripts::Script { + void OnStartup(Entity* self) override; + void OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) override; + void OnTimerDone(Entity* self, std::string timerName) override; + void Wander(Entity& self, Entity& player); +}; + +#endif //!HATCHLINGPETS_H diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index a1f37153..fc955819 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -166,6 +166,7 @@ #include "AgSalutingNpcs.h" #include "BossSpiderQueenEnemyServer.h" #include "RockHydrantSmashable.h" +#include "HatchlingPets.h" // Misc Scripts #include "ExplodingAsset.h" @@ -423,6 +424,7 @@ namespace { {"scripts\\ai\\NS\\L_NS_CONCERT_INSTRUMENT_QB.lua", []() {return new NsConcertInstrument();}}, {"scripts\\ai\\NS\\L_NS_JONNY_FLAG_MISSION_SERVER.lua", []() {return new NsJohnnyMissionServer();}}, {"scripts\\02_server\\Objects\\L_STINKY_FISH_TARGET.lua", []() {return new StinkyFishTarget();}}, + {"scripts\\02_server\\Objects\\Hatchlings\\L_HATCHLING_PETS.lua", []() {return new HatchlingPets();}}, {"scripts\\zone\\PROPERTY\\NS\\L_ZONE_NS_PROPERTY.lua", []() {return new ZoneNsProperty();}}, {"scripts\\02_server\\Map\\Property\\NS_Med\\L_ZONE_NS_MED_PROPERTY.lua", []() {return new ZoneNsMedProperty();}}, {"scripts\\02_server\\Map\\NS\\L_NS_TOKEN_CONSOLE_SERVER.lua", []() {return new NsTokenConsoleServer();}},