mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-06-17 20:24:21 +00:00
feat: enemy npc pathing
they live 🎉 tested that enemies path all around the world should they have a path configured. tested that the admiral in gf (at the first camp) paths now. fixes #1546
This commit is contained in:
@@ -210,8 +210,10 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stunnedThisFrame) {
|
if (stunnedThisFrame) {
|
||||||
m_MovementAI->Stop();
|
if (!m_MovementAI->IsPaused()) m_MovementAI->Pause();
|
||||||
|
|
||||||
|
// in this case we just become unstunned so check if we paused and resume if we did
|
||||||
|
if (!m_Stunned && m_MovementAI->IsPaused()) m_MovementAI->Resume();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,12 +319,14 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
|||||||
SetAiState(AiState::aggro);
|
SetAiState(AiState::aggro);
|
||||||
} else {
|
} else {
|
||||||
SetAiState(AiState::idle);
|
SetAiState(AiState::idle);
|
||||||
|
if (m_MovementAI) m_MovementAI->SetMaxSpeed(1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasSkillToCast) return;
|
if (!hasSkillToCast) return;
|
||||||
|
|
||||||
if (m_Target == LWOOBJID_EMPTY) {
|
if (m_Target == LWOOBJID_EMPTY) {
|
||||||
SetAiState(AiState::idle);
|
SetAiState(AiState::idle);
|
||||||
|
if (m_MovementAI) m_MovementAI->SetMaxSpeed(1.0f);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -618,6 +622,11 @@ void BaseCombatAIComponent::Wander() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have a path to follow we should almost certainly do that instead of wandering.
|
||||||
|
if (m_MovementAI->HasPath()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_MovementAI->SetHaltDistance(0);
|
m_MovementAI->SetHaltDistance(0);
|
||||||
|
|
||||||
const auto& info = m_MovementAI->GetInfo();
|
const auto& info = m_MovementAI->GetInfo();
|
||||||
@@ -862,12 +871,12 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReport
|
|||||||
// roundInfo.PushDebug<AMFDoubleValue>("Combat Start Delay") = m_CombatStartDelay;
|
// roundInfo.PushDebug<AMFDoubleValue>("Combat Start Delay") = m_CombatStartDelay;
|
||||||
std::string curState;
|
std::string curState;
|
||||||
switch (m_State) {
|
switch (m_State) {
|
||||||
case idle: curState = "Idling"; break;
|
case idle: curState = "Idling"; break;
|
||||||
case aggro: curState = "Aggroed"; break;
|
case aggro: curState = "Aggroed"; break;
|
||||||
case tether: curState = "Returning to Tether"; break;
|
case tether: curState = "Returning to Tether"; break;
|
||||||
case spawn: curState = "Spawn"; break;
|
case spawn: curState = "Spawn"; break;
|
||||||
case dead: curState = "Dead"; break;
|
case dead: curState = "Dead"; break;
|
||||||
default: curState = "Unknown or Undefined"; break;
|
default: curState = "Unknown or Undefined"; break;
|
||||||
}
|
}
|
||||||
cmptType.PushDebug<AMFStringValue>("Current Combat State") = curState;
|
cmptType.PushDebug<AMFStringValue>("Current Combat State") = curState;
|
||||||
|
|
||||||
|
|||||||
@@ -236,6 +236,8 @@ public:
|
|||||||
|
|
||||||
bool MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
bool MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||||
|
|
||||||
|
void SetStartingPosition(const NiPoint3& pos) { m_StartPosition = pos; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Returns the current target or the target that currently is the largest threat to this entity
|
* Returns the current target or the target that currently is the largest threat to this entity
|
||||||
|
|||||||
@@ -19,6 +19,12 @@
|
|||||||
#include "Amf3.h"
|
#include "Amf3.h"
|
||||||
|
|
||||||
#include "dNavMesh.h"
|
#include "dNavMesh.h"
|
||||||
|
#include "eWaypointCommandType.h"
|
||||||
|
#include "StringifiedEnum.h"
|
||||||
|
#include "SkillComponent.h"
|
||||||
|
#include "GeneralUtils.h"
|
||||||
|
#include "RenderComponent.h"
|
||||||
|
#include "InventoryComponent.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +66,7 @@ MovementAIComponent::MovementAIComponent(Entity* parent, const int32_t component
|
|||||||
|
|
||||||
RegisterMsg(&MovementAIComponent::OnGetObjectReportInfo);
|
RegisterMsg(&MovementAIComponent::OnGetObjectReportInfo);
|
||||||
|
|
||||||
if (!m_Parent->GetComponent<BaseCombatAIComponent>()) SetPath(m_Parent->GetVarAsString(u"attached_path"));
|
SetPath(m_Parent->GetVarAsString(u"attached_path"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MovementAIComponent::SetPath(const std::string pathName) {
|
void MovementAIComponent::SetPath(const std::string pathName) {
|
||||||
@@ -162,6 +168,7 @@ void MovementAIComponent::Update(const float deltaTime) {
|
|||||||
} else {
|
} else {
|
||||||
// Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
|
// Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
|
||||||
const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1;
|
const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1;
|
||||||
|
RunWaypointCommands(waypointNum);
|
||||||
if (m_CurrentPath.empty()) {
|
if (m_CurrentPath.empty()) {
|
||||||
if (m_Path) {
|
if (m_Path) {
|
||||||
if (m_Path->pathBehavior == PathBehavior::Loop) {
|
if (m_Path->pathBehavior == PathBehavior::Loop) {
|
||||||
@@ -172,17 +179,16 @@ void MovementAIComponent::Update(const float deltaTime) {
|
|||||||
if (m_IsBounced) std::ranges::reverse(waypoints);
|
if (m_IsBounced) std::ranges::reverse(waypoints);
|
||||||
SetPath(waypoints);
|
SetPath(waypoints);
|
||||||
} else if (m_Path->pathBehavior == PathBehavior::Once) {
|
} else if (m_Path->pathBehavior == PathBehavior::Once) {
|
||||||
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
|
// In this case we intended to follow a path and once we've followed it we camp there, otherwise we'd just wander home again.
|
||||||
|
m_BaseCombatAI->SetStartingPosition(m_SourcePosition);
|
||||||
Stop();
|
Stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
|
|
||||||
Stop();
|
Stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
|
|
||||||
SetDestination(m_CurrentPath.top().position);
|
SetDestination(m_CurrentPath.top().position);
|
||||||
|
|
||||||
m_CurrentPath.pop();
|
m_CurrentPath.pop();
|
||||||
@@ -423,7 +429,69 @@ NiPoint3 MovementAIComponent::GetDestination() const {
|
|||||||
void MovementAIComponent::SetMaxSpeed(const float value) {
|
void MovementAIComponent::SetMaxSpeed(const float value) {
|
||||||
if (value == m_MaxSpeed) return;
|
if (value == m_MaxSpeed) return;
|
||||||
m_MaxSpeed = value;
|
m_MaxSpeed = value;
|
||||||
m_Acceleration = value / 5;
|
m_Acceleration = value / 5.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MovementAIComponent::RunWaypointCommands(uint32_t waypointNum) {
|
||||||
|
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
|
||||||
|
|
||||||
|
if (!m_Path || waypointNum >= m_Path->pathWaypoints.size()) return;
|
||||||
|
const auto& commands = m_Path->pathWaypoints[waypointNum].commands;
|
||||||
|
for (const auto& [command, data] : commands) {
|
||||||
|
LOG_DEBUG("%s %s %s", StringifiedEnum::ToString(command).data(), m_Path->pathName.c_str(), data.c_str());
|
||||||
|
const auto dataSplit = GeneralUtils::SplitString(data, ',');
|
||||||
|
switch (command) {
|
||||||
|
case eWaypointCommandType::INVALID: break;
|
||||||
|
case eWaypointCommandType::BOUNCE: break;
|
||||||
|
case eWaypointCommandType::STOP: Pause(); break;
|
||||||
|
case eWaypointCommandType::GROUP_EMOTE: break;
|
||||||
|
case eWaypointCommandType::SET_VARIABLE: break; // Empty in the client
|
||||||
|
case eWaypointCommandType::CAST_SKILL: {
|
||||||
|
const auto skill = GeneralUtils::TryParse<uint32_t>(data);
|
||||||
|
if (skill) {
|
||||||
|
auto* const skillComponent = m_Parent->GetComponent<SkillComponent>();
|
||||||
|
if (skillComponent) skillComponent->CastSkill(skill.value());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eWaypointCommandType::EQUIP_INVENTORY: {
|
||||||
|
auto* const inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
|
||||||
|
if (inventoryComponent) {
|
||||||
|
// items should always exist
|
||||||
|
auto* const item = inventoryComponent->GetInventory(eInventoryType::ITEMS)->FindItemBySlot(0);
|
||||||
|
inventoryComponent->EquipItem(item);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eWaypointCommandType::UNEQUIP_INVENTORY: {
|
||||||
|
auto* const inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
|
||||||
|
if (inventoryComponent) {
|
||||||
|
// items should always exist
|
||||||
|
auto* const item = inventoryComponent->GetInventory(eInventoryType::ITEMS)->FindItemBySlot(0);
|
||||||
|
inventoryComponent->UnEquipItem(item);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// case eWaypointCommandType::DELAY: {
|
||||||
|
// Pause(GeneralUtils::TryParse<float>(data).value_or(0.0f));
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
case eWaypointCommandType::EMOTE: {
|
||||||
|
// m_Delay = RenderComponent::GetAnimationTime(m_Parent, data);
|
||||||
|
// const auto emoteID = GeneralUtils::TryParse<uint32_t>(data);
|
||||||
|
// if (emoteID) GameMessages::SendPlayEmote(m_Parent->GetObjectID(), emoteID.value(), LWOOBJID_EMPTY, UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
|
// break;
|
||||||
|
}
|
||||||
|
case eWaypointCommandType::TELEPORT: break;
|
||||||
|
case eWaypointCommandType::PATH_SPEED: m_BaseSpeed = GetBaseSpeed(m_Parent->GetLOT()) * GeneralUtils::TryParse<float>(data).value_or(1.0f); break;
|
||||||
|
case eWaypointCommandType::REMOVE_NPC: break;
|
||||||
|
case eWaypointCommandType::CHANGE_WAYPOINT: SetPath(dataSplit[0]); break;
|
||||||
|
case eWaypointCommandType::DELETE_SELF: break;
|
||||||
|
case eWaypointCommandType::KILL_SELF: m_Parent->Smash(); break;
|
||||||
|
case eWaypointCommandType::SPAWN_OBJECT: break;
|
||||||
|
case eWaypointCommandType::PLAY_SOUND: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||||
|
|||||||
@@ -212,8 +212,16 @@ public:
|
|||||||
bool IsPaused() const { return m_Paused; }
|
bool IsPaused() const { return m_Paused; }
|
||||||
|
|
||||||
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||||
|
|
||||||
|
bool HasPath() const { return m_Path != nullptr; }
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
* Runs the commands on a waypoint if a path exists
|
||||||
|
*/
|
||||||
|
void RunWaypointCommands(uint32_t waypointNum);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current position of the entity
|
* Sets the current position of the entity
|
||||||
* @param value the position to set
|
* @param value the position to set
|
||||||
|
|||||||
@@ -101,15 +101,23 @@ void Zone::LoadZoneIntoMemory() {
|
|||||||
m_Paths.reserve(pathCount);
|
m_Paths.reserve(pathCount);
|
||||||
for (uint32_t i = 0; i < pathCount; ++i) LoadPath(file);
|
for (uint32_t i = 0; i < pathCount; ++i) LoadPath(file);
|
||||||
|
|
||||||
for (Path path : m_Paths) {
|
for (const Path& path : m_Paths) {
|
||||||
if (path.pathType != PathType::Spawner) continue;
|
if (path.pathType != PathType::Spawner) continue;
|
||||||
SpawnerInfo info = SpawnerInfo();
|
SpawnerInfo info{};
|
||||||
for (PathWaypoint waypoint : path.pathWaypoints) {
|
for (int i = 0; i < path.pathWaypoints.size(); i++) {
|
||||||
|
const auto& waypoint = path.pathWaypoints[i];
|
||||||
SpawnerNode* node = new SpawnerNode();
|
SpawnerNode* node = new SpawnerNode();
|
||||||
node->position = waypoint.position;
|
node->position = waypoint.position;
|
||||||
node->rotation = waypoint.rotation;
|
node->rotation = waypoint.rotation;
|
||||||
node->nodeID = 0;
|
node->nodeID = 0;
|
||||||
node->config = waypoint.config;
|
node->config = path.pathWaypoints[0].config;
|
||||||
|
// All spawner waypoints get the config data of the first waypoint, but then we
|
||||||
|
// overwrite settings on this waypoint if we have another one defined of the same name
|
||||||
|
if (i != 0) {
|
||||||
|
for (const auto& [key, value] : waypoint.config) {
|
||||||
|
node->config.ParseInsert(value->GetString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto& data : waypoint.config | std::views::values) {
|
for (const auto& data : waypoint.config | std::views::values) {
|
||||||
if (!data) continue;
|
if (!data) continue;
|
||||||
|
|||||||
Reference in New Issue
Block a user