mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-26 23:47:21 +00:00
14d4c87a2f
+ Can now specify a cordinate to path find to + Can now enable/disable the combat AI
1196 lines
32 KiB
C++
1196 lines
32 KiB
C++
#include "Recorder.h"
|
|
|
|
#include "ControllablePhysicsComponent.h"
|
|
#include "GameMessages.h"
|
|
#include "InventoryComponent.h"
|
|
#include "../dWorldServer/ObjectIDManager.h"
|
|
#include "ChatPackets.h"
|
|
#include "EntityManager.h"
|
|
#include "EntityInfo.h"
|
|
#include "ServerPreconditions.hpp"
|
|
#include "MovementAIComponent.h"
|
|
#include "BaseCombatAIComponent.h"
|
|
|
|
using namespace Cinema::Recording;
|
|
|
|
std::unordered_map<LWOOBJID, Recorder*> m_Recorders = {};
|
|
|
|
std::unordered_map<int32_t, std::string> m_EffectAnimations = {};
|
|
|
|
Recorder::Recorder() {
|
|
this->m_StartTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
|
|
this->m_IsRecording = false;
|
|
this->m_LastRecordTime = this->m_StartTime;
|
|
}
|
|
|
|
Recorder::~Recorder() {
|
|
}
|
|
|
|
void Recorder::AddRecord(Record* record)
|
|
{
|
|
if (!this->m_IsRecording) {
|
|
return;
|
|
}
|
|
|
|
LOG("Adding record");
|
|
|
|
const auto currentTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
|
|
|
|
// Time since the last record time in seconds
|
|
record->m_Delay = (currentTime - this->m_LastRecordTime).count() / 1000.0f;
|
|
|
|
m_LastRecordTime = currentTime;
|
|
|
|
this->m_Records.push_back(record);
|
|
}
|
|
|
|
void Recorder::Act(Entity* actor, Play* variables) {
|
|
LOG("Acting %d steps", m_Records.size());
|
|
|
|
// Loop through all records
|
|
ActingDispatch(actor, m_Records, 0, variables);
|
|
}
|
|
|
|
void Recorder::ActingDispatch(Entity* actor, const std::vector<Record*>& records, size_t index, Play* variables) {
|
|
if (index >= records.size()) {
|
|
return;
|
|
}
|
|
|
|
auto* record = records[index];
|
|
|
|
// Check if the record is a fork
|
|
auto* forkRecord = dynamic_cast<ForkRecord*>(record);
|
|
|
|
float delay = record->m_Delay;
|
|
|
|
if (forkRecord) {
|
|
if (variables == nullptr) {
|
|
// Skip the fork
|
|
ActingDispatch(actor, records, index + 1, variables);
|
|
return;
|
|
}
|
|
|
|
bool success = false;
|
|
|
|
if (!forkRecord->variable.empty()) {
|
|
const auto& variable = variables->variables.find(forkRecord->variable);
|
|
|
|
if (variable != variables->variables.end()) {
|
|
success = variable->second == forkRecord->value;
|
|
}
|
|
} else if (!forkRecord->precondition.empty()) {
|
|
auto precondtion = Preconditions::CreateExpression(forkRecord->precondition);
|
|
|
|
success = precondtion.Check(actor);
|
|
} else {
|
|
success = true;
|
|
}
|
|
|
|
if (success) {
|
|
// Find the success record
|
|
for (auto i = 0; i < records.size(); ++i) {
|
|
auto* record = records[i];
|
|
|
|
if (record->m_Name == forkRecord->success) {
|
|
ActingDispatch(actor, records, i, variables);
|
|
return;
|
|
}
|
|
}
|
|
|
|
LOG("Failed to find fork label success: %s", forkRecord->success.c_str());
|
|
|
|
return;
|
|
}
|
|
else {
|
|
// Find the failure record
|
|
for (auto i = 0; i < records.size(); ++i) {
|
|
auto* record = records[i];
|
|
|
|
if (record->m_Name == forkRecord->failure) {
|
|
ActingDispatch(actor, records, i, variables);
|
|
return;
|
|
}
|
|
}
|
|
|
|
LOG("Failed to find fork label failure: %s", forkRecord->failure.c_str());
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check if the record is a jump
|
|
auto* jumpRecord = dynamic_cast<JumpRecord*>(record);
|
|
|
|
if (jumpRecord) {
|
|
// Find the jump record
|
|
for (auto i = 0; i < records.size(); ++i) {
|
|
auto* record = records[i];
|
|
|
|
if (record->m_Name == jumpRecord->label) {
|
|
ActingDispatch(actor, records, i, variables);
|
|
return;
|
|
}
|
|
}
|
|
|
|
LOG("Failed to find jump label: %s", jumpRecord->label.c_str());
|
|
|
|
return;
|
|
}
|
|
|
|
// Check if the record is a set variable
|
|
auto* setVariableRecord = dynamic_cast<SetVariableRecord*>(record);
|
|
|
|
if (setVariableRecord) {
|
|
if (variables == nullptr) {
|
|
// Skip the set variable
|
|
ActingDispatch(actor, records, index + 1, variables);
|
|
return;
|
|
}
|
|
|
|
variables->variables[setVariableRecord->variable] = setVariableRecord->value;
|
|
|
|
ActingDispatch(actor, records,index + 1, variables);
|
|
return;
|
|
}
|
|
|
|
// Check if the record is a barrier
|
|
auto* barrierRecord = dynamic_cast<BarrierRecord*>(record);
|
|
|
|
if (barrierRecord) {
|
|
if (variables == nullptr) {
|
|
// Skip the barrier
|
|
ActingDispatch(actor, records, index + 1, variables);
|
|
return;
|
|
}
|
|
|
|
auto actionTaken = std::make_shared<bool>(false);
|
|
|
|
variables->SetupBarrier(barrierRecord->signal, [actor, records, index, variables, actionTaken]() {
|
|
if (*actionTaken) {
|
|
return;
|
|
}
|
|
|
|
*actionTaken = true;
|
|
|
|
ActingDispatch(actor, records, index + 1, variables);
|
|
});
|
|
|
|
if (barrierRecord->timeout <= 0.0001f) {
|
|
return;
|
|
}
|
|
|
|
Game::entityManager->GetZoneControlEntity()->AddCallbackTimer(barrierRecord->timeout, [barrierRecord, records, actor, index, variables, actionTaken]() {
|
|
if (*actionTaken) {
|
|
return;
|
|
}
|
|
|
|
*actionTaken = true;
|
|
|
|
for (auto i = 0; i < records.size(); ++i) {
|
|
auto* record = records[i];
|
|
|
|
if (record->m_Name == barrierRecord->timeoutLabel) {
|
|
ActingDispatch(actor, records, i, variables);
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
// Check if the record is a signal
|
|
auto* signalRecord = dynamic_cast<SignalRecord*>(record);
|
|
|
|
if (signalRecord) {
|
|
if (variables != nullptr) {
|
|
variables->SignalBarrier(signalRecord->signal);
|
|
}
|
|
}
|
|
|
|
// Check if the record is a conclude
|
|
auto* concludeRecord = dynamic_cast<ConcludeRecord*>(record);
|
|
|
|
if (concludeRecord) {
|
|
if (variables != nullptr) {
|
|
variables->Conclude();
|
|
}
|
|
}
|
|
|
|
// Check if the record is a visibility record
|
|
auto* visibilityRecord = dynamic_cast<VisibilityRecord*>(record);
|
|
|
|
if (visibilityRecord) {
|
|
if (!visibilityRecord->visible) {
|
|
ServerPreconditions::AddExcludeFor(actor->GetObjectID(), variables->player);
|
|
} else {
|
|
ServerPreconditions::RemoveExcludeFor(actor->GetObjectID(), variables->player);
|
|
}
|
|
}
|
|
|
|
// Check if the record is a player proximity record
|
|
auto* playerProximityRecord = dynamic_cast<PlayerProximityRecord*>(record);
|
|
|
|
if (playerProximityRecord) {
|
|
if (variables == nullptr) {
|
|
// Skip the player proximity record
|
|
ActingDispatch(actor, records, index + 1, variables);
|
|
return;
|
|
}
|
|
|
|
auto actionTaken = std::make_shared<bool>(false);
|
|
|
|
PlayerProximityDispatch(actor, records, index, variables, actionTaken);
|
|
|
|
if (playerProximityRecord->timeout <= 0.0f) {
|
|
return;
|
|
}
|
|
|
|
Game::entityManager->GetZoneControlEntity()->AddCallbackTimer(playerProximityRecord->timeout, [playerProximityRecord, actor, records, index, variables, actionTaken]() {
|
|
if (*actionTaken) {
|
|
return;
|
|
}
|
|
|
|
*actionTaken = true;
|
|
|
|
for (auto i = 0; i < records.size(); ++i) {
|
|
auto* record = records[i];
|
|
|
|
if (record->m_Name == playerProximityRecord->timeoutLabel) {
|
|
ActingDispatch(actor, records, i, variables);
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
// Check if the record is a coroutine record
|
|
auto* coroutineRecord = dynamic_cast<CoroutineRecord*>(record);
|
|
|
|
if (coroutineRecord) {
|
|
actor->AddCallbackTimer(delay, [actor, coroutineRecord, index, variables]() {
|
|
ActingDispatch(actor, coroutineRecord->records, 0, variables);
|
|
});
|
|
}
|
|
|
|
// Check if the record is a path find record
|
|
auto* pathFindRecord = dynamic_cast<PathFindRecord*>(record);
|
|
|
|
if (pathFindRecord) {
|
|
auto* movementAiComponent = actor->GetComponent<MovementAIComponent>();
|
|
|
|
if (movementAiComponent == nullptr) {
|
|
movementAiComponent = actor->AddComponent<MovementAIComponent>(MovementAIInfo{});
|
|
}
|
|
|
|
movementAiComponent->SetDestination(pathFindRecord->position);
|
|
movementAiComponent->SetMaxSpeed(pathFindRecord->speed);
|
|
|
|
PathFindDispatch(actor, records, index, variables);
|
|
|
|
return;
|
|
}
|
|
|
|
actor->AddCallbackTimer(delay, [actor, records, index, variables]() {
|
|
ActingDispatch(actor, records, index + 1, variables);
|
|
});
|
|
|
|
record->Act(actor);
|
|
}
|
|
|
|
void Cinema::Recording::Recorder::PlayerProximityDispatch(Entity* actor, const std::vector<Record*>& records, size_t index, Play* variables, std::shared_ptr<bool> actionTaken) {
|
|
auto* record = dynamic_cast<PlayerProximityRecord*>(records[index]);
|
|
auto* player = Game::entityManager->GetEntity(variables->player);
|
|
|
|
if (player == nullptr || record == nullptr) {
|
|
return;
|
|
}
|
|
|
|
const auto& playerPosition = player->GetPosition();
|
|
const auto& actorPosition = actor->GetPosition();
|
|
|
|
const auto distance = NiPoint3::Distance(playerPosition, actorPosition);
|
|
|
|
if (distance <= record->distance) {
|
|
if (*actionTaken) {
|
|
return;
|
|
}
|
|
|
|
*actionTaken = true;
|
|
|
|
ActingDispatch(actor, records, index + 1, variables);
|
|
|
|
return;
|
|
}
|
|
|
|
Game::entityManager->GetZoneControlEntity()->AddCallbackTimer(1.0f, [actor, records, index, variables, actionTaken]() {
|
|
PlayerProximityDispatch(actor, records, index, variables, actionTaken);
|
|
});
|
|
}
|
|
|
|
void Cinema::Recording::Recorder::PathFindDispatch(Entity* actor, const std::vector<Record*>& records, size_t index, Play* variables) {
|
|
auto* record = dynamic_cast<PathFindRecord*>(records[index]);
|
|
|
|
if (record == nullptr) {
|
|
return;
|
|
}
|
|
|
|
auto* movementAiComponent = actor->GetComponent<MovementAIComponent>();
|
|
|
|
if (movementAiComponent == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (movementAiComponent->AtFinalWaypoint()) {
|
|
ActingDispatch(actor, records, index + 1, variables);
|
|
return;
|
|
}
|
|
|
|
Game::entityManager->GetZoneControlEntity()->AddCallbackTimer(1.0f, [actor, records, index, variables]() {
|
|
PathFindDispatch(actor, records, index, variables);
|
|
});
|
|
}
|
|
|
|
void Cinema::Recording::Recorder::LoadRecords(tinyxml2::XMLElement* root, std::vector<Record*>& records) {
|
|
|
|
for (auto* element = root->FirstChildElement(); element; element = element->NextSiblingElement()) {
|
|
std::string name = element->Name();
|
|
|
|
if (!element->Attribute("t")) {
|
|
element->SetAttribute("t", 0.0f);
|
|
}
|
|
|
|
// If the name does not end with Record, add it
|
|
if (name.find("Record") == std::string::npos) {
|
|
name += "Record";
|
|
}
|
|
|
|
Record* record = nullptr;
|
|
|
|
if (name == "MovementRecord") {
|
|
record = new MovementRecord();
|
|
} else if (name == "SpeakRecord") {
|
|
record = new SpeakRecord();
|
|
} else if (name == "AnimationRecord") {
|
|
record = new AnimationRecord();
|
|
} else if (name == "EquipRecord") {
|
|
record = new EquipRecord();
|
|
} else if (name == "UnequipRecord") {
|
|
record = new UnequipRecord();
|
|
} else if (name == "ClearEquippedRecord") {
|
|
record = new ClearEquippedRecord();
|
|
} else if (name == "ForkRecord") {
|
|
record = new ForkRecord();
|
|
} else if (name == "WaitRecord") {
|
|
record = new WaitRecord();
|
|
} else if (name == "JumpRecord") {
|
|
record = new JumpRecord();
|
|
} else if (name == "SetVariableRecord") {
|
|
record = new SetVariableRecord();
|
|
} else if (name == "BarrierRecord") {
|
|
record = new BarrierRecord();
|
|
} else if (name == "SignalRecord") {
|
|
record = new SignalRecord();
|
|
} else if (name == "ConcludeRecord") {
|
|
record = new ConcludeRecord();
|
|
} else if (name == "PlayerProximityRecord") {
|
|
record = new PlayerProximityRecord();
|
|
} else if (name == "VisibilityRecord") {
|
|
record = new VisibilityRecord();
|
|
} else if (name == "PlayEffectRecord") {
|
|
record = new PlayEffectRecord();
|
|
} else if (name == "CoroutineRecord") {
|
|
record = new CoroutineRecord();
|
|
} else if (name == "PathFindRecord") {
|
|
record = new PathFindRecord();
|
|
} else if (name == "CombatAIRecord") {
|
|
record = new CombatAIRecord();
|
|
} else {
|
|
LOG("Unknown record type: %s", name.c_str());
|
|
continue;
|
|
}
|
|
|
|
record->Deserialize(element);
|
|
records.push_back(record);
|
|
|
|
if (element->Attribute("name")) {
|
|
records.back()->m_Name = element->Attribute("name");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
Entity* Recorder::ActFor(Entity* actorTemplate, Entity* player, Play* variables) {
|
|
EntityInfo info;
|
|
info.lot = actorTemplate->GetLOT();
|
|
info.pos = actorTemplate->GetPosition();
|
|
info.rot = actorTemplate->GetRotation();
|
|
info.scale = 1;
|
|
info.spawner = nullptr;
|
|
info.spawnerID = player->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")
|
|
};
|
|
|
|
// Spawn it
|
|
auto* actor = Game::entityManager->CreateEntity(info);
|
|
|
|
// Hide the template from the player
|
|
ServerPreconditions::AddExcludeFor(player->GetObjectID(), actorTemplate->GetObjectID());
|
|
|
|
// Solo act for the player
|
|
ServerPreconditions::AddSoloActor(actor->GetObjectID(), player->GetObjectID());
|
|
|
|
Game::entityManager->ConstructEntity(actor);
|
|
|
|
Act(actor, variables);
|
|
|
|
return actor;
|
|
}
|
|
|
|
void Recorder::StopActingFor(Entity* actor, Entity* actorTemplate, LWOOBJID playerID) {
|
|
// Remove the exclude for the player
|
|
ServerPreconditions::RemoveExcludeFor(playerID, actorTemplate->GetObjectID());
|
|
|
|
Game::entityManager->DestroyEntity(actor);
|
|
}
|
|
|
|
bool Recorder::IsRecording() const {
|
|
return this->m_IsRecording;
|
|
}
|
|
|
|
void Recorder::StartRecording(LWOOBJID actorID) {
|
|
const auto& it = m_Recorders.find(actorID);
|
|
|
|
// Delete the old recorder if it exists
|
|
if (it != m_Recorders.end()) {
|
|
delete it->second;
|
|
m_Recorders.erase(it);
|
|
}
|
|
|
|
Recorder* recorder = new Recorder();
|
|
m_Recorders.insert_or_assign(actorID, recorder);
|
|
recorder->m_IsRecording = true;
|
|
}
|
|
|
|
void Recorder::StopRecording(LWOOBJID actorID) {
|
|
auto iter = m_Recorders.find(actorID);
|
|
if (iter == m_Recorders.end()) {
|
|
return;
|
|
}
|
|
|
|
iter->second->m_IsRecording = false;
|
|
}
|
|
|
|
Recorder* Recorder::GetRecorder(LWOOBJID actorID) {
|
|
auto iter = m_Recorders.find(actorID);
|
|
if (iter == m_Recorders.end()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return iter->second;
|
|
}
|
|
|
|
void Cinema::Recording::Recorder::RegisterEffectForActor(LWOOBJID actorID, const int32_t& effectId) {
|
|
auto iter = m_Recorders.find(actorID);
|
|
if (iter == m_Recorders.end()) {
|
|
return;
|
|
}
|
|
|
|
auto& recorder = iter->second;
|
|
|
|
const auto& effectIter = m_EffectAnimations.find(effectId);
|
|
|
|
if (effectIter == m_EffectAnimations.end()) {
|
|
auto statement = CDClientDatabase::CreatePreppedStmt("SELECT animationName FROM BehaviorEffect WHERE effectID = ? LIMIT 1;");
|
|
|
|
statement.bind(1, effectId);
|
|
|
|
auto result = statement.execQuery();
|
|
|
|
if (result.eof()) {
|
|
result.finalize();
|
|
|
|
m_EffectAnimations.emplace(effectId, "");
|
|
}
|
|
else {
|
|
const auto animationName = result.getStringField(0);
|
|
|
|
m_EffectAnimations.emplace(effectId, animationName);
|
|
|
|
result.finalize();
|
|
|
|
recorder->AddRecord(new AnimationRecord(animationName));
|
|
}
|
|
}
|
|
else {
|
|
recorder->AddRecord(new AnimationRecord(effectIter->second));
|
|
}
|
|
|
|
recorder->AddRecord(new PlayEffectRecord(std::to_string(effectId)));
|
|
}
|
|
|
|
MovementRecord::MovementRecord(const NiPoint3& position, const NiQuaternion& rotation, const NiPoint3& velocity, const NiPoint3& angularVelocity, bool onGround, bool dirtyVelocity, bool dirtyAngularVelocity) {
|
|
this->position = position;
|
|
this->rotation = rotation;
|
|
this->velocity = velocity;
|
|
this->angularVelocity = angularVelocity;
|
|
this->onGround = onGround;
|
|
this->dirtyVelocity = dirtyVelocity;
|
|
this->dirtyAngularVelocity = dirtyAngularVelocity;
|
|
}
|
|
|
|
void MovementRecord::Act(Entity* actor) {
|
|
auto* controllableComponent = actor->GetComponent<ControllablePhysicsComponent>();
|
|
|
|
if (controllableComponent) {
|
|
controllableComponent->SetPosition(position);
|
|
controllableComponent->SetRotation(rotation);
|
|
controllableComponent->SetVelocity(velocity);
|
|
controllableComponent->SetAngularVelocity(angularVelocity);
|
|
controllableComponent->SetIsOnGround(onGround);
|
|
controllableComponent->SetDirtyVelocity(dirtyVelocity);
|
|
controllableComponent->SetDirtyAngularVelocity(dirtyAngularVelocity);
|
|
}
|
|
|
|
Game::entityManager->SerializeEntity(actor);
|
|
}
|
|
|
|
void MovementRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("MovementRecord");
|
|
|
|
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("vx", velocity.x);
|
|
element->SetAttribute("vy", velocity.y);
|
|
element->SetAttribute("vz", velocity.z);
|
|
|
|
element->SetAttribute("avx", angularVelocity.x);
|
|
element->SetAttribute("avy", angularVelocity.y);
|
|
element->SetAttribute("avz", angularVelocity.z);
|
|
|
|
element->SetAttribute("g", onGround);
|
|
|
|
element->SetAttribute("dv", dirtyVelocity);
|
|
|
|
element->SetAttribute("dav", dirtyAngularVelocity);
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void MovementRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
position.x = element->FloatAttribute("x");
|
|
position.y = element->FloatAttribute("y");
|
|
position.z = element->FloatAttribute("z");
|
|
|
|
rotation.x = element->FloatAttribute("qx");
|
|
rotation.y = element->FloatAttribute("qy");
|
|
rotation.z = element->FloatAttribute("qz");
|
|
rotation.w = element->FloatAttribute("qw");
|
|
|
|
velocity.x = element->FloatAttribute("vx");
|
|
velocity.y = element->FloatAttribute("vy");
|
|
velocity.z = element->FloatAttribute("vz");
|
|
|
|
angularVelocity.x = element->FloatAttribute("avx");
|
|
angularVelocity.y = element->FloatAttribute("avy");
|
|
angularVelocity.z = element->FloatAttribute("avz");
|
|
|
|
onGround = element->BoolAttribute("g");
|
|
|
|
dirtyVelocity = element->BoolAttribute("dv");
|
|
|
|
dirtyAngularVelocity = element->BoolAttribute("dav");
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
SpeakRecord::SpeakRecord(const std::string& text) {
|
|
this->text = text;
|
|
}
|
|
|
|
void SpeakRecord::Act(Entity* actor) {
|
|
GameMessages::SendNotifyClientZoneObject(
|
|
actor->GetObjectID(), u"sendToclient_bubble", 0, 0, actor->GetObjectID(), text, UNASSIGNED_SYSTEM_ADDRESS);
|
|
|
|
Game::entityManager->SerializeEntity(actor);
|
|
}
|
|
|
|
void SpeakRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("SpeakRecord");
|
|
|
|
element->SetAttribute("text", text.c_str());
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void SpeakRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
text = element->Attribute("text");
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
AnimationRecord::AnimationRecord(const std::string& animation) {
|
|
this->animation = animation;
|
|
}
|
|
|
|
void AnimationRecord::Act(Entity* actor) {
|
|
GameMessages::SendPlayAnimation(actor, GeneralUtils::ASCIIToUTF16(animation));
|
|
|
|
Game::entityManager->SerializeEntity(actor);
|
|
}
|
|
|
|
void AnimationRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("AnimationRecord");
|
|
|
|
element->SetAttribute("animation", animation.c_str());
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void AnimationRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
animation = element->Attribute("animation");
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
EquipRecord::EquipRecord(LOT item) {
|
|
this->item = item;
|
|
}
|
|
|
|
void EquipRecord::Act(Entity* actor) {
|
|
auto* inventoryComponent = actor->GetComponent<InventoryComponent>();
|
|
|
|
const LWOOBJID id = ObjectIDManager::Instance()->GenerateObjectID();
|
|
|
|
const auto& info = Inventory::FindItemComponent(item);
|
|
|
|
if (inventoryComponent) {
|
|
inventoryComponent->UpdateSlot(info.equipLocation, { id, item, 1, 0 });
|
|
}
|
|
|
|
Game::entityManager->SerializeEntity(actor);
|
|
}
|
|
|
|
void EquipRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("EquipRecord");
|
|
|
|
element->SetAttribute("item", item);
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void EquipRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
item = element->IntAttribute("item");
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
UnequipRecord::UnequipRecord(LOT item) {
|
|
this->item = item;
|
|
}
|
|
|
|
void UnequipRecord::Act(Entity* actor) {
|
|
auto* inventoryComponent = actor->GetComponent<InventoryComponent>();
|
|
|
|
const auto& info = Inventory::FindItemComponent(item);
|
|
|
|
if (inventoryComponent) {
|
|
inventoryComponent->RemoveSlot(info.equipLocation);
|
|
}
|
|
|
|
Game::entityManager->SerializeEntity(actor);
|
|
}
|
|
|
|
void UnequipRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("UnequipRecord");
|
|
|
|
element->SetAttribute("item", item);
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void UnequipRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
item = element->IntAttribute("item");
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
void ClearEquippedRecord::Act(Entity* actor) {
|
|
auto* inventoryComponent = actor->GetComponent<InventoryComponent>();
|
|
|
|
if (inventoryComponent) {
|
|
auto equipped = inventoryComponent->GetEquippedItems();
|
|
|
|
for (auto entry : equipped) {
|
|
inventoryComponent->RemoveSlot(entry.first);
|
|
}
|
|
}
|
|
|
|
Game::entityManager->SerializeEntity(actor);
|
|
}
|
|
|
|
void ClearEquippedRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("ClearEquippedRecord");
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void ClearEquippedRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
void Recorder::SaveToFile(const std::string& filename) {
|
|
tinyxml2::XMLDocument document;
|
|
|
|
auto* root = document.NewElement("Recorder");
|
|
|
|
for (auto* record : m_Records) {
|
|
record->Serialize(document, root);
|
|
}
|
|
|
|
document.InsertFirstChild(root);
|
|
|
|
document.SaveFile(filename.c_str());
|
|
}
|
|
|
|
float Recorder::GetDuration() const {
|
|
// Return the sum of all the record delays
|
|
float duration = 0.0f;
|
|
|
|
for (auto* record : m_Records) {
|
|
duration += record->m_Delay;
|
|
}
|
|
|
|
return duration;
|
|
}
|
|
|
|
Recorder* Recorder::LoadFromFile(const std::string& filename) {
|
|
tinyxml2::XMLDocument document;
|
|
|
|
if (document.LoadFile(filename.c_str()) != tinyxml2::XML_SUCCESS) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto* root = document.FirstChildElement("Recorder");
|
|
|
|
if (!root) {
|
|
return nullptr;
|
|
}
|
|
|
|
Recorder* recorder = new Recorder();
|
|
|
|
LoadRecords(root, recorder->m_Records);
|
|
|
|
return recorder;
|
|
}
|
|
|
|
void Recorder::AddRecording(LWOOBJID actorID, Recorder* recorder) {
|
|
const auto& it = m_Recorders.find(actorID);
|
|
|
|
// Delete the old recorder if it exists
|
|
if (it != m_Recorders.end()) {
|
|
delete it->second;
|
|
m_Recorders.erase(it);
|
|
}
|
|
|
|
m_Recorders.insert_or_assign(actorID, recorder);
|
|
}
|
|
|
|
Cinema::Recording::ForkRecord::ForkRecord(const std::string& variable, const std::string& value, const std::string& success, const std::string& failure) {
|
|
this->variable = variable;
|
|
this->value = value;
|
|
this->success = success;
|
|
this->failure = failure;
|
|
}
|
|
|
|
void Cinema::Recording::ForkRecord::Act(Entity* actor) {
|
|
}
|
|
|
|
void Cinema::Recording::ForkRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("ForkRecord");
|
|
|
|
if (!variable.empty()) {
|
|
element->SetAttribute("variable", variable.c_str());
|
|
element->SetAttribute("value", value.c_str());
|
|
}
|
|
|
|
element->SetAttribute("success", success.c_str());
|
|
element->SetAttribute("failure", failure.c_str());
|
|
|
|
if (!precondition.empty()) {
|
|
element->SetAttribute("precondtion", precondition.c_str());
|
|
}
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void Cinema::Recording::ForkRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
if (element->Attribute("variable")) {
|
|
variable = element->Attribute("variable");
|
|
value = element->Attribute("value");
|
|
}
|
|
|
|
success = element->Attribute("success");
|
|
failure = element->Attribute("failure");
|
|
|
|
if (element->Attribute("precondition")) {
|
|
precondition = element->Attribute("precondition");
|
|
}
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
Cinema::Recording::WaitRecord::WaitRecord(float delay) {
|
|
this->m_Delay = delay;
|
|
}
|
|
|
|
void Cinema::Recording::WaitRecord::Act(Entity* actor) {
|
|
}
|
|
|
|
void Cinema::Recording::WaitRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("WaitRecord");
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void Cinema::Recording::WaitRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
Cinema::Recording::JumpRecord::JumpRecord(const std::string& label) {
|
|
this->label = label;
|
|
}
|
|
|
|
void Cinema::Recording::JumpRecord::Act(Entity* actor) {
|
|
}
|
|
|
|
void Cinema::Recording::JumpRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("JumpRecord");
|
|
|
|
element->SetAttribute("label", label.c_str());
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void Cinema::Recording::JumpRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
label = element->Attribute("label");
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
Cinema::Recording::SetVariableRecord::SetVariableRecord(const std::string& variable, const std::string& value) {
|
|
this->variable = variable;
|
|
this->value = value;
|
|
}
|
|
|
|
void Cinema::Recording::SetVariableRecord::Act(Entity* actor) {
|
|
}
|
|
|
|
void Cinema::Recording::SetVariableRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("SetVariableRecord");
|
|
|
|
element->SetAttribute("variable", variable.c_str());
|
|
element->SetAttribute("value", value.c_str());
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void Cinema::Recording::SetVariableRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
variable = element->Attribute("variable");
|
|
value = element->Attribute("value");
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
Cinema::Recording::BarrierRecord::BarrierRecord(const std::string& signal, float timeout, const std::string& timeoutLabel) {
|
|
this->signal = signal;
|
|
this->timeout = timeout;
|
|
this->timeoutLabel = timeoutLabel;
|
|
}
|
|
|
|
void Cinema::Recording::BarrierRecord::Act(Entity* actor) {
|
|
}
|
|
|
|
void Cinema::Recording::BarrierRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("BarrierRecord");
|
|
|
|
element->SetAttribute("signal", signal.c_str());
|
|
if (timeout > 0.0f) {
|
|
element->SetAttribute("timeout", timeout);
|
|
}
|
|
|
|
if (!timeoutLabel.empty()) {
|
|
element->SetAttribute("timeoutLabel", timeoutLabel.c_str());
|
|
}
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void Cinema::Recording::BarrierRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
signal = element->Attribute("signal");
|
|
|
|
if (element->Attribute("timeout")) {
|
|
timeout = element->FloatAttribute("timeout");
|
|
}
|
|
|
|
if (element->Attribute("timeoutLabel")) {
|
|
timeoutLabel = element->Attribute("timeoutLabel");
|
|
}
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
Cinema::Recording::SignalRecord::SignalRecord(const std::string& signal) {
|
|
this->signal = signal;
|
|
}
|
|
|
|
void Cinema::Recording::SignalRecord::Act(Entity* actor) {
|
|
}
|
|
|
|
void Cinema::Recording::SignalRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("SignalRecord");
|
|
|
|
element->SetAttribute("signal", signal.c_str());
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void Cinema::Recording::SignalRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
signal = element->Attribute("signal");
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
Cinema::Recording::PlayerProximityRecord::PlayerProximityRecord(float distance, float timeout, const std::string& timeoutLabel) {
|
|
this->distance = distance;
|
|
this->timeout = timeout;
|
|
this->timeoutLabel = timeoutLabel;
|
|
}
|
|
|
|
void Cinema::Recording::PlayerProximityRecord::Act(Entity* actor) {
|
|
}
|
|
|
|
void Cinema::Recording::PlayerProximityRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("PlayerProximityRecord");
|
|
|
|
element->SetAttribute("distance", distance);
|
|
if (timeout > 0.0f) {
|
|
element->SetAttribute("timeout", timeout);
|
|
}
|
|
|
|
if (!timeoutLabel.empty()) {
|
|
element->SetAttribute("timeoutLabel", timeoutLabel.c_str());
|
|
}
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void Cinema::Recording::PlayerProximityRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
distance = element->FloatAttribute("distance");
|
|
|
|
if (element->Attribute("timeout")) {
|
|
timeout = element->FloatAttribute("timeout");
|
|
}
|
|
|
|
if (element->Attribute("timeoutLabel")) {
|
|
timeoutLabel = element->Attribute("timeoutLabel");
|
|
}
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
void Cinema::Recording::ConcludeRecord::Act(Entity* actor) {
|
|
}
|
|
|
|
void Cinema::Recording::ConcludeRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("ConcludeRecord");
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void Cinema::Recording::ConcludeRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
Cinema::Recording::VisibilityRecord::VisibilityRecord(bool visible) {
|
|
this->visible = visible;
|
|
}
|
|
|
|
void Cinema::Recording::VisibilityRecord::Act(Entity* actor) {
|
|
}
|
|
|
|
void Cinema::Recording::VisibilityRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("VisibilityRecord");
|
|
|
|
element->SetAttribute("visible", visible);
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void Cinema::Recording::VisibilityRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
visible = element->BoolAttribute("visible");
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
Cinema::Recording::PlayEffectRecord::PlayEffectRecord(const std::string& effect) {
|
|
this->effect = effect;
|
|
}
|
|
|
|
void Cinema::Recording::PlayEffectRecord::Act(Entity* actor) {
|
|
int32_t effectID = 0;
|
|
|
|
if (!GeneralUtils::TryParse(effect, effectID)) {
|
|
return;
|
|
}
|
|
|
|
GameMessages::SendPlayFXEffect(
|
|
actor->GetObjectID(),
|
|
effectID,
|
|
u"cast",
|
|
std::to_string(ObjectIDManager::GenerateRandomObjectID())
|
|
);
|
|
|
|
Game::entityManager->SerializeEntity(actor);
|
|
}
|
|
|
|
void Cinema::Recording::PlayEffectRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("PlayEffectRecord");
|
|
|
|
element->SetAttribute("effect", effect.c_str());
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void Cinema::Recording::PlayEffectRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
effect = element->Attribute("effect");
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
void Cinema::Recording::CoroutineRecord::Act(Entity* actor) {
|
|
|
|
}
|
|
|
|
void Cinema::Recording::CoroutineRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("CoroutineRecord");
|
|
|
|
for (auto* record : records) {
|
|
record->Serialize(document, element);
|
|
}
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void Cinema::Recording::CoroutineRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
Recorder::LoadRecords(element, records);
|
|
}
|
|
|
|
Cinema::Recording::PathFindRecord::PathFindRecord(const NiPoint3& position, float speed) {
|
|
this->position = position;
|
|
this->speed = speed;
|
|
}
|
|
|
|
void Cinema::Recording::PathFindRecord::Act(Entity* actor) {
|
|
}
|
|
|
|
void Cinema::Recording::PathFindRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("PathFindRecord");
|
|
|
|
element->SetAttribute("x", position.x);
|
|
element->SetAttribute("y", position.y);
|
|
element->SetAttribute("z", position.z);
|
|
|
|
element->SetAttribute("speed", speed);
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void Cinema::Recording::PathFindRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
position.x = element->FloatAttribute("x");
|
|
position.y = element->FloatAttribute("y");
|
|
position.z = element->FloatAttribute("z");
|
|
|
|
speed = element->FloatAttribute("speed");
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|
|
|
|
Cinema::Recording::CombatAIRecord::CombatAIRecord(bool enabled) {
|
|
this->enabled = enabled;
|
|
}
|
|
|
|
void Cinema::Recording::CombatAIRecord::Act(Entity* actor) {
|
|
auto* baseCombatAIComponent = actor->GetComponent<BaseCombatAIComponent>();
|
|
|
|
if (baseCombatAIComponent == nullptr) {
|
|
return;
|
|
}
|
|
|
|
baseCombatAIComponent->SetDisabled(!enabled);
|
|
}
|
|
|
|
void Cinema::Recording::CombatAIRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
|
auto* element = document.NewElement("CombatAIRecord");
|
|
|
|
element->SetAttribute("enabled", enabled);
|
|
|
|
element->SetAttribute("t", m_Delay);
|
|
|
|
parent->InsertEndChild(element);
|
|
}
|
|
|
|
void Cinema::Recording::CombatAIRecord::Deserialize(tinyxml2::XMLElement* element) {
|
|
enabled = element->BoolAttribute("enabled");
|
|
|
|
m_Delay = element->DoubleAttribute("t");
|
|
}
|