mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 07:47:13 +00:00
feat(destroyable): enemy preconditions from the level, as the client
An object's EnemyPrecondition (level config) decides who it is an enemy of: whoever asks and doesn't meet the preconditions, whatever the factions (LWODestroyableComponent::msgIsEnemy 0x00c9e930; ',' ';' ':' join groups that must all pass, '|' alternatives). Forbidden Valley Siege's objects use it today; any level object can. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
#include "BaseCombatAIComponent.h"
|
||||
#include "TeamManager.h"
|
||||
#include "BuffComponent.h"
|
||||
#include "Preconditions.h"
|
||||
#include "SkillComponent.h"
|
||||
#include "Item.h"
|
||||
#include "Amf3.h"
|
||||
@@ -163,6 +164,9 @@ void DestroyableComponent::LoadDataFromTemplate() {
|
||||
// The level config (LWODestroyableComponent::LoadConfigData 0x00c44cb0 reads it for every destroyable): is_smashable,
|
||||
// set_faction / override_faction and the smashable loot matrix
|
||||
void DestroyableComponent::LoadConfigData() {
|
||||
const auto enemyPrecondition = m_Parent->GetVarAsString(u"EnemyPrecondition");
|
||||
if (!enemyPrecondition.empty()) m_EnemyPrecondition = std::make_unique<PreconditionExpression>(enemyPrecondition);
|
||||
|
||||
const bool isSmashable = m_Parent->GetVarAs<int32_t>(u"is_smashable") != 0;
|
||||
const bool isCharacter = m_Parent->GetCharacter() != nullptr;
|
||||
if (!isCharacter) {
|
||||
@@ -536,6 +540,10 @@ bool DestroyableComponent::IsEnemy(const Entity* other) const {
|
||||
return false;
|
||||
}
|
||||
const auto* otherDestroyableComponent = other->GetComponent<DestroyableComponent>();
|
||||
// An object with enemy preconditions is an enemy unless this one meets them; factions don't count then
|
||||
if (otherDestroyableComponent && otherDestroyableComponent->GetEnemyPrecondition()) {
|
||||
return !otherDestroyableComponent->GetEnemyPrecondition()->Check(m_Parent);
|
||||
}
|
||||
if (otherDestroyableComponent != nullptr) {
|
||||
for (const auto enemyFaction : m_EnemyFactionIDs) {
|
||||
for (const auto otherFaction : otherDestroyableComponent->GetFactionIDs()) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define DESTROYABLECOMPONENT_H
|
||||
|
||||
#include "RakNetTypes.h"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include "tinyxml2.h"
|
||||
@@ -10,6 +11,8 @@
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "Implementation.h"
|
||||
|
||||
class PreconditionExpression;
|
||||
|
||||
struct ObjectLoadState;
|
||||
|
||||
namespace GameMessages {
|
||||
@@ -471,6 +474,10 @@ public:
|
||||
*/
|
||||
static void Resurrect(Entity& entity);
|
||||
|
||||
// The level's EnemyPrecondition: an object asking whether this one is its enemy must meet it, or it is one
|
||||
// (LWODestroyableComponent::msgIsEnemy 0x00c9e930); its factions don't count then
|
||||
const PreconditionExpression* GetEnemyPrecondition() const { return m_EnemyPrecondition.get(); }
|
||||
|
||||
// Whether a resurrection has started and not finished (the client's isResurrecting)
|
||||
bool GetIsResurrecting() const { return m_IsResurrecting; }
|
||||
|
||||
@@ -640,6 +647,9 @@ private:
|
||||
// A resurrection has started (Resurrect) and its respawn animation hasn't finished
|
||||
bool m_IsResurrecting = false;
|
||||
|
||||
// The level config's EnemyPrecondition, when it has one
|
||||
std::unique_ptr<PreconditionExpression> m_EnemyPrecondition;
|
||||
|
||||
/**
|
||||
* Whether this entity is smashed. Unused, here for serialization
|
||||
*/
|
||||
|
||||
@@ -731,3 +731,23 @@ TEST_F(DestroyableTest, RequestResurrectOnlyWhenDead) {
|
||||
request.Handle(*baseEntity, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
EXPECT_TRUE(destroyableComponent->GetIsResurrecting());
|
||||
}
|
||||
|
||||
// LWODestroyableComponent::msgIsEnemy: a target with enemy preconditions is an enemy of whoever doesn't meet them,
|
||||
// whatever its factions
|
||||
TEST_F(DestroyableTest, EnemyPreconditionDecidesEnemies) {
|
||||
if (!CDClientDatabase::isConnected) CDClientDatabase::Connect(":memory:");
|
||||
CDClientDatabase::ExecuteDML("CREATE TABLE IF NOT EXISTS Preconditions (id INTEGER, type INTEGER, targetLOT TEXT, targetCount INTEGER);");
|
||||
CDClientDatabase::ExecuteDML("INSERT INTO Preconditions VALUES (90201, 2, '6194', 1);"); // has an item the asker lacks
|
||||
CDClientDatabase::ExecuteDML("INSERT INTO Preconditions VALUES (90202, 2, '', 1);"); // always met
|
||||
|
||||
Entity target(0x0102030405060709LL, info);
|
||||
auto* targetDestroyable = target.AddComponent<DestroyableComponent>(-1);
|
||||
|
||||
target.SetVar<std::string>(u"EnemyPrecondition", "90201");
|
||||
targetDestroyable->LoadConfigData();
|
||||
EXPECT_TRUE(destroyableComponent->IsEnemy(&target));
|
||||
|
||||
target.SetVar<std::string>(u"EnemyPrecondition", "90202");
|
||||
targetDestroyable->LoadConfigData();
|
||||
EXPECT_FALSE(destroyableComponent->IsEnemy(&target));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user