mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-12-23 05:53:34 +00:00
Initial changes.
* Recorder to recall player actions. * Server precondtions to manage entity visiblity.
This commit is contained in:
parent
50921cce2d
commit
9e56725cff
@ -7,9 +7,9 @@ LICENSE=AGPL-3.0
|
||||
# Set __dynamic to 1 to enable the -rdynamic flag for the linker, yielding some symbols in crashlogs.
|
||||
__dynamic=1
|
||||
# Set __ggdb to 1 to enable the -ggdb flag for the linker, including more debug info.
|
||||
# __ggdb=1
|
||||
__ggdb=1
|
||||
# Set __include_backtrace__ to 1 to includes the backtrace library for better crashlogs.
|
||||
# __include_backtrace__=1
|
||||
__include_backtrace__=1
|
||||
# Set __compile_backtrace__ to 1 to compile the backtrace library instead of using system libraries.
|
||||
# __compile_backtrace__=1
|
||||
# Set to the number of jobs (make -j equivalent) to compile the mariadbconn files with.
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "eReplicaPacketType.h"
|
||||
#include "ServerPreconditions.hpp"
|
||||
|
||||
// Configure which zones have ghosting disabled, mostly small worlds.
|
||||
std::vector<LWOMAPID> EntityManager::m_GhostingExcludedZones = {
|
||||
@ -515,13 +516,15 @@ void EntityManager::UpdateGhosting(Player* player) {
|
||||
ghostingDistanceMax = ghostingDistanceMin;
|
||||
}
|
||||
|
||||
if (observed && distance > ghostingDistanceMax && !isOverride) {
|
||||
auto condition = ServerPreconditions::CheckPreconditions(entity, player);
|
||||
|
||||
if (observed && ((distance > ghostingDistanceMax && !isOverride) || !condition)) {
|
||||
player->GhostEntity(id);
|
||||
|
||||
DestructEntity(entity, player->GetSystemAddress());
|
||||
|
||||
entity->SetObservers(entity->GetObservers() - 1);
|
||||
} else if (!observed && ghostingDistanceMin > distance) {
|
||||
} else if (!observed && ghostingDistanceMin > distance && condition) {
|
||||
// Check collectables, don't construct if it has been collected
|
||||
uint32_t collectionId = entity->GetCollectibleID();
|
||||
|
||||
@ -563,13 +566,15 @@ void EntityManager::CheckGhosting(Entity* entity) {
|
||||
|
||||
const auto distance = NiPoint3::DistanceSquared(referencePoint, entityPoint);
|
||||
|
||||
if (observed && distance > ghostingDistanceMax) {
|
||||
const auto precondition = ServerPreconditions::CheckPreconditions(entity, player);
|
||||
|
||||
if (observed && (distance > ghostingDistanceMax || !precondition)) {
|
||||
player->GhostEntity(id);
|
||||
|
||||
DestructEntity(entity, player->GetSystemAddress());
|
||||
|
||||
entity->SetObservers(entity->GetObservers() - 1);
|
||||
} else if (!observed && ghostingDistanceMin > distance) {
|
||||
} else if (!observed && (ghostingDistanceMin > distance && precondition)) {
|
||||
player->ObserveEntity(id);
|
||||
|
||||
ConstructEntity(entity, player->GetSystemAddress());
|
||||
|
@ -32,9 +32,6 @@ Player::Player(const LWOOBJID& objectID, const EntityInfo info, User* user, Enti
|
||||
m_GhostReferencePoint = NiPoint3::ZERO;
|
||||
m_GhostOverridePoint = NiPoint3::ZERO;
|
||||
m_GhostOverride = false;
|
||||
m_ObservedEntitiesLength = 256;
|
||||
m_ObservedEntitiesUsed = 0;
|
||||
m_ObservedEntities.resize(m_ObservedEntitiesLength);
|
||||
|
||||
m_Character->SetEntity(this);
|
||||
|
||||
@ -180,41 +177,21 @@ bool Player::GetGhostOverride() const {
|
||||
}
|
||||
|
||||
void Player::ObserveEntity(int32_t id) {
|
||||
for (int32_t i = 0; i < m_ObservedEntitiesUsed; i++) {
|
||||
if (m_ObservedEntities[i] == 0 || m_ObservedEntities[i] == id) {
|
||||
m_ObservedEntities[i] = id;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto index = m_ObservedEntitiesUsed++;
|
||||
|
||||
if (m_ObservedEntitiesUsed > m_ObservedEntitiesLength) {
|
||||
m_ObservedEntities.resize(m_ObservedEntitiesLength + m_ObservedEntitiesLength);
|
||||
|
||||
m_ObservedEntitiesLength = m_ObservedEntitiesLength + m_ObservedEntitiesLength;
|
||||
}
|
||||
|
||||
m_ObservedEntities[index] = id;
|
||||
m_ObservedEntities.emplace(id);
|
||||
}
|
||||
|
||||
bool Player::IsObserved(int32_t id) {
|
||||
for (int32_t i = 0; i < m_ObservedEntitiesUsed; i++) {
|
||||
if (m_ObservedEntities[i] == id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return m_ObservedEntities.find(id) != m_ObservedEntities.end();
|
||||
}
|
||||
|
||||
void Player::GhostEntity(int32_t id) {
|
||||
for (int32_t i = 0; i < m_ObservedEntitiesUsed; i++) {
|
||||
if (m_ObservedEntities[i] == id) {
|
||||
m_ObservedEntities[i] = 0;
|
||||
}
|
||||
const auto& iter = m_ObservedEntities.find(id);
|
||||
|
||||
if (iter == m_ObservedEntities.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_ObservedEntities.erase(iter);
|
||||
}
|
||||
|
||||
Player* Player::GetPlayer(const SystemAddress& sysAddr) {
|
||||
@ -262,9 +239,8 @@ void Player::SetDroppedCoins(uint64_t value) {
|
||||
Player::~Player() {
|
||||
Game::logger->Log("Player", "Deleted player");
|
||||
|
||||
for (int32_t i = 0; i < m_ObservedEntitiesUsed; i++) {
|
||||
const auto id = m_ObservedEntities[i];
|
||||
|
||||
for (const auto& id : m_ObservedEntities)
|
||||
{
|
||||
if (id == 0) {
|
||||
continue;
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include "Entity.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
/**
|
||||
* Extended Entity for player data and behavior.
|
||||
*
|
||||
@ -120,11 +122,7 @@ private:
|
||||
|
||||
bool m_GhostOverride;
|
||||
|
||||
std::vector<int32_t> m_ObservedEntities;
|
||||
|
||||
int32_t m_ObservedEntitiesLength;
|
||||
|
||||
int32_t m_ObservedEntitiesUsed;
|
||||
std::unordered_set<int32_t> m_ObservedEntities;
|
||||
|
||||
std::vector<LWOOBJID> m_LimboConstructions;
|
||||
|
||||
|
@ -99,6 +99,8 @@
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
#include "CDObjectsTable.h"
|
||||
|
||||
#include "Recorder.h"
|
||||
|
||||
void GameMessages::SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
@ -189,6 +191,12 @@ void GameMessages::SendPlayAnimation(Entity* entity, const std::u16string& anima
|
||||
if (fScale != 1.0f) bitStream.Write(fScale);
|
||||
|
||||
SEND_PACKET_BROADCAST;
|
||||
|
||||
auto* recorder = Recording::Recorder::GetRecorder(entity->GetObjectID());
|
||||
|
||||
if (recorder != nullptr) {
|
||||
recorder->AddRecord(new Recording::AnimationRecord(GeneralUtils::UTF16ToWTF8(animationName)));
|
||||
}
|
||||
}
|
||||
|
||||
void GameMessages::SendPlayerReady(Entity* entity, const SystemAddress& sysAddr) {
|
||||
@ -5166,6 +5174,17 @@ void GameMessages::HandleRespondToMission(RakNet::BitStream* inStream, Entity* e
|
||||
inStream->Read(isDefaultReward);
|
||||
if (isDefaultReward) inStream->Read(reward);
|
||||
|
||||
Entity* offerer = Game::entityManager->GetEntity(receiverID);
|
||||
|
||||
if (offerer == nullptr) {
|
||||
Game::logger->Log("GameMessages", "Unable to get receiver entity %llu for RespondToMission", receiverID);
|
||||
return;
|
||||
}
|
||||
|
||||
for (CppScripts::Script* script : CppScripts::GetEntityScripts(offerer)) {
|
||||
script->OnRespondToMission(offerer, missionID, Game::entityManager->GetEntity(playerID), reward);
|
||||
}
|
||||
|
||||
MissionComponent* missionComponent = static_cast<MissionComponent*>(entity->GetComponent(eReplicaComponentType::MISSION));
|
||||
if (!missionComponent) {
|
||||
Game::logger->Log("GameMessages", "Unable to get mission component for entity %llu to handle RespondToMission", playerID);
|
||||
@ -5178,17 +5197,6 @@ void GameMessages::HandleRespondToMission(RakNet::BitStream* inStream, Entity* e
|
||||
} else {
|
||||
Game::logger->Log("GameMessages", "Unable to get mission %i for entity %llu to update reward in RespondToMission", missionID, playerID);
|
||||
}
|
||||
|
||||
Entity* offerer = Game::entityManager->GetEntity(receiverID);
|
||||
|
||||
if (offerer == nullptr) {
|
||||
Game::logger->Log("GameMessages", "Unable to get receiver entity %llu for RespondToMission", receiverID);
|
||||
return;
|
||||
}
|
||||
|
||||
for (CppScripts::Script* script : CppScripts::GetEntityScripts(offerer)) {
|
||||
script->OnRespondToMission(offerer, missionID, Game::entityManager->GetEntity(playerID), reward);
|
||||
}
|
||||
}
|
||||
|
||||
void GameMessages::HandleMissionDialogOK(RakNet::BitStream* inStream, Entity* entity) {
|
||||
@ -5359,6 +5367,12 @@ void GameMessages::HandleEquipItem(RakNet::BitStream* inStream, Entity* entity)
|
||||
item->Equip();
|
||||
|
||||
Game::entityManager->SerializeEntity(entity);
|
||||
|
||||
auto* recorder = Recording::Recorder::GetRecorder(entity->GetObjectID());
|
||||
|
||||
if (recorder != nullptr) {
|
||||
recorder->AddRecord(new Recording::EquipRecord(item->GetLot()));
|
||||
}
|
||||
}
|
||||
|
||||
void GameMessages::HandleUnequipItem(RakNet::BitStream* inStream, Entity* entity) {
|
||||
@ -5379,6 +5393,12 @@ void GameMessages::HandleUnequipItem(RakNet::BitStream* inStream, Entity* entity
|
||||
item->UnEquip();
|
||||
|
||||
Game::entityManager->SerializeEntity(entity);
|
||||
|
||||
auto* recorder = Recording::Recorder::GetRecorder(entity->GetObjectID());
|
||||
|
||||
if (recorder != nullptr) {
|
||||
recorder->AddRecord(new Recording::UnequipRecord(item->GetLot()));
|
||||
}
|
||||
}
|
||||
|
||||
void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
|
@ -5,4 +5,6 @@ set(DGAME_DUTILITIES_SOURCES "BrickDatabase.cpp"
|
||||
"Mail.cpp"
|
||||
"Preconditions.cpp"
|
||||
"SlashCommandHandler.cpp"
|
||||
"Recorder.cpp"
|
||||
"ServerPreconditions.cpp"
|
||||
"VanityUtilities.cpp" PARENT_SCOPE)
|
||||
|
474
dGame/dUtilities/Recorder.cpp
Normal file
474
dGame/dUtilities/Recorder.cpp
Normal file
@ -0,0 +1,474 @@
|
||||
#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"
|
||||
|
||||
using namespace Recording;
|
||||
|
||||
std::unordered_map<LWOOBJID, Recorder*> m_Recorders = {};
|
||||
|
||||
Recorder::Recorder() {
|
||||
this->m_StartTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
|
||||
this->m_IsRecording = false;
|
||||
}
|
||||
|
||||
Recorder::~Recorder() {
|
||||
}
|
||||
|
||||
void Recorder::AddRecord(Record* record)
|
||||
{
|
||||
if (!this->m_IsRecording) {
|
||||
return;
|
||||
}
|
||||
|
||||
Game::logger->Log("Recorder", "Adding record");
|
||||
|
||||
// Time since start of recording
|
||||
record->m_Timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()) - this->m_StartTime;
|
||||
|
||||
this->m_MovementRecords.push_back(record);
|
||||
}
|
||||
|
||||
void Recorder::Act(Entity* actor) {
|
||||
Game::logger->Log("Recorder", "Acting %d steps", m_MovementRecords.size());
|
||||
|
||||
// Loop through all records
|
||||
for (auto* record : m_MovementRecords) {
|
||||
record->Act(actor);
|
||||
}
|
||||
}
|
||||
|
||||
Entity* Recording::Recorder::ActFor(Entity* actorTemplate, Entity* player) {
|
||||
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);
|
||||
|
||||
return actor;
|
||||
}
|
||||
|
||||
void Recording::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;
|
||||
}
|
||||
|
||||
Recording::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 Recording::MovementRecord::Act(Entity* actor) {
|
||||
// Calculate the amount of seconds (as a float) since the start of the recording
|
||||
float time = m_Timestamp.count() / 1000.0f;
|
||||
|
||||
auto r = *this;
|
||||
|
||||
actor->AddCallbackTimer(time, [actor, r] () {
|
||||
auto* controllableComponent = actor->GetComponent<ControllablePhysicsComponent>();
|
||||
|
||||
if (controllableComponent) {
|
||||
controllableComponent->SetPosition(r.position);
|
||||
controllableComponent->SetRotation(r.rotation);
|
||||
controllableComponent->SetVelocity(r.velocity);
|
||||
controllableComponent->SetAngularVelocity(r.angularVelocity);
|
||||
controllableComponent->SetIsOnGround(r.onGround);
|
||||
controllableComponent->SetDirtyVelocity(r.dirtyVelocity);
|
||||
controllableComponent->SetDirtyAngularVelocity(r.dirtyAngularVelocity);
|
||||
}
|
||||
|
||||
Game::entityManager->SerializeEntity(actor);
|
||||
});
|
||||
}
|
||||
|
||||
void Recording::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_Timestamp.count());
|
||||
|
||||
parent->InsertEndChild(element);
|
||||
}
|
||||
|
||||
void Recording::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_Timestamp = std::chrono::milliseconds(element->Int64Attribute("t"));
|
||||
}
|
||||
|
||||
Recording::SpeakRecord::SpeakRecord(const std::string& text) {
|
||||
this->text = text;
|
||||
}
|
||||
|
||||
void Recording::SpeakRecord::Act(Entity* actor) {
|
||||
// Calculate the amount of seconds (as a float) since the start of the recording
|
||||
float time = m_Timestamp.count() / 1000.0f;
|
||||
|
||||
auto r = *this;
|
||||
|
||||
actor->AddCallbackTimer(time, [actor, r] () {
|
||||
GameMessages::SendNotifyClientZoneObject(
|
||||
actor->GetObjectID(), u"sendToclient_bubble", 0, 0, actor->GetObjectID(), r.text, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
|
||||
Game::entityManager->SerializeEntity(actor);
|
||||
});
|
||||
}
|
||||
|
||||
void Recording::SpeakRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
||||
auto* element = document.NewElement("SpeakRecord");
|
||||
|
||||
element->SetAttribute("text", text.c_str());
|
||||
|
||||
element->SetAttribute("t", m_Timestamp.count());
|
||||
|
||||
parent->InsertEndChild(element);
|
||||
}
|
||||
|
||||
void Recording::SpeakRecord::Deserialize(tinyxml2::XMLElement* element) {
|
||||
text = element->Attribute("text");
|
||||
|
||||
m_Timestamp = std::chrono::milliseconds(element->Int64Attribute("t"));
|
||||
}
|
||||
|
||||
Recording::AnimationRecord::AnimationRecord(const std::string& animation) {
|
||||
this->animation = animation;
|
||||
}
|
||||
|
||||
void Recording::AnimationRecord::Act(Entity* actor) {
|
||||
// Calculate the amount of seconds (as a float) since the start of the recording
|
||||
float time = m_Timestamp.count() / 1000.0f;
|
||||
|
||||
auto r = *this;
|
||||
|
||||
actor->AddCallbackTimer(time, [actor, r] () {
|
||||
GameMessages::SendPlayAnimation(actor, GeneralUtils::ASCIIToUTF16(r.animation));
|
||||
|
||||
Game::entityManager->SerializeEntity(actor);
|
||||
});
|
||||
}
|
||||
|
||||
void Recording::AnimationRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
||||
auto* element = document.NewElement("AnimationRecord");
|
||||
|
||||
element->SetAttribute("animation", animation.c_str());
|
||||
|
||||
element->SetAttribute("t", m_Timestamp.count());
|
||||
|
||||
parent->InsertEndChild(element);
|
||||
}
|
||||
|
||||
void Recording::AnimationRecord::Deserialize(tinyxml2::XMLElement* element) {
|
||||
animation = element->Attribute("animation");
|
||||
|
||||
m_Timestamp = std::chrono::milliseconds(element->Int64Attribute("t"));
|
||||
}
|
||||
|
||||
Recording::EquipRecord::EquipRecord(LOT item) {
|
||||
this->item = item;
|
||||
}
|
||||
|
||||
void Recording::EquipRecord::Act(Entity* actor) {
|
||||
// Calculate the amount of seconds (as a float) since the start of the recording
|
||||
float time = m_Timestamp.count() / 1000.0f;
|
||||
|
||||
auto r = *this;
|
||||
|
||||
actor->AddCallbackTimer(time, [actor, r] () {
|
||||
auto* inventoryComponent = actor->GetComponent<InventoryComponent>();
|
||||
|
||||
const LWOOBJID id = ObjectIDManager::Instance()->GenerateObjectID();
|
||||
|
||||
const auto& info = Inventory::FindItemComponent(r.item);
|
||||
|
||||
if (inventoryComponent) {
|
||||
inventoryComponent->UpdateSlot(info.equipLocation, { id, r.item, 1, 0 });
|
||||
}
|
||||
|
||||
Game::entityManager->SerializeEntity(actor);
|
||||
});
|
||||
}
|
||||
|
||||
void Recording::EquipRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
||||
auto* element = document.NewElement("EquipRecord");
|
||||
|
||||
element->SetAttribute("item", item);
|
||||
|
||||
element->SetAttribute("t", m_Timestamp.count());
|
||||
|
||||
parent->InsertEndChild(element);
|
||||
}
|
||||
|
||||
void Recording::EquipRecord::Deserialize(tinyxml2::XMLElement* element) {
|
||||
item = element->IntAttribute("item");
|
||||
|
||||
m_Timestamp = std::chrono::milliseconds(element->Int64Attribute("t"));
|
||||
}
|
||||
|
||||
Recording::UnequipRecord::UnequipRecord(LOT item) {
|
||||
this->item = item;
|
||||
}
|
||||
|
||||
void Recording::UnequipRecord::Act(Entity* actor) {
|
||||
// Calculate the amount of seconds (as a float) since the start of the recording
|
||||
float time = m_Timestamp.count() / 1000.0f;
|
||||
|
||||
auto r = *this;
|
||||
|
||||
actor->AddCallbackTimer(time, [actor, r] () {
|
||||
auto* inventoryComponent = actor->GetComponent<InventoryComponent>();
|
||||
|
||||
const auto& info = Inventory::FindItemComponent(r.item);
|
||||
|
||||
if (inventoryComponent) {
|
||||
inventoryComponent->RemoveSlot(info.equipLocation);
|
||||
}
|
||||
|
||||
Game::entityManager->SerializeEntity(actor);
|
||||
});
|
||||
}
|
||||
|
||||
void Recording::UnequipRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
||||
auto* element = document.NewElement("UnequipRecord");
|
||||
|
||||
element->SetAttribute("item", item);
|
||||
|
||||
element->SetAttribute("t", m_Timestamp.count());
|
||||
|
||||
parent->InsertEndChild(element);
|
||||
}
|
||||
|
||||
void Recording::UnequipRecord::Deserialize(tinyxml2::XMLElement* element) {
|
||||
item = element->IntAttribute("item");
|
||||
|
||||
m_Timestamp = std::chrono::milliseconds(element->Int64Attribute("t"));
|
||||
}
|
||||
|
||||
void Recording::ClearEquippedRecord::Act(Entity* actor) {
|
||||
// Calculate the amount of seconds (as a float) since the start of the recording
|
||||
float time = m_Timestamp.count() / 1000.0f;
|
||||
|
||||
auto r = *this;
|
||||
|
||||
actor->AddCallbackTimer(time, [actor, r] () {
|
||||
auto* inventoryComponent = actor->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent) {
|
||||
auto equipped = inventoryComponent->GetEquippedItems();
|
||||
|
||||
for (auto entry : equipped) {
|
||||
inventoryComponent->RemoveSlot(entry.first);
|
||||
}
|
||||
}
|
||||
|
||||
Game::entityManager->SerializeEntity(actor);
|
||||
});
|
||||
}
|
||||
|
||||
void Recording::ClearEquippedRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
|
||||
auto* element = document.NewElement("ClearEquippedRecord");
|
||||
|
||||
element->SetAttribute("t", m_Timestamp.count());
|
||||
|
||||
parent->InsertEndChild(element);
|
||||
}
|
||||
|
||||
void Recording::ClearEquippedRecord::Deserialize(tinyxml2::XMLElement* element) {
|
||||
m_Timestamp = std::chrono::milliseconds(element->Int64Attribute("t"));
|
||||
}
|
||||
|
||||
void Recording::Recorder::SaveToFile(const std::string& filename) {
|
||||
tinyxml2::XMLDocument document;
|
||||
|
||||
auto* root = document.NewElement("Recorder");
|
||||
|
||||
for (auto* record : m_MovementRecords) {
|
||||
record->Serialize(document, root);
|
||||
}
|
||||
|
||||
document.InsertFirstChild(root);
|
||||
|
||||
document.SaveFile(filename.c_str());
|
||||
}
|
||||
|
||||
float Recording::Recorder::GetDuration() const {
|
||||
// Return the highest timestamp
|
||||
float duration = 0.0f;
|
||||
|
||||
for (auto* record : m_MovementRecords) {
|
||||
duration = std::max(duration, record->m_Timestamp.count() / 1000.0f);
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
Recorder* Recording::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();
|
||||
|
||||
for (auto* element = root->FirstChildElement(); element; element = element->NextSiblingElement()) {
|
||||
const std::string name = element->Name();
|
||||
|
||||
if (name == "MovementRecord") {
|
||||
MovementRecord* record = new MovementRecord();
|
||||
record->Deserialize(element);
|
||||
recorder->m_MovementRecords.push_back(record);
|
||||
} else if (name == "SpeakRecord") {
|
||||
SpeakRecord* record = new SpeakRecord();
|
||||
record->Deserialize(element);
|
||||
recorder->m_MovementRecords.push_back(record);
|
||||
} else if (name == "AnimationRecord") {
|
||||
AnimationRecord* record = new AnimationRecord();
|
||||
record->Deserialize(element);
|
||||
recorder->m_MovementRecords.push_back(record);
|
||||
} else if (name == "EquipRecord") {
|
||||
EquipRecord* record = new EquipRecord();
|
||||
record->Deserialize(element);
|
||||
recorder->m_MovementRecords.push_back(record);
|
||||
} else if (name == "UnequipRecord") {
|
||||
UnequipRecord* record = new UnequipRecord();
|
||||
record->Deserialize(element);
|
||||
recorder->m_MovementRecords.push_back(record);
|
||||
} else if (name == "ClearEquippedRecord") {
|
||||
ClearEquippedRecord* record = new ClearEquippedRecord();
|
||||
record->Deserialize(element);
|
||||
recorder->m_MovementRecords.push_back(record);
|
||||
}
|
||||
}
|
||||
|
||||
return recorder;
|
||||
}
|
||||
|
||||
void Recording::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);
|
||||
}
|
170
dGame/dUtilities/Recorder.h
Normal file
170
dGame/dUtilities/Recorder.h
Normal file
@ -0,0 +1,170 @@
|
||||
#pragma once
|
||||
|
||||
#include "Player.h"
|
||||
#include "Game.h"
|
||||
#include "EntityManager.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace Recording
|
||||
{
|
||||
|
||||
class Record
|
||||
{
|
||||
public:
|
||||
virtual void Act(Entity* actor) = 0;
|
||||
|
||||
virtual void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) = 0;
|
||||
|
||||
virtual void Deserialize(tinyxml2::XMLElement* element) = 0;
|
||||
|
||||
std::chrono::milliseconds m_Timestamp;
|
||||
};
|
||||
|
||||
|
||||
class MovementRecord : public Record
|
||||
{
|
||||
public:
|
||||
NiPoint3 position;
|
||||
NiQuaternion rotation;
|
||||
NiPoint3 velocity;
|
||||
NiPoint3 angularVelocity;
|
||||
bool onGround;
|
||||
bool dirtyVelocity;
|
||||
bool dirtyAngularVelocity;
|
||||
|
||||
MovementRecord() = default;
|
||||
|
||||
MovementRecord(
|
||||
const NiPoint3& position,
|
||||
const NiQuaternion& rotation,
|
||||
const NiPoint3& velocity,
|
||||
const NiPoint3& angularVelocity,
|
||||
bool onGround, bool dirtyVelocity, bool dirtyAngularVelocity
|
||||
);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class SpeakRecord : public Record
|
||||
{
|
||||
public:
|
||||
std::string text;
|
||||
|
||||
SpeakRecord() = default;
|
||||
|
||||
SpeakRecord(const std::string& text);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class AnimationRecord : public Record
|
||||
{
|
||||
public:
|
||||
std::string animation;
|
||||
|
||||
AnimationRecord() = default;
|
||||
|
||||
AnimationRecord(const std::string& animation);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class EquipRecord : public Record
|
||||
{
|
||||
public:
|
||||
LOT item;
|
||||
|
||||
EquipRecord() = default;
|
||||
|
||||
EquipRecord(LOT item);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class UnequipRecord : public Record
|
||||
{
|
||||
public:
|
||||
LOT item;
|
||||
|
||||
UnequipRecord() = default;
|
||||
|
||||
UnequipRecord(LOT item);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class ClearEquippedRecord : public Record
|
||||
{
|
||||
public:
|
||||
ClearEquippedRecord() = default;
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
|
||||
class Recorder
|
||||
{
|
||||
public:
|
||||
Recorder();
|
||||
|
||||
~Recorder();
|
||||
|
||||
void AddRecord(Record* record);
|
||||
|
||||
void Act(Entity* actor);
|
||||
|
||||
Entity* ActFor(Entity* actorTemplate, Entity* player);
|
||||
|
||||
void StopActingFor(Entity* actor, Entity* actorTemplate, LWOOBJID playerID);
|
||||
|
||||
bool IsRecording() const;
|
||||
|
||||
void SaveToFile(const std::string& filename);
|
||||
|
||||
float GetDuration() const;
|
||||
|
||||
static Recorder* LoadFromFile(const std::string& filename);
|
||||
|
||||
static void AddRecording(LWOOBJID actorID, Recorder* recorder);
|
||||
|
||||
static void StartRecording(LWOOBJID actorID);
|
||||
|
||||
static void StopRecording(LWOOBJID actorID);
|
||||
|
||||
static Recorder* GetRecorder(LWOOBJID actorID);
|
||||
|
||||
private:
|
||||
std::vector<Record*> m_MovementRecords;
|
||||
|
||||
bool m_IsRecording;
|
||||
|
||||
std::chrono::milliseconds m_StartTime;
|
||||
};
|
||||
|
||||
}
|
113
dGame/dUtilities/ServerPreconditions.cpp
Normal file
113
dGame/dUtilities/ServerPreconditions.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
#include "ServerPreconditions.hpp"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
using namespace ServerPreconditions;
|
||||
|
||||
std::unordered_map<LOT, std::vector<std::pair<bool, PreconditionExpression>>> ServerPreconditions::m_Preconditions;
|
||||
|
||||
std::unordered_map<LWOOBJID, LWOOBJID> ServerPreconditions::m_SoloActors;
|
||||
|
||||
std::unordered_map<LWOOBJID, std::unordered_set<LWOOBJID>> ServerPreconditions::m_ExcludeForPlayer;
|
||||
|
||||
void ServerPreconditions::LoadPreconditions(std::string file) {
|
||||
tinyxml2::XMLDocument doc;
|
||||
doc.LoadFile(file.c_str());
|
||||
|
||||
tinyxml2::XMLElement* root = doc.FirstChildElement("Preconditions");
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (tinyxml2::XMLElement* element = root->FirstChildElement("Entity"); element; element = element->NextSiblingElement("Entity")) {
|
||||
LOT lot = element->UnsignedAttribute("lot");
|
||||
std::vector<std::pair<bool, PreconditionExpression>> preconditions;
|
||||
|
||||
for (tinyxml2::XMLElement* precondition = element->FirstChildElement("Precondition"); precondition; precondition = precondition->NextSiblingElement("Precondition")) {
|
||||
const auto condition = Preconditions::CreateExpression(precondition->GetText());
|
||||
|
||||
int64_t inverted;
|
||||
|
||||
if (precondition->QueryInt64Attribute("not", &inverted) == tinyxml2::XML_SUCCESS) {
|
||||
preconditions.push_back(std::make_pair(inverted > 0, condition));
|
||||
}
|
||||
else {
|
||||
preconditions.push_back(std::make_pair(false, condition));
|
||||
}
|
||||
}
|
||||
|
||||
m_Preconditions[lot] = preconditions;
|
||||
}
|
||||
}
|
||||
|
||||
bool ServerPreconditions::CheckPreconditions(Entity* target, Entity* entity) {
|
||||
if (IsExcludedFor(entity->GetObjectID(), target->GetObjectID())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsSoloActor(target->GetObjectID())) {
|
||||
return IsActingFor(target->GetObjectID(), entity->GetObjectID());
|
||||
}
|
||||
|
||||
if (m_Preconditions.find(target->GetLOT()) == m_Preconditions.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& [inverse, precondition] : m_Preconditions[target->GetLOT()]) {
|
||||
if (precondition.Check(entity) == inverse) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ServerPreconditions::IsSoloActor(LWOOBJID actor) {
|
||||
return m_SoloActors.find(actor) != m_SoloActors.end();
|
||||
}
|
||||
|
||||
bool ServerPreconditions::IsActingFor(LWOOBJID actor, LWOOBJID target) {
|
||||
return m_SoloActors.find(actor) != m_SoloActors.end() && m_SoloActors[actor] == target;
|
||||
}
|
||||
|
||||
void ServerPreconditions::AddSoloActor(LWOOBJID actor, LWOOBJID target) {
|
||||
m_SoloActors[actor] = target;
|
||||
}
|
||||
|
||||
void ServerPreconditions::RemoveSoloActor(LWOOBJID actor) {
|
||||
m_SoloActors.erase(actor);
|
||||
}
|
||||
|
||||
void ServerPreconditions::AddExcludeFor(LWOOBJID player, LWOOBJID target) {
|
||||
const auto& it = m_ExcludeForPlayer.find(player);
|
||||
|
||||
if (it == m_ExcludeForPlayer.end()) {
|
||||
m_ExcludeForPlayer[player] = std::unordered_set<LWOOBJID>();
|
||||
}
|
||||
|
||||
m_ExcludeForPlayer[player].insert(target);
|
||||
}
|
||||
|
||||
void ServerPreconditions::RemoveExcludeFor(LWOOBJID player, LWOOBJID target) {
|
||||
const auto& it = m_ExcludeForPlayer.find(player);
|
||||
|
||||
if (it == m_ExcludeForPlayer.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_ExcludeForPlayer[player].erase(target);
|
||||
|
||||
if (m_ExcludeForPlayer[player].empty()) {
|
||||
m_ExcludeForPlayer.erase(player);
|
||||
}
|
||||
}
|
||||
|
||||
bool ServerPreconditions::IsExcludedFor(LWOOBJID player, LWOOBJID target) {
|
||||
const auto& it = m_ExcludeForPlayer.find(player);
|
||||
|
||||
if (it == m_ExcludeForPlayer.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return it->second.find(target) != it->second.end();
|
||||
}
|
38
dGame/dUtilities/ServerPreconditions.hpp
Normal file
38
dGame/dUtilities/ServerPreconditions.hpp
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include "dCommonVars.h"
|
||||
#include "Preconditions.h"
|
||||
|
||||
class Entity;
|
||||
|
||||
namespace ServerPreconditions
|
||||
{
|
||||
|
||||
extern std::unordered_map<LOT, std::vector<std::pair<bool, PreconditionExpression>>> m_Preconditions;
|
||||
|
||||
extern std::unordered_map<LWOOBJID, LWOOBJID> m_SoloActors;
|
||||
|
||||
extern std::unordered_map<LWOOBJID, std::unordered_set<LWOOBJID>> m_ExcludeForPlayer;
|
||||
|
||||
void LoadPreconditions(std::string file);
|
||||
|
||||
bool CheckPreconditions(Entity* target, Entity* entity);
|
||||
|
||||
bool IsSoloActor(LWOOBJID actor);
|
||||
|
||||
bool IsActingFor(LWOOBJID actor, LWOOBJID target);
|
||||
|
||||
void AddSoloActor(LWOOBJID actor, LWOOBJID target);
|
||||
|
||||
void RemoveSoloActor(LWOOBJID actor);
|
||||
|
||||
void AddExcludeFor(LWOOBJID player, LWOOBJID target);
|
||||
|
||||
void RemoveExcludeFor(LWOOBJID player, LWOOBJID target);
|
||||
|
||||
bool IsExcludedFor(LWOOBJID player, LWOOBJID target);
|
||||
|
||||
}
|
@ -86,6 +86,9 @@
|
||||
#include "CDObjectsTable.h"
|
||||
#include "CDZoneTableTable.h"
|
||||
|
||||
#include "Recorder.h"
|
||||
#include "ServerPreconditions.hpp"
|
||||
|
||||
void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr) {
|
||||
auto commandCopy = command;
|
||||
// Sanity check that a command was given
|
||||
@ -711,6 +714,33 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
||||
return;
|
||||
}
|
||||
|
||||
if (chatCommand == "removemission" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
||||
if (args.size() == 0) return;
|
||||
|
||||
uint32_t missionID;
|
||||
|
||||
if (!GeneralUtils::TryParse(args[0], missionID)) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto* comp = static_cast<MissionComponent*>(entity->GetComponent(eReplicaComponentType::MISSION));
|
||||
|
||||
if (comp == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* mission = comp->GetMission(missionID);
|
||||
|
||||
if (mission == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
comp->RemoveMission(missionID);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (chatCommand == "playeffect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) {
|
||||
int32_t effectID = 0;
|
||||
|
||||
@ -883,6 +913,86 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
||||
GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS);
|
||||
}
|
||||
|
||||
if (chatCommand == "record-act" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
||||
EntityInfo info;
|
||||
info.lot = 2097253;
|
||||
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
|
||||
if (args.size() > 0) {
|
||||
if (!GeneralUtils::TryParse(args[0], info.lot)) {
|
||||
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);
|
||||
|
||||
auto* record = Recording::Recorder::GetRecorder(entity->GetObjectID());
|
||||
|
||||
if (record) {
|
||||
record->Act(actor);
|
||||
} else {
|
||||
Game::logger->Log("SlashCommandHandler", "Failed to get recorder for objectID: %llu", entity->GetObjectID());
|
||||
}
|
||||
}
|
||||
|
||||
if (chatCommand == "record-start" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
||||
Recording::Recorder::StartRecording(entity->GetObjectID());
|
||||
|
||||
auto* record = Recording::Recorder::GetRecorder(entity->GetObjectID());
|
||||
|
||||
if (record) {
|
||||
if (args.size() > 0 && args[0] == "clear") {
|
||||
record->AddRecord(new Recording::ClearEquippedRecord());
|
||||
}
|
||||
} else {
|
||||
Game::logger->Log("SlashCommandHandler", "Failed to get recorder for objectID: %llu", entity->GetObjectID());
|
||||
}
|
||||
}
|
||||
|
||||
if (chatCommand == "record-stop" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
||||
Recording::Recorder::StopRecording(entity->GetObjectID());
|
||||
}
|
||||
|
||||
if (chatCommand == "record-save" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) {
|
||||
auto* record = Recording::Recorder::GetRecorder(entity->GetObjectID());
|
||||
|
||||
if (record) {
|
||||
record->SaveToFile(args[0]);
|
||||
} else {
|
||||
Game::logger->Log("SlashCommandHandler", "Failed to get recorder for objectID: %llu", entity->GetObjectID());
|
||||
}
|
||||
}
|
||||
|
||||
if (chatCommand == "record-load" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) {
|
||||
auto* record = Recording::Recorder::LoadFromFile(args[0]);
|
||||
|
||||
if (record) {
|
||||
Recording::Recorder::AddRecording(entity->GetObjectID(), record);
|
||||
} else {
|
||||
Game::logger->Log("SlashCommandHandler", "Failed to load recording from file: %s", args[0].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if ((chatCommand == "teleport" || chatCommand == "tele") && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_MODERATOR) {
|
||||
NiPoint3 pos{};
|
||||
if (args.size() == 3) {
|
||||
|
@ -117,18 +117,23 @@ void VanityUtilities::SpawnVanity() {
|
||||
if (!npc.m_Phrases.empty()){
|
||||
npcEntity->SetVar<std::vector<std::string>>(u"chats", npc.m_Phrases);
|
||||
|
||||
auto* scriptComponent = npcEntity->GetComponent<ScriptComponent>();
|
||||
|
||||
if (scriptComponent && !npc.m_Script.empty()) {
|
||||
scriptComponent->SetScript(npc.m_Script);
|
||||
scriptComponent->SetSerialized(false);
|
||||
|
||||
for (const auto& npc : npc.m_Flags) {
|
||||
npcEntity->SetVar<bool>(GeneralUtils::ASCIIToUTF16(npc.first), npc.second);
|
||||
}
|
||||
}
|
||||
SetupNPCTalk(npcEntity);
|
||||
}
|
||||
|
||||
auto* scriptComponent = npcEntity->GetComponent<ScriptComponent>();
|
||||
|
||||
Game::logger->Log("VanityUtilities", "Script: %s", npc.m_Script.c_str());
|
||||
|
||||
if (scriptComponent && !npc.m_Script.empty()) {
|
||||
scriptComponent->SetScript(npc.m_Script);
|
||||
scriptComponent->SetSerialized(false);
|
||||
|
||||
Game::logger->Log("VanityUtilities", "Setting script to %s", npc.m_Script.c_str());
|
||||
|
||||
for (const auto& npc : npc.m_Flags) {
|
||||
npcEntity->SetVar<bool>(GeneralUtils::ASCIIToUTF16(npc.first), npc.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (zoneID == 1200) {
|
||||
@ -162,6 +167,9 @@ Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoin
|
||||
entity->SetVar(u"npcName", name);
|
||||
if (entity->GetVar<bool>(u"noGhosting")) entity->SetIsGhostingCandidate(false);
|
||||
|
||||
// Debug print
|
||||
Game::logger->Log("VanityUtilities", "Spawning NPC %s (%i) at %f, %f, %f", name.c_str(), lot, position.x, position.y, position.z);
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent && !inventory.empty()) {
|
||||
@ -176,6 +184,15 @@ Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoin
|
||||
destroyableComponent->SetHealth(0);
|
||||
}
|
||||
|
||||
auto* scriptComponent = entity->GetComponent<ScriptComponent>();
|
||||
|
||||
if (scriptComponent == nullptr)
|
||||
{
|
||||
entity->AddComponent(eReplicaComponentType::SCRIPT, new ScriptComponent(entity, "", false));
|
||||
}
|
||||
|
||||
Game::logger->Log("VanityUtilities", "NPC has script component? %s", (entity->GetComponent<ScriptComponent>() != nullptr) ? "true" : "false");
|
||||
|
||||
Game::entityManager->ConstructEntity(entity);
|
||||
|
||||
return entity;
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "CheatDetection.h"
|
||||
#include "Recorder.h"
|
||||
|
||||
void ClientPackets::HandleChatMessage(const SystemAddress& sysAddr, Packet* packet) {
|
||||
User* user = UserManager::Instance()->GetUser(sysAddr);
|
||||
@ -82,6 +83,12 @@ void ClientPackets::HandleChatMessage(const SystemAddress& sysAddr, Packet* pack
|
||||
std::string sMessage = GeneralUtils::UTF16ToWTF8(message);
|
||||
Game::logger->Log("Chat", "%s: %s", playerName.c_str(), sMessage.c_str());
|
||||
ChatPackets::SendChatMessage(sysAddr, chatChannel, playerName, user->GetLoggedInChar(), isMythran, message);
|
||||
|
||||
auto* recorder = Recording::Recorder::GetRecorder(user->GetLoggedInChar());
|
||||
|
||||
if (recorder != nullptr) {
|
||||
recorder->AddRecord(new Recording::SpeakRecord(sMessage));
|
||||
}
|
||||
}
|
||||
|
||||
void ClientPackets::HandleClientPositionUpdate(const SystemAddress& sysAddr, Packet* packet) {
|
||||
@ -236,6 +243,20 @@ void ClientPackets::HandleClientPositionUpdate(const SystemAddress& sysAddr, Pac
|
||||
comp->SetAngularVelocity(angVelocity);
|
||||
comp->SetDirtyAngularVelocity(angVelocityFlag);
|
||||
|
||||
auto* recorder = Recording::Recorder::GetRecorder(entity->GetObjectID());
|
||||
|
||||
if (recorder != nullptr) {
|
||||
recorder->AddRecord(new Recording::MovementRecord(
|
||||
position,
|
||||
rotation,
|
||||
velocity,
|
||||
angVelocity,
|
||||
onGround,
|
||||
velocityFlag,
|
||||
angVelocityFlag
|
||||
));
|
||||
}
|
||||
|
||||
auto* player = static_cast<Player*>(entity);
|
||||
player->SetGhostReferencePoint(position);
|
||||
Game::entityManager->QueueGhostUpdate(player->GetObjectID());
|
||||
|
@ -1,3 +1,4 @@
|
||||
set(DSCRIPTS_SOURCES_02_SERVER_DLU
|
||||
"DLUVanityNPC.cpp"
|
||||
"DukeDialogueGlowingBrick.cpp"
|
||||
PARENT_SCOPE)
|
||||
|
72
dScripts/02_server/DLU/DukeDialogueGlowingBrick.cpp
Normal file
72
dScripts/02_server/DLU/DukeDialogueGlowingBrick.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include "DukeDialogueGlowingBrick.h"
|
||||
|
||||
#include "eMissionState.h"
|
||||
|
||||
#include "Recorder.h"
|
||||
#include "MissionComponent.h"
|
||||
#include "InventoryComponent.h"
|
||||
|
||||
void DukeDialogueGlowingBrick::OnStartup(Entity* self) {
|
||||
Game::logger->Log("DukeDialogueGlowingBrick", "OnStartup");
|
||||
}
|
||||
|
||||
void DukeDialogueGlowingBrick::OnTimerDone(Entity* self, std::string timerName) {
|
||||
}
|
||||
|
||||
void DukeDialogueGlowingBrick::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, eMissionState missionState) {
|
||||
if (missionID != 201453) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (missionState != eMissionState::AVAILABLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* recorder = Recording::Recorder::LoadFromFile("DukeGlowing.xml");
|
||||
|
||||
if (recorder == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* actor = recorder->ActFor(self, target);
|
||||
|
||||
if (actor == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto targetID = target->GetObjectID();
|
||||
const auto actorID = actor->GetObjectID();
|
||||
|
||||
self->AddCallbackTimer(3.0f, [targetID] () {
|
||||
auto* target = Game::entityManager->GetEntity(targetID);
|
||||
|
||||
if (target == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* missionComponent = target->GetComponent<MissionComponent>();
|
||||
|
||||
if (missionComponent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
missionComponent->CompleteMission(201453);
|
||||
});
|
||||
|
||||
self->AddCallbackTimer(recorder->GetDuration() + 10.0f, [recorder, self, actorID, targetID] () {
|
||||
auto* target = Game::entityManager->GetEntity(targetID);
|
||||
auto* actor = Game::entityManager->GetEntity(actorID);
|
||||
|
||||
if (target == nullptr || actor == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
recorder->StopActingFor(actor, self, targetID);
|
||||
|
||||
delete recorder;
|
||||
});
|
||||
}
|
||||
|
||||
void DukeDialogueGlowingBrick::OnRespondToMission(Entity* self, int missionID, Entity* player, int reward) {
|
||||
|
||||
}
|
11
dScripts/02_server/DLU/DukeDialogueGlowingBrick.h
Normal file
11
dScripts/02_server/DLU/DukeDialogueGlowingBrick.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include "CppScripts.h"
|
||||
|
||||
class DukeDialogueGlowingBrick : public CppScripts::Script
|
||||
{
|
||||
public:
|
||||
void OnStartup(Entity* self) override;
|
||||
void OnTimerDone(Entity* self, std::string timerName) override;
|
||||
void OnMissionDialogueOK(Entity* self, Entity* target, int missionID, eMissionState missionState) override;
|
||||
void OnRespondToMission(Entity* self, int missionID, Entity* player, int reward) override;
|
||||
};
|
@ -212,6 +212,7 @@
|
||||
|
||||
// DLU Scripts
|
||||
#include "DLUVanityNPC.h"
|
||||
#include "DukeDialogueGlowingBrick.h"
|
||||
|
||||
// AM Scripts
|
||||
#include "AmConsoleTeleportServer.h"
|
||||
@ -819,6 +820,8 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr
|
||||
//DLU:
|
||||
else if (scriptName == "scripts\\02_server\\DLU\\DLUVanityNPC.lua")
|
||||
script = new DLUVanityNPC();
|
||||
else if (scriptName == "scripts\\02_server\\DLU\\DukeGlowing.lua")
|
||||
script = new DukeDialogueGlowingBrick();
|
||||
|
||||
// Survival minigame
|
||||
else if (scriptName == "scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_STROMBIE.lua")
|
||||
|
@ -75,6 +75,8 @@
|
||||
#include "EntityManager.h"
|
||||
#include "CheatDetection.h"
|
||||
|
||||
#include "ServerPreconditions.hpp"
|
||||
|
||||
namespace Game {
|
||||
dLogger* logger = nullptr;
|
||||
dServer* server = nullptr;
|
||||
@ -255,6 +257,8 @@ int main(int argc, char** argv) {
|
||||
|
||||
PerformanceManager::SelectProfile(zoneID);
|
||||
|
||||
ServerPreconditions::LoadPreconditions("vanity/preconditions.xml");
|
||||
|
||||
Game::entityManager = new EntityManager();
|
||||
Game::zoneManager = new dZoneManager();
|
||||
//Load our level:
|
||||
|
Loading…
Reference in New Issue
Block a user