mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
Volumes on the server touched far more than in the client: - An enemy's body in the physics world was a sphere the size of its aggro radius, so trigger and damage volumes caught enemies from far away (Cavalry Hill enemies taking damage on spawn). It is now the enemy's own radius and collision group from its physics component. - Trigger volumes ignored their collision group and caught everything. dpEntity now filters with the client's collision filter (PeCollisionFilter, 1.10.64 0x00fb6940, group table from 0x00fcf9a0): POI walls ignore enemies, threat clearing walls ignore players, and so on. The aggro sensor keeps seeing only players. - Rotated boxes were tested as the axis aligned box around them, which for a turned wall covers a big square (the AG survival boundary). Sphere and point tests now use the box's own axes. Proximity monitors take an optional collision group like live's SetProximityRadius; the AM shield generators use live's (10 finds enemies, 1 finds players). Fixes #1127 Refs #1971 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
205 lines
5.8 KiB
C++
205 lines
5.8 KiB
C++
#include "AmShieldGeneratorQuickbuild.h"
|
|
#include "EntityManager.h"
|
|
#include "DestroyableComponent.h"
|
|
#include "GameMessages.h"
|
|
#include "EffectsMessages.h"
|
|
#include "MovementAIComponent.h"
|
|
#include "BaseCombatAIComponent.h"
|
|
#include "SkillComponent.h"
|
|
#include "EntityInfo.h"
|
|
#include "QuickBuildComponent.h"
|
|
#include "MissionComponent.h"
|
|
|
|
void AmShieldGeneratorQuickbuild::OnStartup(Entity* self) {
|
|
// Enemies (the client group table: 10 sees group 12) and players (1 sees group 10), like live
|
|
self->SetProximityRadius(20, "shield", 10);
|
|
self->SetProximityRadius(21, "buffer", 1);
|
|
}
|
|
|
|
void AmShieldGeneratorQuickbuild::OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) {
|
|
auto* destroyableComponent = entering->GetComponent<DestroyableComponent>();
|
|
|
|
if (name == "shield") {
|
|
if (!destroyableComponent->HasFaction(4) || entering->IsPlayer()) {
|
|
return;
|
|
}
|
|
|
|
auto enemiesInProximity = self->GetVar<std::vector<LWOOBJID>>(u"Enemies");
|
|
|
|
if (status == "ENTER") {
|
|
EnemyEnteredShield(self, entering);
|
|
|
|
const auto& iter = std::find(enemiesInProximity.begin(), enemiesInProximity.end(), entering->GetObjectID());
|
|
|
|
if (iter == enemiesInProximity.end()) {
|
|
enemiesInProximity.push_back(entering->GetObjectID());
|
|
}
|
|
} else if (status == "LEAVE") {
|
|
const auto& iter = std::find(enemiesInProximity.begin(), enemiesInProximity.end(), entering->GetObjectID());
|
|
|
|
if (iter != enemiesInProximity.end()) {
|
|
enemiesInProximity.erase(iter);
|
|
}
|
|
}
|
|
|
|
self->SetVar<std::vector<LWOOBJID>>(u"Enemies", enemiesInProximity);
|
|
}
|
|
|
|
if (name != "buffer" || !entering->IsPlayer()) {
|
|
return;
|
|
}
|
|
|
|
auto entitiesInProximity = self->GetVar<std::vector<LWOOBJID>>(u"Players");
|
|
|
|
if (status == "ENTER") {
|
|
const auto& iter = std::find(entitiesInProximity.begin(), entitiesInProximity.end(), entering->GetObjectID());
|
|
|
|
if (iter == entitiesInProximity.end()) {
|
|
entitiesInProximity.push_back(entering->GetObjectID());
|
|
}
|
|
} else if (status == "LEAVE") {
|
|
const auto& iter = std::find(entitiesInProximity.begin(), entitiesInProximity.end(), entering->GetObjectID());
|
|
|
|
if (iter != entitiesInProximity.end()) {
|
|
entitiesInProximity.erase(iter);
|
|
}
|
|
}
|
|
|
|
self->SetVar<std::vector<LWOOBJID>>(u"Players", entitiesInProximity);
|
|
}
|
|
|
|
void AmShieldGeneratorQuickbuild::OnDie(Entity* self, Entity* killer) {
|
|
self->CancelAllTimers();
|
|
|
|
auto* child = Game::entityManager->GetEntity(self->GetVar<LWOOBJID>(u"Child"));
|
|
|
|
if (child != nullptr) {
|
|
child->Kill();
|
|
}
|
|
}
|
|
|
|
void AmShieldGeneratorQuickbuild::OnTimerDone(Entity* self, std::string timerName) {
|
|
if (timerName == "BuffPlayers") {
|
|
BuffPlayers(self);
|
|
|
|
self->AddTimer("BuffPlayers", 3.0f);
|
|
} else if (timerName == "PlayFX") {
|
|
GameMessages::PlayFXEffect(self->GetObjectID(), 5351, u"generatorOn", "generatorOn").Send(UNASSIGNED_SYSTEM_ADDRESS);
|
|
|
|
self->AddTimer("PlayFX", 1.5f);
|
|
} else if (timerName == "RefreshEnemies") {
|
|
auto enemiesInProximity = self->GetVar<std::vector<LWOOBJID>>(u"Enemies");
|
|
|
|
for (const auto enemyID : enemiesInProximity) {
|
|
auto* enemy = Game::entityManager->GetEntity(enemyID);
|
|
|
|
if (enemy != nullptr) {
|
|
EnemyEnteredShield(self, enemy);
|
|
}
|
|
}
|
|
|
|
self->AddTimer("RefreshEnemies", 1.5f);
|
|
}
|
|
}
|
|
|
|
void AmShieldGeneratorQuickbuild::OnQuickBuildComplete(Entity* self, Entity* target) {
|
|
StartShield(self);
|
|
|
|
auto enemiesInProximity = self->GetVar<std::vector<LWOOBJID>>(u"Enemies");
|
|
|
|
for (const auto enemyID : enemiesInProximity) {
|
|
auto* enemy = Game::entityManager->GetEntity(enemyID);
|
|
|
|
if (enemy != nullptr) {
|
|
enemy->Smash();
|
|
}
|
|
}
|
|
|
|
auto entitiesInProximity = self->GetVar<std::vector<LWOOBJID>>(u"Players");
|
|
|
|
for (const auto playerID : entitiesInProximity) {
|
|
auto* player = Game::entityManager->GetEntity(playerID);
|
|
|
|
if (player == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
auto* missionComponent = player->GetComponent<MissionComponent>();
|
|
|
|
if (missionComponent == nullptr) {
|
|
return;
|
|
}
|
|
|
|
missionComponent->ForceProgressTaskType(987, 1, 1, false);
|
|
}
|
|
}
|
|
|
|
void AmShieldGeneratorQuickbuild::StartShield(Entity* self) {
|
|
self->AddTimer("PlayFX", 1.5f);
|
|
self->AddTimer("BuffPlayers", 3.0f);
|
|
self->AddTimer("RefreshEnemies", 1.5f);
|
|
|
|
const auto myPos = self->GetPosition();
|
|
const auto myRot = self->GetRotation();
|
|
|
|
EntityInfo info{};
|
|
info.lot = 13111;
|
|
info.pos = myPos;
|
|
info.rot = myRot;
|
|
info.spawnerID = self->GetObjectID();
|
|
|
|
auto* child = Game::entityManager->CreateEntity(info);
|
|
|
|
self->SetVar(u"Child", child->GetObjectID());
|
|
|
|
BuffPlayers(self);
|
|
}
|
|
|
|
void AmShieldGeneratorQuickbuild::BuffPlayers(Entity* self) {
|
|
auto* skillComponent = self->GetComponent<SkillComponent>();
|
|
|
|
if (skillComponent == nullptr) {
|
|
return;
|
|
}
|
|
|
|
auto entitiesInProximity = self->GetVar<std::vector<LWOOBJID>>(u"Players");
|
|
|
|
for (const auto playerID : entitiesInProximity) {
|
|
auto* player = Game::entityManager->GetEntity(playerID);
|
|
|
|
if (player == nullptr) {
|
|
return;
|
|
}
|
|
|
|
skillComponent->CalculateBehavior(1200, 27024, playerID, true);
|
|
}
|
|
}
|
|
|
|
void AmShieldGeneratorQuickbuild::EnemyEnteredShield(Entity* self, Entity* intruder) {
|
|
auto* quickBuildComponent = self->GetComponent<QuickBuildComponent>();
|
|
|
|
if (quickBuildComponent == nullptr || quickBuildComponent->GetState() != eQuickBuildState::COMPLETED) {
|
|
return;
|
|
}
|
|
|
|
auto* baseCombatAIComponent = intruder->GetComponent<BaseCombatAIComponent>();
|
|
auto* movementAIComponent = intruder->GetComponent<MovementAIComponent>();
|
|
|
|
if (baseCombatAIComponent == nullptr || movementAIComponent == nullptr) {
|
|
return;
|
|
}
|
|
|
|
// Knock the enemy back the way it came
|
|
auto dir = QuatUtils::Forward(intruder->GetRotation()) * -1;
|
|
dir.y += 15;
|
|
dir.x *= 50;
|
|
dir.z *= 50;
|
|
|
|
if (NiPoint3::DistanceSquared(self->GetPosition(), intruder->GetPosition()) < 20 * 20) {
|
|
movementAIComponent->Knockback(dir);
|
|
}
|
|
|
|
// Forget the players so it doesn't just run into the shield again
|
|
baseCombatAIComponent->ClearThreat();
|
|
}
|