F4MP/tiltedcode/Code/server/Services/CharacterService.cpp
Jous99 37b16f1547 code upload
codigo original de f4mp y tilted para referencias
2026-01-06 18:45:00 +01:00

865 lines
33 KiB
C++

#include <Services/CharacterService.h>
#include <Components.h>
#include <GameServer.h>
#include <World.h>
#include <Events/CharacterSpawnedEvent.h>
#include <Events/CharacterExteriorCellChangeEvent.h>
#include <Events/CharacterInteriorCellChangeEvent.h>
#include <Events/PlayerEnterWorldEvent.h>
#include <Events/UpdateEvent.h>
#include <Events/CharacterRemoveEvent.h>
#include <Events/OwnershipTransferEvent.h>
#include <Game/OwnerView.h>
#include <Messages/AssignCharacterRequest.h>
#include <Messages/AssignCharacterResponse.h>
#include <Messages/ServerReferencesMoveRequest.h>
#include <Messages/ClientReferencesMoveRequest.h>
#include <Messages/CharacterSpawnRequest.h>
#include <Messages/RequestFactionsChanges.h>
#include <Messages/NotifyFactionsChanges.h>
#include <Messages/NotifyRemoveCharacter.h>
#include <Messages/NotifySpawnData.h>
#include <Messages/RequestOwnershipTransfer.h>
#include <Messages/NotifyOwnershipTransfer.h>
#include <Messages/RequestOwnershipClaim.h>
#include <Messages/MountRequest.h>
#include <Messages/NotifyMount.h>
#include <Messages/NewPackageRequest.h>
#include <Messages/NotifyNewPackage.h>
#include <Messages/RequestRespawn.h>
#include <Messages/NotifyRespawn.h>
#include <Messages/SyncExperienceRequest.h>
#include <Messages/NotifySyncExperience.h>
#include <Messages/DialogueRequest.h>
#include <Messages/NotifyDialogue.h>
#include <Messages/SubtitleRequest.h>
#include <Messages/NotifySubtitle.h>
#include <Messages/NotifyActorTeleport.h>
#include <Messages/NotifyRelinquishControl.h>
#include <Setting.h>
namespace
{
Console::Setting bEnableXpSync{"Gameplay:bEnableXpSync", "Syncs combat XP within the party", true};
}
CharacterService::CharacterService(World& aWorld, entt::dispatcher& aDispatcher) noexcept
: m_world(aWorld)
, m_updateConnection(aDispatcher.sink<UpdateEvent>().connect<&CharacterService::OnUpdate>(this))
, m_interiorCellChangeEventConnection(aDispatcher.sink<CharacterInteriorCellChangeEvent>().connect<&CharacterService::OnCharacterInteriorCellChange>(this))
, m_exteriorCellChangeEventConnection(aDispatcher.sink<CharacterExteriorCellChangeEvent>().connect<&CharacterService::OnCharacterExteriorCellChange>(this))
, m_characterAssignRequestConnection(aDispatcher.sink<PacketEvent<AssignCharacterRequest>>().connect<&CharacterService::OnAssignCharacterRequest>(this))
, m_transferOwnershipConnection(aDispatcher.sink<PacketEvent<RequestOwnershipTransfer>>().connect<&CharacterService::OnOwnershipTransferRequest>(this))
, m_ownershipTransferEventConnection(aDispatcher.sink<OwnershipTransferEvent>().connect<&CharacterService::OnOwnershipTransferEvent>(this))
, m_claimOwnershipConnection(aDispatcher.sink<PacketEvent<RequestOwnershipClaim>>().connect<&CharacterService::OnOwnershipClaimRequest>(this))
, m_removeCharacterConnection(aDispatcher.sink<CharacterRemoveEvent>().connect<&CharacterService::OnCharacterRemoveEvent>(this))
, m_characterSpawnedConnection(aDispatcher.sink<CharacterSpawnedEvent>().connect<&CharacterService::OnCharacterSpawned>(this))
, m_referenceMovementSnapshotConnection(aDispatcher.sink<PacketEvent<ClientReferencesMoveRequest>>().connect<&CharacterService::OnReferencesMoveRequest>(this))
, m_factionsChangesConnection(aDispatcher.sink<PacketEvent<RequestFactionsChanges>>().connect<&CharacterService::OnFactionsChanges>(this))
, m_mountConnection(aDispatcher.sink<PacketEvent<MountRequest>>().connect<&CharacterService::OnMountRequest>(this))
, m_newPackageConnection(aDispatcher.sink<PacketEvent<NewPackageRequest>>().connect<&CharacterService::OnNewPackageRequest>(this))
, m_requestRespawnConnection(aDispatcher.sink<PacketEvent<RequestRespawn>>().connect<&CharacterService::OnRequestRespawn>(this))
, m_syncExperienceConnection(aDispatcher.sink<PacketEvent<SyncExperienceRequest>>().connect<&CharacterService::OnSyncExperienceRequest>(this))
, m_dialogueConnection(aDispatcher.sink<PacketEvent<DialogueRequest>>().connect<&CharacterService::OnDialogueRequest>(this))
, m_subtitleConnection(aDispatcher.sink<PacketEvent<SubtitleRequest>>().connect<&CharacterService::OnSubtitleRequest>(this))
{
}
void CharacterService::Serialize(World& aRegistry, entt::entity aEntity, CharacterSpawnRequest* apSpawnRequest) noexcept
{
const auto& characterComponent = aRegistry.get<CharacterComponent>(aEntity);
apSpawnRequest->ServerId = World::ToInteger(aEntity);
apSpawnRequest->AppearanceBuffer = characterComponent.SaveBuffer;
apSpawnRequest->ChangeFlags = characterComponent.ChangeFlags;
apSpawnRequest->FaceTints = characterComponent.FaceTints;
apSpawnRequest->FactionsContent = characterComponent.FactionsContent;
apSpawnRequest->IsDead = characterComponent.IsDead();
apSpawnRequest->IsPlayer = characterComponent.IsPlayer();
apSpawnRequest->IsWeaponDrawn = characterComponent.IsWeaponDrawn();
apSpawnRequest->IsPlayerSummon = characterComponent.IsPlayerSummon();
apSpawnRequest->PlayerId = characterComponent.PlayerId;
const auto* pFormIdComponent = aRegistry.try_get<FormIdComponent>(aEntity);
if (pFormIdComponent)
{
apSpawnRequest->FormId = pFormIdComponent->Id;
}
const auto* pInventoryComponent = aRegistry.try_get<InventoryComponent>(aEntity);
if (pInventoryComponent)
{
apSpawnRequest->InventoryContent = pInventoryComponent->Content;
}
const auto* pActorValuesComponent = aRegistry.try_get<ActorValuesComponent>(aEntity);
if (pActorValuesComponent)
{
apSpawnRequest->InitialActorValues = pActorValuesComponent->CurrentActorValues;
}
if (characterComponent.BaseId)
{
apSpawnRequest->BaseId = characterComponent.BaseId.Id;
}
const auto* pMovementComponent = aRegistry.try_get<MovementComponent>(aEntity);
if (pMovementComponent)
{
apSpawnRequest->Position = pMovementComponent->Position;
apSpawnRequest->Rotation.x = pMovementComponent->Rotation.x;
apSpawnRequest->Rotation.y = pMovementComponent->Rotation.z;
}
const auto* pCellIdComponent = aRegistry.try_get<CellIdComponent>(aEntity);
if (pCellIdComponent)
{
apSpawnRequest->CellId = pCellIdComponent->Cell;
}
auto& animationComponent = aRegistry.get<AnimationComponent>(aEntity);
apSpawnRequest->ActionsToReplay = animationComponent.ActionsReplayCache.FormRefinedReplayChain();
}
void CharacterService::OnUpdate(const UpdateEvent&) const noexcept
{
ProcessFactionsChanges();
ProcessMovementChanges();
}
void CharacterService::OnCharacterExteriorCellChange(const CharacterExteriorCellChangeEvent& acEvent) const noexcept
{
CharacterSpawnRequest spawnMessage;
Serialize(m_world, acEvent.Entity, &spawnMessage);
NotifyRemoveCharacter removeMessage;
removeMessage.ServerId = World::ToInteger(acEvent.Entity);
for (auto pPlayer : m_world.GetPlayerManager())
{
if (acEvent.Owner == pPlayer)
continue;
if (pPlayer->GetCellComponent().WorldSpaceId != acEvent.WorldSpaceId || pPlayer->GetCellComponent().WorldSpaceId == acEvent.WorldSpaceId && !GridCellCoords::IsCellInGridCell(acEvent.CurrentCoords, pPlayer->GetCellComponent().CenterCoords, false))
{
pPlayer->Send(removeMessage);
}
else if (pPlayer->GetCellComponent().WorldSpaceId == acEvent.WorldSpaceId && GridCellCoords::IsCellInGridCell(acEvent.CurrentCoords, pPlayer->GetCellComponent().CenterCoords, false))
{
pPlayer->Send(spawnMessage);
}
}
}
void CharacterService::OnCharacterInteriorCellChange(const CharacterInteriorCellChangeEvent& acEvent) const noexcept
{
CharacterSpawnRequest spawnMessage;
Serialize(m_world, acEvent.Entity, &spawnMessage);
NotifyRemoveCharacter removeMessage;
removeMessage.ServerId = World::ToInteger(acEvent.Entity);
for (auto pPlayer : m_world.GetPlayerManager())
{
if (acEvent.Owner == pPlayer)
continue;
if (acEvent.NewCell == pPlayer->GetCellComponent().Cell)
pPlayer->Send(spawnMessage);
else
pPlayer->Send(removeMessage);
}
}
void CharacterService::OnAssignCharacterRequest(const PacketEvent<AssignCharacterRequest>& acMessage) const noexcept
{
auto& message = acMessage.Packet;
const auto& refId = message.ReferenceId;
const auto isPlayer = (refId.ModId == 0 && refId.BaseId == 0x14);
const auto isCustom = isPlayer || refId.ModId == std::numeric_limits<uint32_t>::max();
// Check if id is the player
if (!isCustom)
{
// Look for the character
auto view = m_world.view<FormIdComponent, ActorValuesComponent, CharacterComponent, MovementComponent, CellIdComponent, OwnerComponent, InventoryComponent>();
const auto itor = std::find_if(
std::begin(view), std::end(view),
[view, refId](auto entity)
{
const auto& formIdComponent = view.get<FormIdComponent>(entity);
return formIdComponent.Id == refId;
});
if (itor != std::end(view))
{
// This entity already has an owner
spdlog::debug("FormId: {:x}:{:x} is already managed", refId.ModId, refId.BaseId);
auto& actorValuesComponent = view.get<ActorValuesComponent>(*itor);
auto& inventoryComponent = view.get<InventoryComponent>(*itor);
auto& characterComponent = view.get<CharacterComponent>(*itor);
auto& movementComponent = view.get<MovementComponent>(*itor);
auto& cellIdComponent = view.get<CellIdComponent>(*itor);
auto& ownerComponent = view.get<OwnerComponent>(*itor);
auto& partyService = m_world.GetPartyService();
bool isOwner = false;
if (partyService.IsPlayerInParty(acMessage.pPlayer) && partyService.IsPlayerLeader(acMessage.pPlayer) && !characterComponent.IsMount())
{
PartyService::Party* pParty = partyService.GetPlayerParty(acMessage.pPlayer);
Player* pOwningPlayer = view.get<OwnerComponent>(*itor).GetOwner();
// Transfer ownership if owning player is in the same party as the owner
if (std::find(pParty->Members.begin(), pParty->Members.end(), pOwningPlayer) != pParty->Members.end())
{
TransferOwnership(acMessage.pPlayer, World::ToInteger(*itor), acMessage.Packet.CurrentActorData);
isOwner = true;
}
}
AssignCharacterResponse response{};
response.Cookie = message.Cookie;
response.ServerId = World::ToInteger(*itor);
response.Owner = isOwner;
response.AllActorValues = actorValuesComponent.CurrentActorValues;
response.CurrentInventory = inventoryComponent.Content;
response.IsDead = characterComponent.IsDead();
response.IsWeaponDrawn = characterComponent.IsWeaponDrawn();
response.PlayerId = characterComponent.PlayerId;
response.Position = movementComponent.Position;
response.CellId = cellIdComponent.Cell;
response.WorldSpaceId = cellIdComponent.WorldSpaceId;
if (auto* pAnimationComponent = m_world.try_get<AnimationComponent>(*itor))
{
response.ActionsToReplay = pAnimationComponent->ActionsReplayCache.FormRefinedReplayChain();
}
acMessage.pPlayer->Send(response);
return;
}
}
// This entity has no owner create it
CreateCharacter(acMessage);
}
void CharacterService::OnOwnershipTransferRequest(const PacketEvent<RequestOwnershipTransfer>& acMessage) const noexcept
{
auto& message = acMessage.Packet;
const entt::entity cEntity = static_cast<entt::entity>(message.ServerId);
if (!m_world.valid(cEntity))
{
spdlog::warn("Client {:X} requested ownership transfer of an entity that doesn't exist, server id: {:X}", acMessage.pPlayer->GetConnectionId(), message.ServerId);
return;
}
if (auto* pCharacterComponent = m_world.try_get<CharacterComponent>(cEntity))
{
if (pCharacterComponent->IsPlayerSummon())
{
spdlog::info("Client {:X} requested ownership transfer of an orphaned summon, serverid id: {:X}", acMessage.pPlayer->GetConnectionId(), message.ServerId);
m_world.GetDispatcher().trigger(CharacterRemoveEvent(message.ServerId));
return;
}
}
if (message.WorldSpaceId || message.CellId)
{
auto& formIdComponent = m_world.get<FormIdComponent>(cEntity);
NotifyActorTeleport notify{};
notify.FormId = formIdComponent.Id;
notify.WorldSpaceId = message.WorldSpaceId;
notify.CellId = message.CellId;
notify.Position = message.Position;
auto& cellIdComponent = m_world.get<CellIdComponent>(cEntity);
cellIdComponent.WorldSpaceId = message.WorldSpaceId;
cellIdComponent.Cell = message.CellId;
cellIdComponent.CenterCoords = GridCellCoords::CalculateGridCellCoords(message.Position);
auto& movementComponent = m_world.get<MovementComponent>(cEntity);
movementComponent.Position = message.Position;
movementComponent.Sent = true;
GameServer::Get()->SendToPlayers(notify, acMessage.pPlayer);
}
auto& characterOwnerComponent = m_world.get<OwnerComponent>(cEntity);
characterOwnerComponent.InvalidOwners.push_back(acMessage.pPlayer);
m_world.GetDispatcher().trigger(OwnershipTransferEvent(cEntity));
}
void CharacterService::OnOwnershipTransferEvent(const OwnershipTransferEvent& acEvent) const noexcept
{
const auto view = m_world.view<OwnerComponent, CharacterComponent, CellIdComponent>();
auto& characterComponent = view.get<CharacterComponent>(acEvent.Entity);
auto& ownerComponent = view.get<OwnerComponent>(acEvent.Entity);
auto& cellIdComponent = view.get<CellIdComponent>(acEvent.Entity);
NotifyOwnershipTransfer response;
response.ServerId = World::ToInteger(acEvent.Entity);
bool foundOwner = false;
for (auto pPlayer : m_world.GetPlayerManager())
{
if (ownerComponent.GetOwner() == pPlayer)
continue;
bool isPlayerInvalid = false;
for (const auto invalidOwner : ownerComponent.InvalidOwners)
{
isPlayerInvalid = invalidOwner == pPlayer;
if (isPlayerInvalid)
break;
}
if (isPlayerInvalid)
continue;
if (!pPlayer->GetCellComponent().IsInRange(cellIdComponent, characterComponent.IsDragon()))
continue;
ownerComponent.SetOwner(pPlayer);
pPlayer->Send(response);
foundOwner = true;
break;
}
if (!foundOwner)
m_world.GetDispatcher().trigger(CharacterRemoveEvent(response.ServerId));
}
void CharacterService::OnCharacterRemoveEvent(const CharacterRemoveEvent& acEvent) const noexcept
{
const auto view = m_world.view<OwnerComponent>();
const auto it = view.find(static_cast<entt::entity>(acEvent.ServerId));
const auto& characterOwnerComponent = view.get<OwnerComponent>(*it);
GameServer::Get()->GetWorld().GetScriptService().HandleCharacterDestoy(*it);
NotifyRemoveCharacter response;
response.ServerId = acEvent.ServerId;
for (auto pPlayer : m_world.GetPlayerManager())
{
if (characterOwnerComponent.GetOwner() == pPlayer)
continue;
pPlayer->Send(response);
}
m_world.destroy(*it);
spdlog::debug("Character destroyed {:X}", acEvent.ServerId);
}
void CharacterService::OnOwnershipClaimRequest(const PacketEvent<RequestOwnershipClaim>& acMessage) const noexcept
{
TransferOwnership(acMessage.pPlayer, acMessage.Packet.ServerId, acMessage.Packet.NewActorData);
}
void CharacterService::OnCharacterSpawned(const CharacterSpawnedEvent& acEvent) const noexcept
{
CharacterSpawnRequest message;
Serialize(m_world, acEvent.Entity, &message);
const auto& ownerComp = m_world.get<OwnerComponent>(acEvent.Entity);
if (!GameServer::Get()->SendToPlayersInRange(message, acEvent.Entity, ownerComp.GetOwner()))
spdlog::error("{}: SendToPlayersInRange failed", __FUNCTION__);
GameServer::Get()->GetWorld().GetScriptService().HandleCharacterSpawn(acEvent.Entity);
}
void CharacterService::OnReferencesMoveRequest(const PacketEvent<ClientReferencesMoveRequest>& acMessage) const noexcept
{
OwnerView<AnimationComponent, MovementComponent, CellIdComponent> view(m_world, acMessage.GetSender());
auto& message = acMessage.Packet;
for (auto& entry : message.Updates)
{
const auto entity = static_cast<entt::entity>(entry.first);
auto itor = view.find(entity);
if (itor == std::end(view))
{
spdlog::debug("{:x} requested move of {:x} but does not exist", acMessage.pPlayer->GetConnectionId(), World::ToInteger(*itor));
continue;
}
auto& movementComponent = view.get<MovementComponent>(*itor);
auto& cellIdComponent = view.get<CellIdComponent>(*itor);
auto& animationComponent = view.get<AnimationComponent>(*itor);
movementComponent.Tick = message.Tick;
const auto movementCopy = movementComponent;
auto& update = entry.second;
auto& movement = update.UpdatedMovement;
movementComponent.Position = movement.Position;
movementComponent.Rotation = glm::vec3(movement.Rotation.x, 0.f, movement.Rotation.y);
movementComponent.Variables = movement.Variables;
movementComponent.Direction = movement.Direction;
cellIdComponent.Cell = movement.CellId;
cellIdComponent.WorldSpaceId = movement.WorldSpaceId;
cellIdComponent.CenterCoords = GridCellCoords::CalculateGridCellCoords(movement.Position.x, movement.Position.y);
for (auto& action : update.ActionEvents)
{
auto [canceled, reason] = GameServer::Get()->GetWorld().GetScriptService().HandleCharacterMove(entity);
if (canceled)
continue;
animationComponent.CurrentAction = action;
animationComponent.Actions.push_back(animationComponent.CurrentAction);
}
animationComponent.ActionsReplayCache.AppendAll(update.ActionEvents);
movementComponent.Sent = false;
}
}
void CharacterService::OnFactionsChanges(const PacketEvent<RequestFactionsChanges>& acMessage) const noexcept
{
OwnerView<CharacterComponent> view(m_world, acMessage.GetSender());
auto& message = acMessage.Packet;
for (auto& [id, factions] : message.Changes)
{
auto it = view.find(static_cast<entt::entity>(id));
if (it == std::end(view) || view.get<OwnerComponent>(*it).GetOwner() != acMessage.pPlayer)
continue;
auto& characterComponent = view.get<CharacterComponent>(*it);
characterComponent.FactionsContent = factions;
characterComponent.SetDirtyFactions(true);
}
}
void CharacterService::OnMountRequest(const PacketEvent<MountRequest>& acMessage) const noexcept
{
auto& message = acMessage.Packet;
NotifyMount notify;
notify.RiderId = message.RiderId;
notify.MountId = message.MountId;
const entt::entity cEntity = static_cast<entt::entity>(message.MountId);
if (!GameServer::Get()->SendToPlayersInRange(notify, cEntity, acMessage.GetSender()))
spdlog::error("{}: SendToPlayersInRange failed", __FUNCTION__);
}
void CharacterService::OnNewPackageRequest(const PacketEvent<NewPackageRequest>& acMessage) const noexcept
{
auto& message = acMessage.Packet;
NotifyNewPackage notify;
notify.ActorId = message.ActorId;
notify.PackageId = message.PackageId;
const entt::entity cEntity = static_cast<entt::entity>(message.ActorId);
if (!GameServer::Get()->SendToPlayersInRange(notify, cEntity, acMessage.GetSender()))
spdlog::error("{}: SendToPlayersInRange failed", __FUNCTION__);
}
void CharacterService::OnRequestRespawn(const PacketEvent<RequestRespawn>& acMessage) const noexcept
{
auto view = m_world.view<OwnerComponent, CharacterComponent>();
auto it = view.find(static_cast<entt::entity>(acMessage.Packet.ActorId));
if (it == view.end())
{
spdlog::warn("No OwnerComponent found for actor id {:X}", acMessage.Packet.ActorId);
return;
}
auto& ownerComponent = view.get<OwnerComponent>(*it);
// Replay cache needs to be cleared when a character respawns
m_world.try_get<AnimationComponent>(*it)->ActionsReplayCache.Clear();
if (ownerComponent.GetOwner() == acMessage.pPlayer)
{
if (!acMessage.Packet.AppearanceBuffer.empty())
{
auto& characterComponent = view.get<CharacterComponent>(*it);
characterComponent.SaveBuffer = acMessage.Packet.AppearanceBuffer;
characterComponent.ChangeFlags = acMessage.Packet.ChangeFlags;
}
NotifyRespawn notify;
notify.ActorId = acMessage.Packet.ActorId;
if (!GameServer::Get()->SendToPlayersInRange(notify, *it, acMessage.GetSender()))
spdlog::error("{}: SendToPlayersInRange failed", __FUNCTION__);
}
else
{
CharacterSpawnRequest message;
Serialize(m_world, *it, &message);
acMessage.GetSender()->Send(message);
}
}
void CharacterService::OnSyncExperienceRequest(const PacketEvent<SyncExperienceRequest>& acMessage) const noexcept
{
if (!bEnableXpSync)
return;
NotifySyncExperience notify;
notify.Experience = acMessage.Packet.Experience;
const auto& partyComponent = acMessage.pPlayer->GetParty();
GameServer::Get()->SendToParty(notify, partyComponent, acMessage.GetSender());
}
void CharacterService::OnDialogueRequest(const PacketEvent<DialogueRequest>& acMessage) const noexcept
{
auto& message = acMessage.Packet;
NotifyDialogue notify{};
notify.ServerId = message.ServerId;
notify.SoundFilename = message.SoundFilename;
const entt::entity cEntity = static_cast<entt::entity>(message.ServerId);
if (!GameServer::Get()->SendToPlayersInRange(notify, cEntity, acMessage.GetSender()))
spdlog::error("{}: SendToPlayersInRange failed", __FUNCTION__);
}
void CharacterService::OnSubtitleRequest(const PacketEvent<SubtitleRequest>& acMessage) const noexcept
{
auto& message = acMessage.Packet;
NotifySubtitle notify{};
notify.ServerId = message.ServerId;
notify.Text = message.Text;
const entt::entity cEntity = static_cast<entt::entity>(message.ServerId);
if (!GameServer::Get()->SendToPlayersInRange(notify, cEntity, acMessage.GetSender()))
spdlog::error("{}: SendToPlayersInRange failed", __FUNCTION__);
}
void CharacterService::CreateCharacter(const PacketEvent<AssignCharacterRequest>& acMessage) const noexcept
{
auto& message = acMessage.Packet;
const auto gameId = message.ReferenceId;
const auto baseId = message.FormId;
const auto cEntity = m_world.create();
const auto isTemporary = gameId.ModId == std::numeric_limits<uint32_t>::max();
const auto isPlayer = (gameId.ModId == 0 && gameId.BaseId == 0x14);
const auto isCustom = isPlayer || isTemporary;
// For player characters and temporary forms
if (!isCustom)
{
m_world.emplace<FormIdComponent>(cEntity, gameId.BaseId, gameId.ModId);
}
else if (baseId != GameId{} && !isTemporary)
{
m_world.destroy(cEntity);
spdlog::warn("Unexpected NpcId, player {:x} might be forging packets", acMessage.pPlayer->GetConnectionId());
return;
}
auto* const pServer = GameServer::Get();
m_world.emplace<OwnerComponent>(cEntity, acMessage.pPlayer);
auto& cellIdComponent = m_world.emplace<CellIdComponent>(cEntity, message.CellId);
if (message.WorldSpaceId != GameId{})
{
cellIdComponent.WorldSpaceId = message.WorldSpaceId;
cellIdComponent.CenterCoords = GridCellCoords::CalculateGridCellCoords(message.Position);
}
auto& characterComponent = m_world.emplace<CharacterComponent>(cEntity);
characterComponent.ChangeFlags = message.ChangeFlags;
characterComponent.SaveBuffer = std::move(message.AppearanceBuffer);
characterComponent.BaseId = FormIdComponent(message.FormId);
characterComponent.FaceTints = message.FaceTints;
characterComponent.FactionsContent = message.FactionsContent;
characterComponent.SetDead(message.CurrentActorData.IsDead);
characterComponent.SetPlayer(isPlayer);
characterComponent.SetWeaponDrawn(message.CurrentActorData.IsWeaponDrawn);
characterComponent.SetDragon(message.IsDragon);
characterComponent.SetMount(message.IsMount);
characterComponent.SetPlayerSummon(message.IsPlayerSummon);
auto& inventoryComponent = m_world.emplace<InventoryComponent>(cEntity);
inventoryComponent.Content = message.CurrentActorData.InitialInventory;
auto& actorValuesComponent = m_world.emplace<ActorValuesComponent>(cEntity);
actorValuesComponent.CurrentActorValues = message.CurrentActorData.InitialActorValues;
spdlog::debug("FormId: {:x}:{:x} - NpcId: {:x}:{:x} assigned to {:x}", gameId.ModId, gameId.BaseId, baseId.ModId, baseId.BaseId, acMessage.pPlayer->GetConnectionId());
auto& movementComponent = m_world.emplace<MovementComponent>(cEntity);
movementComponent.Tick = pServer->GetTick();
movementComponent.Position = message.Position;
movementComponent.Rotation = {message.Rotation.x, 0.f, message.Rotation.y};
movementComponent.Sent = false;
m_world.emplace<AnimationComponent>(cEntity);
// If this is a player character store a ref and trigger an event
if (isPlayer)
{
const auto pPlayer = acMessage.pPlayer;
pPlayer->SetCharacter(cEntity);
pPlayer->GetQuestLogComponent().QuestContent = message.QuestContent;
characterComponent.PlayerId = pPlayer->GetId();
auto& dispatcher = m_world.GetDispatcher();
dispatcher.trigger(PlayerEnterWorldEvent(pPlayer));
}
AssignCharacterResponse response{};
response.Cookie = message.Cookie;
response.ServerId = World::ToInteger(cEntity);
response.PlayerId = characterComponent.PlayerId;
response.Owner = true;
pServer->Send(acMessage.pPlayer->GetConnectionId(), response);
auto& dispatcher = m_world.GetDispatcher();
dispatcher.trigger(CharacterSpawnedEvent(cEntity));
}
void CharacterService::TransferOwnership(Player* apPlayer, const uint32_t acServerId,
const ActorData& acActorData) const noexcept
{
// const OwnerView<CharacterComponent, CellIdComponent> view(m_world, acMessage.GetSender());
auto view = m_world.view<OwnerComponent>();
const auto it = view.find(static_cast<entt::entity>(acServerId));
if (it == view.end())
{
spdlog::warn("Client {:X} requested ownership of an entity that doesn't exist ({:X})!", apPlayer->GetConnectionId(), acServerId);
return;
}
auto& characterOwnerComponent = view.get<OwnerComponent>(*it);
if (characterOwnerComponent.GetOwner() != apPlayer)
{
NotifyRelinquishControl notify;
notify.ServerId = acServerId;
characterOwnerComponent.pOwner->Send(notify);
}
characterOwnerComponent.SetOwner(apPlayer);
characterOwnerComponent.InvalidOwners.clear();
BroadcastActorData(apPlayer, *it, acActorData);
spdlog::debug("\tOwnership claimed {:X}", acServerId);
}
ActorData CharacterService::BuildActorData(const entt::entity acEntity) const noexcept
{
ActorData actorData{};
const auto* pActorValuesComponent = m_world.try_get<ActorValuesComponent>(acEntity);
if (pActorValuesComponent)
{
actorData.InitialActorValues = pActorValuesComponent->CurrentActorValues;
}
const auto* pInventoryComponent = m_world.try_get<InventoryComponent>(acEntity);
if (pInventoryComponent)
{
actorData.InitialInventory = pInventoryComponent->Content;
}
actorData.IsDead = false;
const auto* pCharacterComponent = m_world.try_get<CharacterComponent>(acEntity);
if (pCharacterComponent)
{
actorData.IsDead = pCharacterComponent->IsDead();
actorData.IsWeaponDrawn = pCharacterComponent->IsWeaponDrawn();
}
return actorData;
}
void CharacterService::ApplyActorData(const entt::entity acEntity, const ActorData& acActorData) const noexcept
{
auto* pActorValuesComponent = m_world.try_get<ActorValuesComponent>(acEntity);
if (pActorValuesComponent)
{
pActorValuesComponent->CurrentActorValues = acActorData.InitialActorValues;
}
auto* pInventoryComponent = m_world.try_get<InventoryComponent>(acEntity);
if (pInventoryComponent)
{
pInventoryComponent->Content = acActorData.InitialInventory;
}
auto* pCharacterComponent = m_world.try_get<CharacterComponent>(acEntity);
if (pCharacterComponent)
{
pCharacterComponent->SetDead(acActorData.IsDead);
pCharacterComponent->SetWeaponDrawn(acActorData.IsWeaponDrawn);
}
}
void CharacterService::BroadcastActorData(Player* apPlayer, const entt::entity acEntity,
const ActorData& acActorData) const noexcept
{
ApplyActorData(acEntity, acActorData);
NotifySpawnData notifySpawnData;
notifySpawnData.Id = World::ToInteger(acEntity);
notifySpawnData.NewActorData = acActorData;
GameServer::Get()->SendToPlayersInRange(notifySpawnData, acEntity, apPlayer);
}
void CharacterService::ProcessFactionsChanges() const noexcept
{
static std::chrono::steady_clock::time_point lastSendTimePoint;
constexpr auto cDelayBetweenSnapshots = 2000ms;
const auto now = std::chrono::steady_clock::now();
if (now - lastSendTimePoint < cDelayBetweenSnapshots)
return;
lastSendTimePoint = now;
const auto characterView = m_world.view<CellIdComponent, CharacterComponent, OwnerComponent>();
TiltedPhoques::Map<Player*, NotifyFactionsChanges> messages;
for (auto entity : characterView)
{
auto& characterComponent = characterView.get<CharacterComponent>(entity);
auto& cellIdComponent = characterView.get<CellIdComponent>(entity);
auto& ownerComponent = characterView.get<OwnerComponent>(entity);
// If we have nothing new to send skip this
if (!characterComponent.IsDirtyFactions())
continue;
for (auto pPlayer : m_world.GetPlayerManager())
{
if (pPlayer == ownerComponent.GetOwner())
continue;
if (!cellIdComponent.IsInRange(pPlayer->GetCellComponent(), characterComponent.IsDragon()))
continue;
auto& message = messages[pPlayer];
auto& change = message.Changes[World::ToInteger(entity)];
change = characterComponent.FactionsContent;
}
characterComponent.SetDirtyFactions(false);
}
for (auto [pPlayer, message] : messages)
{
if (!message.Changes.empty())
pPlayer->Send(message);
}
}
void CharacterService::ProcessMovementChanges() const noexcept
{
static std::chrono::steady_clock::time_point lastSendTimePoint;
constexpr auto cDelayBetweenSnapshots = 1000ms / 50;
const auto now = std::chrono::steady_clock::now();
if (now - lastSendTimePoint < cDelayBetweenSnapshots)
return;
lastSendTimePoint = now;
const auto characterView = m_world.view<CharacterComponent, CellIdComponent, MovementComponent, AnimationComponent, OwnerComponent>();
TiltedPhoques::Map<Player*, ServerReferencesMoveRequest> messages;
for (auto pPlayer : m_world.GetPlayerManager())
{
auto& message = messages[pPlayer];
message.Tick = GameServer::Get()->GetTick();
}
for (auto entity : characterView)
{
auto& characterComponent = characterView.get<CharacterComponent>(entity);
auto& movementComponent = characterView.get<MovementComponent>(entity);
auto& cellIdComponent = characterView.get<CellIdComponent>(entity);
auto& ownerComponent = characterView.get<OwnerComponent>(entity);
auto& animationComponent = characterView.get<AnimationComponent>(entity);
// If we have nothing new to send skip this
if (movementComponent.Sent == true)
continue;
for (auto pPlayer : m_world.GetPlayerManager())
{
if (pPlayer == ownerComponent.GetOwner())
continue;
if (!cellIdComponent.IsInRange(pPlayer->GetCellComponent(), characterComponent.IsDragon()))
continue;
auto& message = messages[pPlayer];
auto& update = message.Updates[World::ToInteger(entity)];
auto& movement = update.UpdatedMovement;
movement.Position = movementComponent.Position;
movement.Rotation.x = movementComponent.Rotation.x;
movement.Rotation.y = movementComponent.Rotation.z;
movement.Direction = movementComponent.Direction;
movement.Variables = movementComponent.Variables;
update.ActionEvents = animationComponent.Actions;
}
}
m_world.view<AnimationComponent>().each([](AnimationComponent& animationComponent)
{
// Remove actions we've sent
animationComponent.Actions.clear();
});
m_world.view<MovementComponent>().each([](MovementComponent& movementComponent) { movementComponent.Sent = true; });
for (auto& [pPlayer, message] : messages)
{
if (!message.Updates.empty())
pPlayer->Send(message);
}
}