Additional record types

This commit is contained in:
wincent 2024-09-12 15:32:44 +02:00
parent dfe924061f
commit 04e9e74d7c
8 changed files with 504 additions and 1 deletions

View File

@ -10,6 +10,7 @@
#include "ServerPreconditions.h" #include "ServerPreconditions.h"
#include "MovementAIComponent.h" #include "MovementAIComponent.h"
#include "BaseCombatAIComponent.h" #include "BaseCombatAIComponent.h"
#include "MissionComponent.h"
using namespace Cinema::Recording; using namespace Cinema::Recording;
@ -84,7 +85,14 @@ void Recorder::ActingDispatch(Entity* actor, const std::vector<Record*>& records
} else if (!forkRecord->precondition.empty()) { } else if (!forkRecord->precondition.empty()) {
auto precondtion = Preconditions::CreateExpression(forkRecord->precondition); auto precondtion = Preconditions::CreateExpression(forkRecord->precondition);
success = precondtion.Check(actor); auto* playerEntity = Game::entityManager->GetEntity(variables->player);
if (playerEntity != nullptr) {
success = precondtion.Check(playerEntity);
}
else {
success = true;
}
} else { } else {
success = true; success = true;
} }
@ -224,6 +232,96 @@ void Recorder::ActingDispatch(Entity* actor, const std::vector<Record*>& records
} }
} }
// Check if the record is a companion record
auto* companionRecord = dynamic_cast<CompanionRecord*>(record);
if (companionRecord && variables != nullptr) {
EntityInfo info;
info.lot = actor->GetLOT();
info.pos = actor->GetPosition();
info.rot = actor->GetRotation();
info.scale = 1;
info.spawner = nullptr;
info.spawnerID = variables->player;
info.spawnerNodeID = 0;
info.settings = {
new LDFData<std::vector<std::u16string>>(u"syncLDF", { u"custom_script_client" }),
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
};
// Spawn it
auto* companion = Game::entityManager->CreateEntity(info);
// Construct it
Game::entityManager->ConstructEntity(companion);
CompanionRecord::SetCompanion(companion, variables->player);
if (!companionRecord->records.empty()) {
ActingDispatch(companion, companionRecord->records, 0, variables);
}
variables->entities.emplace(companion->GetObjectID());
}
auto* spawnRecord = dynamic_cast<SpawnRecord*>(record);
if (spawnRecord && variables != nullptr) {
EntityInfo info;
info.lot = spawnRecord->lot;
info.pos = spawnRecord->position;
info.rot = spawnRecord->rotation;
info.scale = 1;
info.spawner = nullptr;
info.spawnerID = variables->player;
info.spawnerNodeID = 0;
info.settings = {
new LDFData<std::vector<std::u16string>>(u"syncLDF", { u"custom_script_client" }),
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
};
// Spawn it
auto* entity = Game::entityManager->CreateEntity(info);
// Construct it
Game::entityManager->ConstructEntity(entity);
variables->entities.emplace(entity->GetObjectID());
if (!spawnRecord->onSpawnRecords.empty()) {
ActingDispatch(entity, spawnRecord->onSpawnRecords, 0, variables);
}
entity->AddDieCallback([entity, variables, spawnRecord]() {
variables->entities.erase(entity->GetObjectID());
ActingDispatch(entity, spawnRecord->onDespawnRecords, 0, variables);
});
auto* combatAIComponent = entity->GetComponent<BaseCombatAIComponent>();
if (combatAIComponent) {
combatAIComponent->SetAggroRadius(200);
combatAIComponent->SetSoftTetherRadius(200);
combatAIComponent->SetHardTetherRadius(200);
combatAIComponent->SetTarget(variables->player);
}
}
auto* missionRecord = dynamic_cast<MissionRecord*>(record);
if (missionRecord && variables != nullptr) {
auto* playerEntity = Game::entityManager->GetEntity(variables->player);
if (playerEntity) {
auto* missionComponent = playerEntity->GetComponent<MissionComponent>();
if (missionComponent) {
missionComponent->CompleteMission(missionRecord->mission);
}
}
}
// Check if the record is a visibility record // Check if the record is a visibility record
auto* visibilityRecord = dynamic_cast<VisibilityRecord*>(record); auto* visibilityRecord = dynamic_cast<VisibilityRecord*>(record);
@ -414,6 +512,12 @@ void Cinema::Recording::Recorder::LoadRecords(tinyxml2::XMLElement* root, std::v
record = new PathFindRecord(); record = new PathFindRecord();
} else if (name == "CombatAIRecord") { } else if (name == "CombatAIRecord") {
record = new CombatAIRecord(); record = new CombatAIRecord();
} else if (name == "CompanionRecord") {
record = new CompanionRecord();
} else if (name == "SpawnRecord") {
record = new SpawnRecord();
} else if (name == "MissionRecord") {
record = new MissionRecord();
} else { } else {
LOG("Unknown record type: %s", name.c_str()); LOG("Unknown record type: %s", name.c_str());
continue; continue;
@ -1198,3 +1302,170 @@ void Cinema::Recording::CombatAIRecord::Deserialize(tinyxml2::XMLElement* elemen
m_Delay = element->DoubleAttribute("t"); m_Delay = element->DoubleAttribute("t");
} }
void Cinema::Recording::CompanionRecord::Act(Entity* actor) {
}
void Cinema::Recording::CompanionRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
auto* element = document.NewElement("CompanionRecord");
element->SetAttribute("t", m_Delay);
parent->InsertEndChild(element);
}
void Cinema::Recording::CompanionRecord::Deserialize(tinyxml2::XMLElement* element) {
m_Delay = element->DoubleAttribute("t");
records.clear();
Recorder::LoadRecords(element, records);
}
void CompanionProtcol(Entity* entity, LWOOBJID follow) {
auto* followEntity = Game::entityManager->GetEntity(follow);
if (followEntity == nullptr) {
return;
}
auto* movementAI = entity->GetComponent<MovementAIComponent>();
if (movementAI == nullptr) {
movementAI = entity->AddComponent<MovementAIComponent>(MovementAIInfo());
}
if (movementAI == nullptr) {
return;
}
auto* combatAI = entity->GetComponent<BaseCombatAIComponent>();
if (combatAI == nullptr) {
return;
}
const auto distance = NiPoint3::Distance(entity->GetPosition(), followEntity->GetPosition());
combatAI->SetStartPosition(followEntity->GetPosition());
combatAI->SetSoftTetherRadius(15.0f);
combatAI->SetHardTetherRadius(25.0f);
combatAI->SetFocusPosition(followEntity->GetPosition());
combatAI->SetFocusRadius(5.0f);
auto& info = movementAI->GetInfo();
info.wanderChance = 1.0f;
info.wanderDelayMin = 0.5f;
info.wanderDelayMax = 1.0f;
info.wanderSpeed = 1.0f;
/*if (distance > 50.0f) {
movementAI->Warp(followEntity->GetPosition());
Game::entityManager->SerializeEntity(entity);
}*/
/*if (distance > 30.0f) {
movementAI->SetDestination(followEntity->GetPosition());
movementAI->SetHaltDistance(5.0f);
movementAI->SetMaxSpeed(1.0f);
if (combatAI) {
combatAI->SetDisabled(true);
}
}
else {
if (combatAI) {
combatAI->SetDisabled(false);
}
}*/
entity->AddCallbackTimer(1.0f, [entity, follow]() {
CompanionProtcol(entity, follow);
});
}
void Cinema::Recording::CompanionRecord::SetCompanion(Entity* actor, LWOOBJID player) {
CompanionProtcol(actor, player);
}
Cinema::Recording::SpawnRecord::SpawnRecord(LOT lot, const NiPoint3& position, const NiQuaternion& rotation) {
this->lot = lot;
this->position = position;
this->rotation = rotation;
}
void Cinema::Recording::SpawnRecord::Act(Entity* actor) {
}
void Cinema::Recording::SpawnRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
auto* element = document.NewElement("SpawnRecord");
element->SetAttribute("lot", lot);
element->SetAttribute("x", position.x);
element->SetAttribute("y", position.y);
element->SetAttribute("z", position.z);
element->SetAttribute("qx", rotation.x);
element->SetAttribute("qy", rotation.y);
element->SetAttribute("qz", rotation.z);
element->SetAttribute("qw", rotation.w);
element->SetAttribute("t", m_Delay);
parent->InsertEndChild(element);
}
void Cinema::Recording::SpawnRecord::Deserialize(tinyxml2::XMLElement* element) {
lot = element->IntAttribute("lot");
position.x = element->FloatAttribute("x");
position.y = element->FloatAttribute("y");
position.z = element->FloatAttribute("z");
if (element->Attribute("qx")) {
rotation.x = element->FloatAttribute("qx");
rotation.y = element->FloatAttribute("qy");
rotation.z = element->FloatAttribute("qz");
rotation.w = element->FloatAttribute("qw");
}
m_Delay = element->DoubleAttribute("t");
auto* onSpawn = element->FirstChildElement("OnSpawn");
if (onSpawn) {
Recorder::LoadRecords(onSpawn, onSpawnRecords);
}
auto* onDespawn = element->FirstChildElement("OnDespawn");
if (onDespawn) {
Recorder::LoadRecords(onDespawn, onDespawnRecords);
}
}
Cinema::Recording::MissionRecord::MissionRecord(const int32_t& mission) {
this->mission = mission;
}
void Cinema::Recording::MissionRecord::Act(Entity* actor) {
}
void Cinema::Recording::MissionRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
auto* element = document.NewElement("MissionRecord");
element->SetAttribute("mission", mission);
element->SetAttribute("t", m_Delay);
parent->InsertEndChild(element);
}
void Cinema::Recording::MissionRecord::Deserialize(tinyxml2::XMLElement* element) {
mission = element->IntAttribute("mission");
m_Delay = element->DoubleAttribute("t");
}

View File

@ -398,5 +398,60 @@ public:
void Deserialize(tinyxml2::XMLElement* element) override; void Deserialize(tinyxml2::XMLElement* element) override;
}; };
class CompanionRecord : public Record
{
public:
std::vector<Record*> records;
CompanionRecord() = default;
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
static void SetCompanion(Entity* actor, LWOOBJID player);
};
class SpawnRecord : public Record
{
public:
LOT lot = LOT_NULL;
NiPoint3 position;
NiQuaternion rotation;
std::vector<Record*> onSpawnRecords;
std::vector<Record*> onDespawnRecords;
SpawnRecord() = default;
SpawnRecord(LOT lot, const NiPoint3& position, const NiQuaternion& rotation);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class MissionRecord : public Record
{
public:
int32_t mission;
MissionRecord() = default;
MissionRecord(const int32_t& mission);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
} }

View File

@ -13,6 +13,8 @@
#include "SlashCommandHandler.h" #include "SlashCommandHandler.h"
#include "ChatPackets.h" #include "ChatPackets.h"
#include "InventoryComponent.h" #include "InventoryComponent.h"
#include "MovementAIComponent.h"
#include "Recorder.h"
using namespace Cinema; using namespace Cinema;
@ -185,6 +187,14 @@ void Cinema::Scene::AutoLoadScenesForZone(LWOMAPID zone) {
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR .requiredLevel = eGameMasterLevel::LEAD_MODERATOR
}); });
SlashCommandHandler::RegisterCommand(Command{
.help = "",
.info = "",
.aliases = { "companion" },
.handle = CommandCompanion,
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
});
const auto& scenesRoot = Game::config->GetValue("scenes_directory"); const auto& scenesRoot = Game::config->GetValue("scenes_directory");
if (scenesRoot.empty()) { if (scenesRoot.empty()) {
@ -643,3 +653,47 @@ void Cinema::Scene::CommandSceneSetup(Entity* entity, const SystemAddress& sysAd
scene.Rehearse(); scene.Rehearse();
} }
void Cinema::Scene::CommandCompanion(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
EntityInfo info;
info.lot = 0;
info.pos = entity->GetPosition();
info.rot = entity->GetRotation();
info.scale = 1;
info.spawner = nullptr;
info.spawnerID = entity->GetObjectID();
info.spawnerNodeID = 0;
info.settings = {
new LDFData<std::vector<std::u16string>>(u"syncLDF", { u"custom_script_client" }),
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
};
// If there is an argument, set the lot
const auto lotOptional = GeneralUtils::TryParse<LOT>(splitArgs[0]);
if (lotOptional) {
info.lot = lotOptional.value();
} else {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot.");
return;
}
// Spawn it
auto* actor = Game::entityManager->CreateEntity(info);
// If there is an argument, set the actors name
if (args.size() > 1) {
actor->SetVar(u"npcName", args[1]);
}
// Construct it
Game::entityManager->ConstructEntity(actor);
const auto follow = entity->GetObjectID();
actor->AddCallbackTimer(1.0f, [actor, follow]() {
Cinema::Recording::CompanionRecord::SetCompanion(actor, follow);
});
}

View File

@ -133,6 +133,7 @@ private:
static void CommandPrefabDestroy(Entity* entity, const SystemAddress& sysAddr, const std::string args); static void CommandPrefabDestroy(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandSceneAct(Entity* entity, const SystemAddress& sysAddr, const std::string args); static void CommandSceneAct(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandSceneSetup(Entity* entity, const SystemAddress& sysAddr, const std::string args); static void CommandSceneSetup(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandCompanion(Entity* entity, const SystemAddress& sysAddr, const std::string args);
}; };
} // namespace Cinema } // namespace Cinema

View File

@ -630,7 +630,23 @@ void BaseCombatAIComponent::ClearThreat() {
m_DirtyThreat = true; m_DirtyThreat = true;
} }
void BaseCombatAIComponent::SetStartPosition(const NiPoint3& position) {
m_StartPosition = position;
}
void BaseCombatAIComponent::Wander() { void BaseCombatAIComponent::Wander() {
if (m_FocusPosition != NiPoint3Constant::ZERO) {
m_MovementAI->SetHaltDistance(m_FocusRadius);
m_MovementAI->SetDestination(m_FocusPosition);
m_MovementAI->SetMaxSpeed(m_TetherSpeed);
m_Timer += 0.5f;
return;
}
if (!m_MovementAI->AtFinalWaypoint()) { if (!m_MovementAI->AtFinalWaypoint()) {
return; return;
} }
@ -781,6 +797,38 @@ void BaseCombatAIComponent::SetAggroRadius(const float value) {
m_AggroRadius = value; m_AggroRadius = value;
} }
float BaseCombatAIComponent::GetSoftTetherRadius() const {
return m_SoftTetherRadius;
}
void BaseCombatAIComponent::SetSoftTetherRadius(const float value) {
m_SoftTetherRadius = value;
}
void BaseCombatAIComponent::SetHardTetherRadius(const float value) {
m_HardTetherRadius = value;
}
const NiPoint3& BaseCombatAIComponent::GetFocusPosition() const {
return m_FocusPosition;
}
void BaseCombatAIComponent::SetFocusPosition(const NiPoint3& value) {
m_FocusPosition = value;
}
float BaseCombatAIComponent::GetFocusRadius() const {
return m_FocusRadius;
}
void BaseCombatAIComponent::SetFocusRadius(const float value) {
m_FocusRadius = value;
}
float BaseCombatAIComponent::GetHardTetherRadius() const {
return m_HardTetherRadius;
}
void BaseCombatAIComponent::LookAt(const NiPoint3& point) { void BaseCombatAIComponent::LookAt(const NiPoint3& point) {
if (m_Stunned) { if (m_Stunned) {
return; return;

View File

@ -120,6 +120,12 @@ public:
*/ */
const NiPoint3& GetStartPosition() const; const NiPoint3& GetStartPosition() const;
/**
* Sets the position where the entity spawned
* @param position the position where the entity spawned
*/
void SetStartPosition(const NiPoint3& position);
/** /**
* Removes all threats for this entities, and thus chances for it attacking other entities * Removes all threats for this entities, and thus chances for it attacking other entities
*/ */
@ -196,6 +202,54 @@ public:
*/ */
void SetAggroRadius(float value); void SetAggroRadius(float value);
/**
* Gets the soft tether radius
* @return the soft tether radius
*/
float GetSoftTetherRadius() const;
/**
* Sets the soft tether radius
* @param value the soft tether radius
*/
void SetSoftTetherRadius(float value);
/**
* Gets the hard tether radius
* @return the hard tether radius
*/
float GetHardTetherRadius() const;
/**
* Sets the hard tether radius
* @param value the hard tether radius
*/
void SetHardTetherRadius(float value);
/**
* Get the position that the entity should stay around
* @return the focus position
*/
const NiPoint3& GetFocusPosition() const;
/**
* Set the position that the entity should stay around
* @param position the focus position
*/
void SetFocusPosition(const NiPoint3& position);
/**
* Get the radius that the entity should stay around the focus position
* @return the focus radius
*/
float GetFocusRadius() const;
/**
* Set the radius that the entity should stay around the focus position
* @param radius the focus radius
*/
void SetFocusRadius(float radius);
/** /**
* Makes the entity look at a certain point in space * Makes the entity look at a certain point in space
* @param point the point to look at * @param point the point to look at
@ -382,6 +436,16 @@ private:
*/ */
bool m_DirtyStateOrTarget = false; bool m_DirtyStateOrTarget = false;
/**
* A position that the entity should stay around
*/
NiPoint3 m_FocusPosition;
/**
* How far the entity should stay from the focus position
*/
float m_FocusRadius = 0;
/** /**
* Whether the current entity is a mech enemy, needed as mechs tether radius works differently * Whether the current entity is a mech enemy, needed as mechs tether radius works differently
* @return whether this entity is a mech * @return whether this entity is a mech

View File

@ -182,6 +182,10 @@ const MovementAIInfo& MovementAIComponent::GetInfo() const {
return m_Info; return m_Info;
} }
MovementAIInfo& MovementAIComponent::GetInfo() {
return m_Info;
}
bool MovementAIComponent::AdvanceWaypointIndex() { bool MovementAIComponent::AdvanceWaypointIndex() {
if (m_PathIndex >= m_InterpolatedWaypoints.size()) { if (m_PathIndex >= m_InterpolatedWaypoints.size()) {
return false; return false;

View File

@ -74,6 +74,12 @@ public:
*/ */
const MovementAIInfo& GetInfo() const; const MovementAIInfo& GetInfo() const;
/**
* Returns a mutable reference to the basic settings that this entity uses to move around
* @return a mutable reference to the basic settings that this entity uses to move around
*/
MovementAIInfo& GetInfo();
/** /**
* Set a destination point for the entity to move towards * Set a destination point for the entity to move towards
* @param value the destination point to move towards * @param value the destination point to move towards