mirror of
				https://github.com/DarkflameUniverse/DarkflameServer.git
				synced 2025-10-31 12:41:55 +00:00 
			
		
		
		
	Initial changes.
* Recorder to recall player actions. * Server precondtions to manage entity visiblity.
This commit is contained in:
		| @@ -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: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 wincent
					wincent