mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-08 17:28:20 +00:00
refactor: re-write AOE, add FilterTargets, Update TacArc Reading (#1035)
* Re-write AOE behavior for new filter targets Update Tacarc to use new filter targets Added dev commands for skill and attack debugging * Get all entities by detroyable rather than controllable physics Since destroyables are what can be hit * Re-work filter targets to be 100% live accurate reduce memory usage by only using one vector and removing invalid entries get entities in the proximity rather than all entities with des comps in the instance, as was done in live * remove debuging longs and remove oopsie * address feedback * make log more useful * make filter more flat * Add some more checks to filter targets add pvp checks to isenemy * fix typing * Add filter target to TacArc and update filter target * fix double declaration * Some debugging logs * Update TacArc reading * make log clearer * logs * Update TacArcBehavior.cpp * banana * fix max targets * remove extreanous parenthesesuuesdsds * make behavior slot use a real type --------- Co-authored-by: David Markowitz <EmosewaMC@gmail.com>
This commit is contained in:
parent
471d65707c
commit
d8ac148cee
@ -298,6 +298,16 @@ std::vector<Entity*> EntityManager::GetEntitiesByLOT(const LOT& lot) const {
|
||||
return entities;
|
||||
}
|
||||
|
||||
std::vector<Entity*> EntityManager::GetEntitiesByProximity(NiPoint3 reference, float radius) const{
|
||||
std::vector<Entity*> entities = {};
|
||||
if (radius > 1000.0f) return entities;
|
||||
for (const auto& entity : m_Entities) {
|
||||
if (NiPoint3::Distance(reference, entity.second->GetPosition()) <= radius) entities.push_back(entity.second);
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
|
||||
Entity* EntityManager::GetZoneControlEntity() const {
|
||||
return m_ZoneControlEntity;
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ public:
|
||||
std::vector<Entity*> GetEntitiesInGroup(const std::string& group);
|
||||
std::vector<Entity*> GetEntitiesByComponent(eReplicaComponentType componentType) const;
|
||||
std::vector<Entity*> GetEntitiesByLOT(const LOT& lot) const;
|
||||
std::vector<Entity*> GetEntitiesByProximity(NiPoint3 reference, float radius) const;
|
||||
Entity* GetZoneControlEntity() const;
|
||||
|
||||
// Get spawn point entity by spawn name
|
||||
|
@ -20,134 +20,114 @@ void AreaOfEffectBehavior::Handle(BehaviorContext* context, RakNet::BitStream* b
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetCount > this->m_maxTargets) {
|
||||
if (this->m_useTargetPosition && branch.target == LWOOBJID_EMPTY) return;
|
||||
|
||||
if (targetCount == 0){
|
||||
PlayFx(u"miss", context->originator);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<LWOOBJID> targets;
|
||||
if (targetCount > this->m_maxTargets) {
|
||||
Game::logger->Log("AreaOfEffectBehavior", "Serialized size is greater than max targets! Size: %i, Max: %i", targetCount, this->m_maxTargets);
|
||||
return;
|
||||
}
|
||||
|
||||
auto caster = context->caster;
|
||||
if (this->m_useTargetAsCaster) context->caster = branch.target;
|
||||
|
||||
std::vector<LWOOBJID> targets;
|
||||
targets.reserve(targetCount);
|
||||
|
||||
for (auto i = 0u; i < targetCount; ++i) {
|
||||
LWOOBJID target{};
|
||||
|
||||
if (!bitStream->Read(target)) {
|
||||
Game::logger->Log("AreaOfEffectBehavior", "failed to read in target %i from bitStream, aborting target Handle!", i);
|
||||
return;
|
||||
};
|
||||
|
||||
targets.push_back(target);
|
||||
}
|
||||
|
||||
for (auto target : targets) {
|
||||
branch.target = target;
|
||||
|
||||
this->m_action->Handle(context, bitStream, branch);
|
||||
}
|
||||
context->caster = caster;
|
||||
PlayFx(u"cast", context->originator);
|
||||
}
|
||||
|
||||
void AreaOfEffectBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
auto* self = Game::entityManager->GetEntity(context->caster);
|
||||
if (self == nullptr) {
|
||||
Game::logger->Log("AreaOfEffectBehavior", "Invalid self for (%llu)!", context->originator);
|
||||
auto* caster = Game::entityManager->GetEntity(context->caster);
|
||||
if (!caster) return;
|
||||
|
||||
return;
|
||||
// determine the position we are casting the AOE from
|
||||
auto reference = branch.isProjectile ? branch.referencePosition : caster->GetPosition();
|
||||
if (this->m_useTargetPosition) {
|
||||
if (branch.target == LWOOBJID_EMPTY) return;
|
||||
auto branchTarget = Game::entityManager->GetEntity(branch.target);
|
||||
if (branchTarget) reference = branchTarget->GetPosition();
|
||||
}
|
||||
|
||||
auto reference = branch.isProjectile ? branch.referencePosition : self->GetPosition();
|
||||
reference += this->m_offset;
|
||||
|
||||
std::vector<Entity*> targets;
|
||||
|
||||
auto* presetTarget = Game::entityManager->GetEntity(branch.target);
|
||||
|
||||
if (presetTarget != nullptr) {
|
||||
if (this->m_radius * this->m_radius >= Vector3::DistanceSquared(reference, presetTarget->GetPosition())) {
|
||||
targets.push_back(presetTarget);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t includeFaction = m_includeFaction;
|
||||
|
||||
if (self->GetLOT() == 14466) // TODO: Fix edge case
|
||||
{
|
||||
includeFaction = 1;
|
||||
}
|
||||
|
||||
// Gets all of the valid targets, passing in if should target enemies and friends
|
||||
for (auto validTarget : context->GetValidTargets(m_ignoreFaction, includeFaction, m_TargetSelf == 1, m_targetEnemy == 1, m_targetFriend == 1)) {
|
||||
auto* entity = Game::entityManager->GetEntity(validTarget);
|
||||
|
||||
if (entity == nullptr) {
|
||||
Game::logger->Log("AreaOfEffectBehavior", "Invalid target (%llu) for (%llu)!", validTarget, context->originator);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::find(targets.begin(), targets.end(), entity) != targets.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (destroyableComponent == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (destroyableComponent->HasFaction(m_ignoreFaction)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto distance = Vector3::DistanceSquared(reference, entity->GetPosition());
|
||||
|
||||
if (this->m_radius * this->m_radius >= distance && (this->m_maxTargets == 0 || targets.size() < this->m_maxTargets)) {
|
||||
targets.push_back(entity);
|
||||
}
|
||||
}
|
||||
std::vector<Entity*> targets {};
|
||||
targets = Game::entityManager->GetEntitiesByProximity(reference, this->m_radius);
|
||||
context->FilterTargets(targets, this->m_ignoreFactionList, this->m_includeFactionList, this->m_targetSelf, this->m_targetEnemy, this->m_targetFriend, this->m_targetTeam);
|
||||
|
||||
// sort by distance
|
||||
std::sort(targets.begin(), targets.end(), [reference](Entity* a, Entity* b) {
|
||||
const auto aDistance = Vector3::DistanceSquared(a->GetPosition(), reference);
|
||||
const auto bDistance = Vector3::DistanceSquared(b->GetPosition(), reference);
|
||||
const auto aDistance = NiPoint3::Distance(a->GetPosition(), reference);
|
||||
const auto bDistance = NiPoint3::Distance(b->GetPosition(), reference);
|
||||
return aDistance < bDistance;
|
||||
}
|
||||
);
|
||||
|
||||
return aDistance > bDistance;
|
||||
});
|
||||
// resize if we have more than max targets allows
|
||||
if (targets.size() > this->m_maxTargets) targets.resize(this->m_maxTargets);
|
||||
|
||||
const uint32_t size = targets.size();
|
||||
bitStream->Write<uint32_t>(targets.size());
|
||||
|
||||
bitStream->Write(size);
|
||||
|
||||
if (size == 0) {
|
||||
if (targets.size() == 0) {
|
||||
PlayFx(u"miss", context->originator);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
context->foundTarget = true;
|
||||
// write all the targets to the bitstream
|
||||
for (auto* target : targets) {
|
||||
bitStream->Write(target->GetObjectID());
|
||||
}
|
||||
|
||||
context->foundTarget = true;
|
||||
|
||||
for (auto* target : targets) {
|
||||
bitStream->Write(target->GetObjectID());
|
||||
|
||||
PlayFx(u"cast", context->originator, target->GetObjectID());
|
||||
}
|
||||
|
||||
for (auto* target : targets) {
|
||||
branch.target = target->GetObjectID();
|
||||
|
||||
this->m_action->Calculate(context, bitStream, branch);
|
||||
// then cast all the actions
|
||||
for (auto* target : targets) {
|
||||
branch.target = target->GetObjectID();
|
||||
this->m_action->Calculate(context, bitStream, branch);
|
||||
}
|
||||
PlayFx(u"cast", context->originator);
|
||||
}
|
||||
}
|
||||
|
||||
void AreaOfEffectBehavior::Load() {
|
||||
this->m_action = GetAction("action");
|
||||
this->m_action = GetAction("action"); // required
|
||||
this->m_radius = GetFloat("radius", 0.0f); // required
|
||||
this->m_maxTargets = GetInt("max targets", 100);
|
||||
if (this->m_maxTargets == 0) this->m_maxTargets = 100;
|
||||
this->m_useTargetPosition = GetBoolean("use_target_position", false);
|
||||
this->m_useTargetAsCaster = GetBoolean("use_target_as_caster", false);
|
||||
this->m_offset = NiPoint3(
|
||||
GetFloat("offset_x", 0.0f),
|
||||
GetFloat("offset_y", 0.0f),
|
||||
GetFloat("offset_z", 0.0f)
|
||||
);
|
||||
|
||||
this->m_radius = GetFloat("radius");
|
||||
|
||||
this->m_maxTargets = GetInt("max targets");
|
||||
|
||||
this->m_ignoreFaction = GetInt("ignore_faction");
|
||||
|
||||
this->m_includeFaction = GetInt("include_faction");
|
||||
|
||||
this->m_TargetSelf = GetInt("target_self");
|
||||
|
||||
this->m_targetEnemy = GetInt("target_enemy");
|
||||
|
||||
this->m_targetFriend = GetInt("target_friend");
|
||||
// params after this are needed for filter targets
|
||||
const auto parameters = GetParameterNames();
|
||||
for (const auto& parameter : parameters) {
|
||||
if (parameter.first.rfind("include_faction", 0) == 0) {
|
||||
this->m_includeFactionList.push_front(parameter.second);
|
||||
} else if (parameter.first.rfind("ignore_faction", 0) == 0) {
|
||||
this->m_ignoreFactionList.push_front(parameter.second);
|
||||
}
|
||||
}
|
||||
this->m_targetSelf = GetBoolean("target_self", false);
|
||||
this->m_targetEnemy = GetBoolean("target_enemy", false);
|
||||
this->m_targetFriend = GetBoolean("target_friend", false);
|
||||
this->m_targetTeam = GetBoolean("target_team", false);
|
||||
}
|
||||
|
@ -1,34 +1,26 @@
|
||||
#pragma once
|
||||
#include "Behavior.h"
|
||||
#include <forward_list>
|
||||
|
||||
class AreaOfEffectBehavior final : public Behavior
|
||||
{
|
||||
public:
|
||||
Behavior* m_action;
|
||||
|
||||
uint32_t m_maxTargets;
|
||||
|
||||
float m_radius;
|
||||
|
||||
int32_t m_ignoreFaction;
|
||||
|
||||
int32_t m_includeFaction;
|
||||
|
||||
int32_t m_TargetSelf;
|
||||
|
||||
int32_t m_targetEnemy;
|
||||
|
||||
int32_t m_targetFriend;
|
||||
|
||||
/*
|
||||
* Inherited
|
||||
*/
|
||||
explicit AreaOfEffectBehavior(const uint32_t behaviorId) : Behavior(behaviorId) {
|
||||
}
|
||||
|
||||
explicit AreaOfEffectBehavior(const uint32_t behaviorId) : Behavior(behaviorId) {}
|
||||
void Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override;
|
||||
|
||||
void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override;
|
||||
|
||||
void Load() override;
|
||||
private:
|
||||
Behavior* m_action;
|
||||
uint32_t m_maxTargets;
|
||||
float m_radius;
|
||||
bool m_useTargetPosition;
|
||||
bool m_useTargetAsCaster;
|
||||
NiPoint3 m_offset;
|
||||
|
||||
std::forward_list<int32_t> m_ignoreFactionList {};
|
||||
std::forward_list<int32_t> m_includeFactionList {};
|
||||
bool m_targetSelf;
|
||||
bool m_targetEnemy;
|
||||
bool m_targetFriend;
|
||||
bool m_targetTeam;
|
||||
};
|
||||
|
@ -175,7 +175,7 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId) {
|
||||
case BehaviorTemplates::BEHAVIOR_SPEED:
|
||||
behavior = new SpeedBehavior(behaviorId);
|
||||
break;
|
||||
case BehaviorTemplates::BEHAVIOR_DARK_INSPIRATION:
|
||||
case BehaviorTemplates::BEHAVIOR_DARK_INSPIRATION:
|
||||
behavior = new DarkInspirationBehavior(behaviorId);
|
||||
break;
|
||||
case BehaviorTemplates::BEHAVIOR_LOOT_BUFF:
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "PhantomPhysicsComponent.h"
|
||||
#include "RebuildComponent.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "TeamManager.h"
|
||||
#include "eConnectionType.h"
|
||||
|
||||
BehaviorSyncEntry::BehaviorSyncEntry() {
|
||||
@ -307,46 +308,123 @@ void BehaviorContext::Reset() {
|
||||
this->scheduledUpdates.clear();
|
||||
}
|
||||
|
||||
std::vector<LWOOBJID> BehaviorContext::GetValidTargets(int32_t ignoreFaction, int32_t includeFaction, bool targetSelf, bool targetEnemy, bool targetFriend) const {
|
||||
auto* entity = Game::entityManager->GetEntity(this->caster);
|
||||
void BehaviorContext::FilterTargets(std::vector<Entity*>& targets, std::forward_list<int32_t>& ignoreFactionList, std::forward_list<int32_t>& includeFactionList, bool targetSelf, bool targetEnemy, bool targetFriend, bool targetTeam) const {
|
||||
|
||||
std::vector<LWOOBJID> targets;
|
||||
|
||||
if (entity == nullptr) {
|
||||
Game::logger->Log("BehaviorContext", "Invalid entity for (%llu)!", this->originator);
|
||||
|
||||
return targets;
|
||||
// if we aren't targeting anything, then clear the targets vector
|
||||
if (!targetSelf && !targetEnemy && !targetFriend && !targetTeam && ignoreFactionList.empty() && includeFactionList.empty()) {
|
||||
targets.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ignoreFaction && !includeFaction) {
|
||||
for (auto entry : entity->GetTargetsInPhantom()) {
|
||||
auto* instance = Game::entityManager->GetEntity(entry);
|
||||
|
||||
if (instance == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
targets.push_back(entry);
|
||||
}
|
||||
// if the caster is not there, return empty targets list
|
||||
auto* caster = Game::entityManager->GetEntity(this->caster);
|
||||
if (!caster) {
|
||||
Game::logger->LogDebug("BehaviorContext", "Invalid caster for (%llu)!", this->originator);
|
||||
targets.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ignoreFaction || includeFaction || (!entity->HasComponent(eReplicaComponentType::PHANTOM_PHYSICS) && targets.empty())) {
|
||||
DestroyableComponent* destroyableComponent;
|
||||
if (!entity->TryGetComponent(eReplicaComponentType::DESTROYABLE, destroyableComponent)) {
|
||||
return targets;
|
||||
auto index = targets.begin();
|
||||
while (index != targets.end()) {
|
||||
auto candidate = *index;
|
||||
|
||||
// make sure we don't have a nullptr
|
||||
if (!candidate) {
|
||||
index = targets.erase(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS);
|
||||
for (auto* candidate : entities) {
|
||||
const auto id = candidate->GetObjectID();
|
||||
// handle targeting the caster
|
||||
if (candidate == caster){
|
||||
// if we aren't targeting self, erase, otherise increment and continue
|
||||
if (!targetSelf) index = targets.erase(index);
|
||||
else index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((id != entity->GetObjectID() || targetSelf) && destroyableComponent->CheckValidity(id, ignoreFaction || includeFaction, targetEnemy, targetFriend)) {
|
||||
targets.push_back(id);
|
||||
// make sure that the entity is targetable
|
||||
if (!CheckTargetingRequirements(candidate)) {
|
||||
index = targets.erase(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
// get factions to check against
|
||||
// CheckTargetingRequirements checks for a destroyable component
|
||||
// but we check again because bounds check are necessary
|
||||
auto candidateDestroyableComponent = candidate->GetComponent<DestroyableComponent>();
|
||||
if (!candidateDestroyableComponent) {
|
||||
index = targets.erase(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
// if they are dead, then earse and continue
|
||||
if (candidateDestroyableComponent->GetIsDead()){
|
||||
index = targets.erase(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
// if their faction is explicitly included, increment and continue
|
||||
auto candidateFactions = candidateDestroyableComponent->GetFactionIDs();
|
||||
if (CheckFactionList(includeFactionList, candidateFactions)){
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if they are a team member
|
||||
if (targetTeam){
|
||||
auto* team = TeamManager::Instance()->GetTeam(this->caster);
|
||||
if (team){
|
||||
// if we find a team member keep it and continue to skip enemy checks
|
||||
if(std::find(team->members.begin(), team->members.end(), candidate->GetObjectID()) != team->members.end()){
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
// if the caster doesn't have a destroyable component, return an empty targets list
|
||||
auto* casterDestroyableComponent = caster->GetComponent<DestroyableComponent>();
|
||||
if (!casterDestroyableComponent) {
|
||||
targets.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we arent targeting a friend, and they are a friend OR
|
||||
// if we are not targeting enemies and they are an enemy OR.
|
||||
// if we are ignoring their faction is explicitly ignored
|
||||
// erase and continue
|
||||
auto isEnemy = casterDestroyableComponent->IsEnemy(candidate);
|
||||
if ((!targetFriend && !isEnemy) ||
|
||||
(!targetEnemy && isEnemy) ||
|
||||
CheckFactionList(ignoreFactionList, candidateFactions)) {
|
||||
index = targets.erase(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// some basic checks as well as the check that matters for this: if the quickbuild is complete
|
||||
bool BehaviorContext::CheckTargetingRequirements(const Entity* target) const {
|
||||
// if the target is a nullptr, then it's not valid
|
||||
if (!target) return false;
|
||||
|
||||
// ignore quickbuilds that aren't completed
|
||||
auto* targetQuickbuildComponent = target->GetComponent<RebuildComponent>();
|
||||
if (targetQuickbuildComponent && targetQuickbuildComponent->GetState() != eRebuildState::COMPLETED) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns true if any of the object factions are in the faction list
|
||||
bool BehaviorContext::CheckFactionList(std::forward_list<int32_t>& factionList, std::vector<int32_t>& objectsFactions) const {
|
||||
if (factionList.empty() || objectsFactions.empty()) return false;
|
||||
for (auto faction : factionList){
|
||||
if(std::find(objectsFactions.begin(), objectsFactions.end(), faction) != objectsFactions.end()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "GameMessages.h"
|
||||
|
||||
#include <vector>
|
||||
#include <forward_list>
|
||||
|
||||
class Behavior;
|
||||
|
||||
@ -106,7 +107,11 @@ struct BehaviorContext
|
||||
|
||||
void Reset();
|
||||
|
||||
std::vector<LWOOBJID> GetValidTargets(int32_t ignoreFaction = 0, int32_t includeFaction = 0, const bool targetSelf = false, const bool targetEnemy = true, const bool targetFriend = false) const;
|
||||
void FilterTargets(std::vector<Entity*>& targetsReference, std::forward_list<int32_t>& ignoreFaction, std::forward_list<int32_t>& includeFaction, const bool targetSelf = false, const bool targetEnemy = true, const bool targetFriend = false, const bool targetTeam = false) const;
|
||||
|
||||
bool CheckTargetingRequirements(const Entity* target) const;
|
||||
|
||||
bool CheckFactionList(std::forward_list<int32_t>& factionList, std::vector<int32_t>& objectsFactions) const;
|
||||
|
||||
explicit BehaviorContext(LWOOBJID originator, bool calculation = false);
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
#ifndef BEHAVIORSLOT_H
|
||||
#define BEHAVIORSLOT_H
|
||||
#include <cstdint>
|
||||
|
||||
enum class BehaviorSlot
|
||||
{
|
||||
enum class BehaviorSlot : int32_t {
|
||||
Invalid = -1,
|
||||
Primary,
|
||||
Offhand,
|
||||
|
@ -12,16 +12,24 @@
|
||||
#include <vector>
|
||||
|
||||
void TacArcBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
if (this->m_targetEnemy && this->m_usePickedTarget && branch.target > 0) {
|
||||
this->m_action->Handle(context, bitStream, branch);
|
||||
std::vector<Entity*> targets = {};
|
||||
|
||||
return;
|
||||
if (this->m_usePickedTarget && branch.target != LWOOBJID_EMPTY) {
|
||||
auto target = Game::entityManager->GetEntity(branch.target);
|
||||
if (!target) Game::logger->Log("TacArcBehavior", "target %llu is null", branch.target);
|
||||
else {
|
||||
targets.push_back(target);
|
||||
context->FilterTargets(targets, this->m_ignoreFactionList, this->m_includeFactionList, this->m_targetSelf, this->m_targetEnemy, this->m_targetFriend, this->m_targetTeam);
|
||||
if (!targets.empty()) {
|
||||
this->m_action->Handle(context, bitStream, branch);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hit = false;
|
||||
|
||||
if (!bitStream->Read(hit)) {
|
||||
Game::logger->Log("TacArcBehavior", "Unable to read hit from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
bool hasTargets = false;
|
||||
if (!bitStream->Read(hasTargets)) {
|
||||
Game::logger->Log("TacArcBehavior", "Unable to read hasTargets from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
|
||||
@ -35,26 +43,23 @@ void TacArcBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStre
|
||||
|
||||
if (blocked) {
|
||||
this->m_blockedAction->Handle(context, bitStream, branch);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (hit) {
|
||||
if (hasTargets) {
|
||||
uint32_t count = 0;
|
||||
|
||||
if (!bitStream->Read(count)) {
|
||||
Game::logger->Log("TacArcBehavior", "Unable to read count from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
|
||||
if (count > m_maxTargets && m_maxTargets > 0) {
|
||||
count = m_maxTargets;
|
||||
if (count > m_maxTargets) {
|
||||
Game::logger->Log("TacArcBehavior", "Bitstream has too many targets Max:%i Recv:%i", this->m_maxTargets, count);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<LWOOBJID> targets;
|
||||
|
||||
for (auto i = 0u; i < count; ++i) {
|
||||
for (auto i = 0u; i < count; i++) {
|
||||
LWOOBJID id{};
|
||||
|
||||
if (!bitStream->Read(id)) {
|
||||
@ -62,17 +67,19 @@ void TacArcBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStre
|
||||
return;
|
||||
};
|
||||
|
||||
targets.push_back(id);
|
||||
if (id != LWOOBJID_EMPTY) {
|
||||
auto* canidate = Game::entityManager->GetEntity(id);
|
||||
if (canidate) targets.push_back(canidate);
|
||||
} else {
|
||||
Game::logger->Log("TacArcBehavior", "Bitstream has LWOOBJID_EMPTY as a target!");
|
||||
}
|
||||
}
|
||||
|
||||
for (auto target : targets) {
|
||||
branch.target = target;
|
||||
|
||||
branch.target = target->GetObjectID();
|
||||
this->m_action->Handle(context, bitStream, branch);
|
||||
}
|
||||
} else {
|
||||
this->m_missAction->Handle(context, bitStream, branch);
|
||||
}
|
||||
} else this->m_missAction->Handle(context, bitStream, branch);
|
||||
}
|
||||
|
||||
void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
@ -82,23 +89,15 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitS
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* destroyableComponent = self->GetComponent<DestroyableComponent>();
|
||||
|
||||
if ((this->m_usePickedTarget || context->clientInitalized) && branch.target > 0) {
|
||||
const auto* target = Game::entityManager->GetEntity(branch.target);
|
||||
|
||||
if (target == nullptr) {
|
||||
std::vector<Entity*> targets = {};
|
||||
if (this->m_usePickedTarget && branch.target != LWOOBJID_EMPTY) {
|
||||
auto target = Game::entityManager->GetEntity(branch.target);
|
||||
targets.push_back(target);
|
||||
context->FilterTargets(targets, this->m_ignoreFactionList, this->m_includeFactionList, this->m_targetSelf, this->m_targetEnemy, this->m_targetFriend, this->m_targetTeam);
|
||||
if (!targets.empty()) {
|
||||
this->m_action->Handle(context, bitStream, branch);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the game is specific about who to target, check that
|
||||
if (destroyableComponent == nullptr || ((!m_targetFriend && !m_targetEnemy
|
||||
|| m_targetFriend && destroyableComponent->IsFriend(target)
|
||||
|| m_targetEnemy && destroyableComponent->IsEnemy(target)))) {
|
||||
this->m_action->Calculate(context, bitStream, branch);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto* combatAi = self->GetComponent<BaseCombatAIComponent>();
|
||||
@ -107,50 +106,25 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitS
|
||||
|
||||
auto reference = self->GetPosition(); //+ m_offset;
|
||||
|
||||
std::vector<Entity*> targets;
|
||||
targets.clear();
|
||||
|
||||
std::vector<LWOOBJID> validTargets;
|
||||
std::vector<Entity*> validTargets = Game::entityManager->GetEntitiesByProximity(reference, this->m_maxRange);
|
||||
|
||||
if (combatAi != nullptr) {
|
||||
if (combatAi->GetTarget() != LWOOBJID_EMPTY) {
|
||||
validTargets.push_back(combatAi->GetTarget());
|
||||
}
|
||||
}
|
||||
|
||||
// Find all valid targets, based on whether we target enemies or friends
|
||||
for (const auto& contextTarget : context->GetValidTargets()) {
|
||||
if (destroyableComponent != nullptr) {
|
||||
const auto* targetEntity = Game::entityManager->GetEntity(contextTarget);
|
||||
|
||||
if (m_targetEnemy && destroyableComponent->IsEnemy(targetEntity)
|
||||
|| m_targetFriend && destroyableComponent->IsFriend(targetEntity)) {
|
||||
validTargets.push_back(contextTarget);
|
||||
}
|
||||
} else {
|
||||
validTargets.push_back(contextTarget);
|
||||
}
|
||||
}
|
||||
// filter all valid targets, based on whether we target enemies or friends
|
||||
context->FilterTargets(validTargets, this->m_ignoreFactionList, this->m_includeFactionList, this->m_targetSelf, this->m_targetEnemy, this->m_targetFriend, this->m_targetTeam);
|
||||
|
||||
for (auto validTarget : validTargets) {
|
||||
if (targets.size() >= this->m_maxTargets) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto* entity = Game::entityManager->GetEntity(validTarget);
|
||||
|
||||
if (entity == nullptr) {
|
||||
Game::logger->Log("TacArcBehavior", "Invalid target (%llu) for (%llu)!", validTarget, context->originator);
|
||||
|
||||
if (std::find(targets.begin(), targets.end(), validTarget) != targets.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::find(targets.begin(), targets.end(), entity) != targets.end()) {
|
||||
continue;
|
||||
}
|
||||
if (validTarget->GetIsDead()) continue;
|
||||
|
||||
if (entity->GetIsDead()) continue;
|
||||
|
||||
const auto otherPosition = entity->GetPosition();
|
||||
const auto otherPosition = validTarget->GetPosition();
|
||||
|
||||
const auto heightDifference = std::abs(otherPosition.y - casterPosition.y);
|
||||
|
||||
@ -180,8 +154,8 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitS
|
||||
|
||||
const float degreeAngle = std::abs(Vector3::Angle(forward, normalized) * (180 / 3.14) - 180);
|
||||
|
||||
if (distance >= this->m_minDistance && this->m_maxDistance >= distance && degreeAngle <= 2 * this->m_angle) {
|
||||
targets.push_back(entity);
|
||||
if (distance >= this->m_minRange && this->m_maxRange >= distance && degreeAngle <= 2 * this->m_angle) {
|
||||
targets.push_back(validTarget);
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,43 +202,48 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitS
|
||||
}
|
||||
|
||||
void TacArcBehavior::Load() {
|
||||
this->m_usePickedTarget = GetBoolean("use_picked_target");
|
||||
this->m_maxRange = GetFloat("max range");
|
||||
this->m_height = GetFloat("height", 2.2f);
|
||||
this->m_distanceWeight = GetFloat("distance_weight", 0.0f);
|
||||
this->m_angleWeight = GetFloat("angle_weight", 0.0f);
|
||||
this->m_angle = GetFloat("angle", 45.0f);
|
||||
this->m_minRange = GetFloat("min range", 0.0f);
|
||||
this->m_offset = NiPoint3(
|
||||
GetFloat("offset_x", 0.0f),
|
||||
GetFloat("offset_y", 0.0f),
|
||||
GetFloat("offset_z", 0.0f)
|
||||
);
|
||||
this->m_method = GetInt("method", 1);
|
||||
this->m_upperBound = GetFloat("upper_bound", 4.4f);
|
||||
this->m_lowerBound = GetFloat("lower_bound", 0.4f);
|
||||
this->m_usePickedTarget = GetBoolean("use_picked_target", false);
|
||||
this->m_useTargetPostion = GetBoolean("use_target_position", false);
|
||||
this->m_checkEnv = GetBoolean("check_env", false);
|
||||
this->m_useAttackPriority = GetBoolean("use_attack_priority", false);
|
||||
|
||||
this->m_action = GetAction("action");
|
||||
|
||||
this->m_missAction = GetAction("miss action");
|
||||
|
||||
this->m_checkEnv = GetBoolean("check_env");
|
||||
|
||||
this->m_blockedAction = GetAction("blocked action");
|
||||
|
||||
this->m_minDistance = GetFloat("min range");
|
||||
this->m_maxTargets = GetInt("max targets", 100);
|
||||
if (this->m_maxTargets == 0) this->m_maxTargets = 100;
|
||||
|
||||
this->m_maxDistance = GetFloat("max range");
|
||||
this->m_farHeight = GetFloat("far_height", 5.0f);
|
||||
this->m_farWidth = GetFloat("far_width", 5.0f);
|
||||
this->m_nearHeight = GetFloat("near_height", 5.0f);
|
||||
this->m_nearWidth = GetFloat("near_width", 5.0f);
|
||||
|
||||
this->m_maxTargets = GetInt("max targets");
|
||||
|
||||
this->m_targetEnemy = GetBoolean("target_enemy");
|
||||
|
||||
this->m_targetFriend = GetBoolean("target_friend");
|
||||
|
||||
this->m_targetTeam = GetBoolean("target_team");
|
||||
|
||||
this->m_angle = GetFloat("angle");
|
||||
|
||||
this->m_upperBound = GetFloat("upper_bound");
|
||||
|
||||
this->m_lowerBound = GetFloat("lower_bound");
|
||||
|
||||
this->m_farHeight = GetFloat("far_height");
|
||||
|
||||
this->m_farWidth = GetFloat("far_width");
|
||||
|
||||
this->m_method = GetInt("method");
|
||||
|
||||
this->m_offset = {
|
||||
GetFloat("offset_x"),
|
||||
GetFloat("offset_y"),
|
||||
GetFloat("offset_z")
|
||||
};
|
||||
// params after this are needed for filter targets
|
||||
const auto parameters = GetParameterNames();
|
||||
for (const auto& parameter : parameters) {
|
||||
if (parameter.first.rfind("include_faction", 0) == 0) {
|
||||
this->m_includeFactionList.push_front(parameter.second);
|
||||
} else if (parameter.first.rfind("ignore_faction", 0) == 0) {
|
||||
this->m_ignoreFactionList.push_front(parameter.second);
|
||||
}
|
||||
}
|
||||
this->m_targetSelf = GetBoolean("target_caster", false);
|
||||
this->m_targetEnemy = GetBoolean("target_enemy", false);
|
||||
this->m_targetFriend = GetBoolean("target_friend", false);
|
||||
this->m_targetTeam = GetBoolean("target_team", false);
|
||||
}
|
||||
|
@ -2,56 +2,42 @@
|
||||
#include "Behavior.h"
|
||||
#include "dCommonVars.h"
|
||||
#include "NiPoint3.h"
|
||||
#include <forward_list>
|
||||
|
||||
class TacArcBehavior final : public Behavior
|
||||
{
|
||||
class TacArcBehavior final : public Behavior {
|
||||
public:
|
||||
bool m_usePickedTarget;
|
||||
|
||||
Behavior* m_action;
|
||||
|
||||
bool m_checkEnv;
|
||||
|
||||
Behavior* m_missAction;
|
||||
|
||||
Behavior* m_blockedAction;
|
||||
|
||||
float m_minDistance;
|
||||
|
||||
float m_maxDistance;
|
||||
|
||||
uint32_t m_maxTargets;
|
||||
|
||||
bool m_targetEnemy;
|
||||
|
||||
bool m_targetFriend;
|
||||
|
||||
bool m_targetTeam;
|
||||
|
||||
float m_angle;
|
||||
|
||||
float m_upperBound;
|
||||
|
||||
float m_lowerBound;
|
||||
|
||||
float m_farHeight;
|
||||
|
||||
float m_farWidth;
|
||||
|
||||
uint32_t m_method;
|
||||
|
||||
NiPoint3 m_offset;
|
||||
|
||||
/*
|
||||
* Inherited
|
||||
*/
|
||||
|
||||
explicit TacArcBehavior(const uint32_t behavior_id) : Behavior(behavior_id) {
|
||||
}
|
||||
|
||||
explicit TacArcBehavior(const uint32_t behavior_id) : Behavior(behavior_id) {}
|
||||
void Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override;
|
||||
|
||||
void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override;
|
||||
|
||||
void Load() override;
|
||||
private:
|
||||
float m_maxRange;
|
||||
float m_height;
|
||||
float m_distanceWeight;
|
||||
float m_angleWeight;
|
||||
float m_angle;
|
||||
float m_minRange;
|
||||
NiPoint3 m_offset;
|
||||
uint32_t m_method;
|
||||
float m_upperBound;
|
||||
float m_lowerBound;
|
||||
bool m_usePickedTarget;
|
||||
bool m_useTargetPostion;
|
||||
bool m_checkEnv;
|
||||
bool m_useAttackPriority;
|
||||
Behavior* m_action;
|
||||
Behavior* m_missAction;
|
||||
Behavior* m_blockedAction;
|
||||
uint32_t m_maxTargets;
|
||||
float m_farHeight;
|
||||
float m_farWidth;
|
||||
float m_nearHeight;
|
||||
float m_nearWidth;
|
||||
|
||||
std::forward_list<int32_t> m_ignoreFactionList {};
|
||||
std::forward_list<int32_t> m_includeFactionList {};
|
||||
bool m_targetSelf;
|
||||
bool m_targetEnemy;
|
||||
bool m_targetFriend;
|
||||
bool m_targetTeam;
|
||||
};
|
||||
|
@ -363,9 +363,10 @@ void DestroyableComponent::SetIsShielded(bool value) {
|
||||
|
||||
void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignoreChecks) {
|
||||
// Ignore factionID -1
|
||||
if (factionID == -1 && !ignoreChecks) {
|
||||
return;
|
||||
}
|
||||
if (factionID == -1 && !ignoreChecks) return;
|
||||
|
||||
// if we already have that faction, don't add it again
|
||||
if (std::find(m_FactionIDs.begin(), m_FactionIDs.end(), factionID) != m_FactionIDs.end()) return;
|
||||
|
||||
m_FactionIDs.push_back(factionID);
|
||||
m_DirtyHealth = true;
|
||||
@ -407,6 +408,14 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
|
||||
}
|
||||
|
||||
bool DestroyableComponent::IsEnemy(const Entity* other) const {
|
||||
if (m_Parent->IsPlayer() && other->IsPlayer()){
|
||||
auto* thisCharacterComponent = m_Parent->GetComponent<CharacterComponent>();
|
||||
if (!thisCharacterComponent) return false;
|
||||
auto* otherCharacterComponent = other->GetComponent<CharacterComponent>();
|
||||
if (!otherCharacterComponent) return false;
|
||||
if (thisCharacterComponent->GetPvpEnabled() && otherCharacterComponent->GetPvpEnabled()) return true;
|
||||
return false;
|
||||
}
|
||||
const auto* otherDestroyableComponent = other->GetComponent<DestroyableComponent>();
|
||||
if (otherDestroyableComponent != nullptr) {
|
||||
for (const auto enemyFaction : m_EnemyFactionIDs) {
|
||||
@ -485,43 +494,6 @@ Entity* DestroyableComponent::GetKiller() const {
|
||||
return Game::entityManager->GetEntity(m_KillerID);
|
||||
}
|
||||
|
||||
bool DestroyableComponent::CheckValidity(const LWOOBJID target, const bool ignoreFactions, const bool targetEnemy, const bool targetFriend) const {
|
||||
auto* targetEntity = Game::entityManager->GetEntity(target);
|
||||
|
||||
if (targetEntity == nullptr) {
|
||||
Game::logger->Log("DestroyableComponent", "Invalid entity for checking validity (%llu)!", target);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* targetDestroyable = targetEntity->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (targetDestroyable == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* targetQuickbuild = targetEntity->GetComponent<RebuildComponent>();
|
||||
|
||||
if (targetQuickbuild != nullptr) {
|
||||
const auto state = targetQuickbuild->GetState();
|
||||
|
||||
if (state != eRebuildState::COMPLETED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ignoreFactions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get if the target entity is an enemy and friend
|
||||
bool isEnemy = IsEnemy(targetEntity);
|
||||
bool isFriend = IsFriend(targetEntity);
|
||||
|
||||
// Return true if the target type matches what we are targeting
|
||||
return (isEnemy && targetEnemy) || (isFriend && targetFriend);
|
||||
}
|
||||
|
||||
|
||||
void DestroyableComponent::Heal(const uint32_t health) {
|
||||
auto current = static_cast<uint32_t>(GetHealth());
|
||||
const auto max = static_cast<uint32_t>(GetMaxHealth());
|
||||
|
@ -371,14 +371,6 @@ public:
|
||||
*/
|
||||
Entity* GetKiller() const;
|
||||
|
||||
/**
|
||||
* Checks if the target ID is a valid enemy of this entity
|
||||
* @param target the target ID to check for
|
||||
* @param ignoreFactions whether or not check for the factions, e.g. just return true if the entity cannot be smashed
|
||||
* @return if the target ID is a valid enemy
|
||||
*/
|
||||
bool CheckValidity(LWOOBJID target, bool ignoreFactions = false, bool targetEnemy = true, bool targetFriend = false) const;
|
||||
|
||||
/**
|
||||
* Attempt to damage this entity, handles everything from health and armor to absorption, immunity and callbacks.
|
||||
* @param damage the damage to attempt to apply
|
||||
|
@ -1158,19 +1158,7 @@ void InventoryComponent::AddItemSkills(const LOT lot) {
|
||||
|
||||
const auto skill = FindSkill(lot);
|
||||
|
||||
if (skill == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index != m_Skills.end()) {
|
||||
const auto old = index->second;
|
||||
|
||||
GameMessages::SendRemoveSkill(m_Parent, old);
|
||||
}
|
||||
|
||||
GameMessages::SendAddSkill(m_Parent, skill, static_cast<int>(slot));
|
||||
|
||||
m_Skills.insert_or_assign(slot, skill);
|
||||
SetSkill(slot, skill);
|
||||
}
|
||||
|
||||
void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
||||
@ -1197,7 +1185,7 @@ void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
||||
if (slot == BehaviorSlot::Primary) {
|
||||
m_Skills.insert_or_assign(BehaviorSlot::Primary, 1);
|
||||
|
||||
GameMessages::SendAddSkill(m_Parent, 1, static_cast<int>(BehaviorSlot::Primary));
|
||||
GameMessages::SendAddSkill(m_Parent, 1, BehaviorSlot::Primary);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1627,3 +1615,29 @@ void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument* document) {
|
||||
petInventoryElement->LinkEndChild(petElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId){
|
||||
BehaviorSlot behaviorSlot = BehaviorSlot::Invalid;
|
||||
if (slot == 1 ) behaviorSlot = BehaviorSlot::Primary;
|
||||
else if (slot == 2 ) behaviorSlot = BehaviorSlot::Offhand;
|
||||
else if (slot == 3 ) behaviorSlot = BehaviorSlot::Neck;
|
||||
else if (slot == 4 ) behaviorSlot = BehaviorSlot::Head;
|
||||
else if (slot == 5 ) behaviorSlot = BehaviorSlot::Consumable;
|
||||
else return false;
|
||||
return SetSkill(behaviorSlot, skillId);
|
||||
}
|
||||
|
||||
bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){
|
||||
if (skillId == 0) return false;
|
||||
const auto index = m_Skills.find(slot);
|
||||
if (index != m_Skills.end()) {
|
||||
const auto old = index->second;
|
||||
GameMessages::SendRemoveSkill(m_Parent, old);
|
||||
}
|
||||
|
||||
GameMessages::SendAddSkill(m_Parent, skillId, slot);
|
||||
m_Skills.insert_or_assign(slot, skillId);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -367,6 +367,11 @@ public:
|
||||
*/
|
||||
void UnequipScripts(Item* unequippedItem);
|
||||
|
||||
std::map<BehaviorSlot, uint32_t> GetSkills(){ return m_Skills; };
|
||||
|
||||
bool SetSkill(int slot, uint32_t skillId);
|
||||
bool SetSkill(BehaviorSlot slot, uint32_t skillId);
|
||||
|
||||
~InventoryComponent() override;
|
||||
|
||||
private:
|
||||
|
@ -1159,7 +1159,7 @@ void GameMessages::SendPlayerReachedRespawnCheckpoint(Entity* entity, const NiPo
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void GameMessages::SendAddSkill(Entity* entity, TSkillID skillID, int slotID) {
|
||||
void GameMessages::SendAddSkill(Entity* entity, TSkillID skillID, BehaviorSlot slotID) {
|
||||
int AICombatWeight = 0;
|
||||
bool bFromSkillSet = false;
|
||||
int castType = 0;
|
||||
@ -1189,8 +1189,8 @@ void GameMessages::SendAddSkill(Entity* entity, TSkillID skillID, int slotID) {
|
||||
|
||||
bitStream.Write(skillID);
|
||||
|
||||
bitStream.Write(slotID != -1);
|
||||
if (slotID != -1) bitStream.Write(slotID);
|
||||
bitStream.Write(slotID != BehaviorSlot::Invalid);
|
||||
if (slotID != BehaviorSlot::Invalid) bitStream.Write(slotID);
|
||||
|
||||
bitStream.Write(temporary);
|
||||
|
||||
|
@ -36,6 +36,7 @@ enum class ePetTamingNotifyType : uint32_t;
|
||||
enum class eUseItemResponse : uint32_t;
|
||||
enum class eQuickBuildFailReason : uint32_t;
|
||||
enum class eRebuildState : uint32_t;
|
||||
enum class BehaviorSlot : int32_t;
|
||||
|
||||
namespace GameMessages {
|
||||
class PropertyDataMessage;
|
||||
@ -119,7 +120,7 @@ namespace GameMessages {
|
||||
void SendSetPlayerControlScheme(Entity* entity, eControlScheme controlScheme);
|
||||
void SendPlayerReachedRespawnCheckpoint(Entity* entity, const NiPoint3& position, const NiQuaternion& rotation);
|
||||
|
||||
void SendAddSkill(Entity* entity, TSkillID skillID, int slotID);
|
||||
void SendAddSkill(Entity* entity, TSkillID skillID, BehaviorSlot slotID);
|
||||
void SendRemoveSkill(Entity* entity, TSkillID skillID);
|
||||
|
||||
void SendFinishArrangingWithItem(Entity* entity, const LWOOBJID& buildAreaID);
|
||||
|
@ -1841,6 +1841,86 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Deleted inventory " + GeneralUtils::UTF8ToUTF16(args[0]));
|
||||
}
|
||||
|
||||
if (chatCommand == "castskill" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
|
||||
auto* skillComponent = entity->GetComponent<SkillComponent>();
|
||||
if (skillComponent){
|
||||
uint32_t skillId;
|
||||
|
||||
if (!GeneralUtils::TryParse(args[0], skillId)) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill ID.");
|
||||
return;
|
||||
} else {
|
||||
skillComponent->CastSkill(skillId, entity->GetObjectID(), entity->GetObjectID());
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Cast skill");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chatCommand == "setskillslot" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 2) {
|
||||
uint32_t skillId;
|
||||
int slot;
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
if (inventoryComponent){
|
||||
if (!GeneralUtils::TryParse(args[0], slot)) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot.");
|
||||
return;
|
||||
} else {
|
||||
if (!GeneralUtils::TryParse(args[1], skillId)) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill.");
|
||||
return;
|
||||
} else {
|
||||
if(inventoryComponent->SetSkill(slot, skillId)) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully");
|
||||
else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chatCommand == "setfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
|
||||
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
if (destroyableComponent){
|
||||
int32_t faction;
|
||||
|
||||
if (!GeneralUtils::TryParse(args[0], faction)) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction.");
|
||||
return;
|
||||
} else {
|
||||
destroyableComponent->SetFaction(faction);
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Set faction and updated enemies list");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chatCommand == "addfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
|
||||
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
if (destroyableComponent){
|
||||
int32_t faction;
|
||||
|
||||
if (!GeneralUtils::TryParse(args[0], faction)) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction.");
|
||||
return;
|
||||
} else {
|
||||
destroyableComponent->AddFaction(faction);
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Added faction and updated enemies list");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chatCommand == "getfactions" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
||||
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
if (destroyableComponent){
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:");
|
||||
for (const auto entry : destroyableComponent->GetFactionIDs()) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry)));
|
||||
}
|
||||
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Enemy factions:");
|
||||
for (const auto entry : destroyableComponent->GetEnemyFactionsIDs()) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chatCommand == "inspect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
|
||||
Entity* closest = nullptr;
|
||||
|
||||
@ -1980,6 +2060,14 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
||||
destuctable->SetFaction(-1);
|
||||
destuctable->AddFaction(faction, true);
|
||||
}
|
||||
} else if (args[1] == "-cf") {
|
||||
auto* destuctable = entity->GetComponent<DestroyableComponent>();
|
||||
if (!destuctable) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!");
|
||||
return;
|
||||
}
|
||||
if (destuctable->IsEnemy(closest)) ChatPackets::SendSystemMessage(sysAddr, u"They are our enemy");
|
||||
else ChatPackets::SendSystemMessage(sysAddr, u"They are NOT our enemy");
|
||||
} else if (args[1] == "-t") {
|
||||
auto* phantomPhysicsComponent = closest->GetComponent<PhantomPhysicsComponent>();
|
||||
|
||||
|
@ -106,7 +106,11 @@ These commands are primarily for development and testing. The usage of many of t
|
||||
|Set Level|`/setlevel <requested_level> (username)`|Sets the using entities level to the requested level. Takes an optional parameter of an in-game players username to set the level of.|8|
|
||||
|crash|`/crash`|Crashes the server.|9|
|
||||
|rollloot|`/rollloot <loot matrix index> <item id> <amount>`|Given a `loot matrix index`, look for `item id` in that matrix `amount` times and print to the chat box statistics of rolling that loot matrix.|9|
|
||||
|
||||
|castskill|`/castskill <skill id>`|Casts the skill as the player|9|
|
||||
|setskillslot|`/setskillslot <slot> <skill id>`||8|
|
||||
|setfaction|`/setfaction <faction id>`|Clears the users current factions and sets it|8|
|
||||
|addfaction|`/addfaction <faction id>`|Add the faction to the users list of factions|8|
|
||||
|getfactions|`/getfactions`|Shows the player's factions|8|
|
||||
## Detailed `/inspect` Usage
|
||||
|
||||
`/inspect <component> (-m <waypoint> | -a <animation> | -s | -p | -f (faction) | -t)`
|
||||
@ -120,6 +124,7 @@ Finds the closest entity with the given component or LDF variable (ignoring play
|
||||
* `-s`: Prints the entity's settings and spawner ID.
|
||||
* `-p`: Prints the entity's position
|
||||
* `-f`: If the entity has a destroyable component, prints whether the entity is smashable and its friendly and enemy faction IDs; if `faction` is specified, adds that faction to the entity.
|
||||
* `-cf`: check if the entity is enemy or friend
|
||||
* `-t`: If the entity has a phantom physics component, prints the effect type, direction, directional multiplier, and whether the effect is active; in any case, if the entity has a trigger, prints the trigger ID.
|
||||
|
||||
## Game Master Levels
|
||||
|
Loading…
Reference in New Issue
Block a user