#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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().connect<&CharacterService::OnUpdate>(this)) , m_interiorCellChangeEventConnection(aDispatcher.sink().connect<&CharacterService::OnCharacterInteriorCellChange>(this)) , m_exteriorCellChangeEventConnection(aDispatcher.sink().connect<&CharacterService::OnCharacterExteriorCellChange>(this)) , m_characterAssignRequestConnection(aDispatcher.sink>().connect<&CharacterService::OnAssignCharacterRequest>(this)) , m_transferOwnershipConnection(aDispatcher.sink>().connect<&CharacterService::OnOwnershipTransferRequest>(this)) , m_ownershipTransferEventConnection(aDispatcher.sink().connect<&CharacterService::OnOwnershipTransferEvent>(this)) , m_claimOwnershipConnection(aDispatcher.sink>().connect<&CharacterService::OnOwnershipClaimRequest>(this)) , m_removeCharacterConnection(aDispatcher.sink().connect<&CharacterService::OnCharacterRemoveEvent>(this)) , m_characterSpawnedConnection(aDispatcher.sink().connect<&CharacterService::OnCharacterSpawned>(this)) , m_referenceMovementSnapshotConnection(aDispatcher.sink>().connect<&CharacterService::OnReferencesMoveRequest>(this)) , m_factionsChangesConnection(aDispatcher.sink>().connect<&CharacterService::OnFactionsChanges>(this)) , m_mountConnection(aDispatcher.sink>().connect<&CharacterService::OnMountRequest>(this)) , m_newPackageConnection(aDispatcher.sink>().connect<&CharacterService::OnNewPackageRequest>(this)) , m_requestRespawnConnection(aDispatcher.sink>().connect<&CharacterService::OnRequestRespawn>(this)) , m_syncExperienceConnection(aDispatcher.sink>().connect<&CharacterService::OnSyncExperienceRequest>(this)) , m_dialogueConnection(aDispatcher.sink>().connect<&CharacterService::OnDialogueRequest>(this)) , m_subtitleConnection(aDispatcher.sink>().connect<&CharacterService::OnSubtitleRequest>(this)) { } void CharacterService::Serialize(World& aRegistry, entt::entity aEntity, CharacterSpawnRequest* apSpawnRequest) noexcept { const auto& characterComponent = aRegistry.get(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(aEntity); if (pFormIdComponent) { apSpawnRequest->FormId = pFormIdComponent->Id; } const auto* pInventoryComponent = aRegistry.try_get(aEntity); if (pInventoryComponent) { apSpawnRequest->InventoryContent = pInventoryComponent->Content; } const auto* pActorValuesComponent = aRegistry.try_get(aEntity); if (pActorValuesComponent) { apSpawnRequest->InitialActorValues = pActorValuesComponent->CurrentActorValues; } if (characterComponent.BaseId) { apSpawnRequest->BaseId = characterComponent.BaseId.Id; } const auto* pMovementComponent = aRegistry.try_get(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(aEntity); if (pCellIdComponent) { apSpawnRequest->CellId = pCellIdComponent->Cell; } auto& animationComponent = aRegistry.get(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& 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::max(); // Check if id is the player if (!isCustom) { // Look for the character auto view = m_world.view(); const auto itor = std::find_if( std::begin(view), std::end(view), [view, refId](auto entity) { const auto& formIdComponent = view.get(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(*itor); auto& inventoryComponent = view.get(*itor); auto& characterComponent = view.get(*itor); auto& movementComponent = view.get(*itor); auto& cellIdComponent = view.get(*itor); auto& ownerComponent = view.get(*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(*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(*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& acMessage) const noexcept { auto& message = acMessage.Packet; const entt::entity cEntity = static_cast(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(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(cEntity); NotifyActorTeleport notify{}; notify.FormId = formIdComponent.Id; notify.WorldSpaceId = message.WorldSpaceId; notify.CellId = message.CellId; notify.Position = message.Position; auto& cellIdComponent = m_world.get(cEntity); cellIdComponent.WorldSpaceId = message.WorldSpaceId; cellIdComponent.Cell = message.CellId; cellIdComponent.CenterCoords = GridCellCoords::CalculateGridCellCoords(message.Position); auto& movementComponent = m_world.get(cEntity); movementComponent.Position = message.Position; movementComponent.Sent = true; GameServer::Get()->SendToPlayers(notify, acMessage.pPlayer); } auto& characterOwnerComponent = m_world.get(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(); auto& characterComponent = view.get(acEvent.Entity); auto& ownerComponent = view.get(acEvent.Entity); auto& cellIdComponent = view.get(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(); const auto it = view.find(static_cast(acEvent.ServerId)); const auto& characterOwnerComponent = view.get(*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& 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(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& acMessage) const noexcept { OwnerView view(m_world, acMessage.GetSender()); auto& message = acMessage.Packet; for (auto& entry : message.Updates) { const auto entity = static_cast(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(*itor); auto& cellIdComponent = view.get(*itor); auto& animationComponent = view.get(*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& acMessage) const noexcept { OwnerView view(m_world, acMessage.GetSender()); auto& message = acMessage.Packet; for (auto& [id, factions] : message.Changes) { auto it = view.find(static_cast(id)); if (it == std::end(view) || view.get(*it).GetOwner() != acMessage.pPlayer) continue; auto& characterComponent = view.get(*it); characterComponent.FactionsContent = factions; characterComponent.SetDirtyFactions(true); } } void CharacterService::OnMountRequest(const PacketEvent& acMessage) const noexcept { auto& message = acMessage.Packet; NotifyMount notify; notify.RiderId = message.RiderId; notify.MountId = message.MountId; const entt::entity cEntity = static_cast(message.MountId); if (!GameServer::Get()->SendToPlayersInRange(notify, cEntity, acMessage.GetSender())) spdlog::error("{}: SendToPlayersInRange failed", __FUNCTION__); } void CharacterService::OnNewPackageRequest(const PacketEvent& acMessage) const noexcept { auto& message = acMessage.Packet; NotifyNewPackage notify; notify.ActorId = message.ActorId; notify.PackageId = message.PackageId; const entt::entity cEntity = static_cast(message.ActorId); if (!GameServer::Get()->SendToPlayersInRange(notify, cEntity, acMessage.GetSender())) spdlog::error("{}: SendToPlayersInRange failed", __FUNCTION__); } void CharacterService::OnRequestRespawn(const PacketEvent& acMessage) const noexcept { auto view = m_world.view(); auto it = view.find(static_cast(acMessage.Packet.ActorId)); if (it == view.end()) { spdlog::warn("No OwnerComponent found for actor id {:X}", acMessage.Packet.ActorId); return; } auto& ownerComponent = view.get(*it); // Replay cache needs to be cleared when a character respawns m_world.try_get(*it)->ActionsReplayCache.Clear(); if (ownerComponent.GetOwner() == acMessage.pPlayer) { if (!acMessage.Packet.AppearanceBuffer.empty()) { auto& characterComponent = view.get(*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& 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& acMessage) const noexcept { auto& message = acMessage.Packet; NotifyDialogue notify{}; notify.ServerId = message.ServerId; notify.SoundFilename = message.SoundFilename; const entt::entity cEntity = static_cast(message.ServerId); if (!GameServer::Get()->SendToPlayersInRange(notify, cEntity, acMessage.GetSender())) spdlog::error("{}: SendToPlayersInRange failed", __FUNCTION__); } void CharacterService::OnSubtitleRequest(const PacketEvent& acMessage) const noexcept { auto& message = acMessage.Packet; NotifySubtitle notify{}; notify.ServerId = message.ServerId; notify.Text = message.Text; const entt::entity cEntity = static_cast(message.ServerId); if (!GameServer::Get()->SendToPlayersInRange(notify, cEntity, acMessage.GetSender())) spdlog::error("{}: SendToPlayersInRange failed", __FUNCTION__); } void CharacterService::CreateCharacter(const PacketEvent& 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::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(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(cEntity, acMessage.pPlayer); auto& cellIdComponent = m_world.emplace(cEntity, message.CellId); if (message.WorldSpaceId != GameId{}) { cellIdComponent.WorldSpaceId = message.WorldSpaceId; cellIdComponent.CenterCoords = GridCellCoords::CalculateGridCellCoords(message.Position); } auto& characterComponent = m_world.emplace(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(cEntity); inventoryComponent.Content = message.CurrentActorData.InitialInventory; auto& actorValuesComponent = m_world.emplace(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(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(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 view(m_world, acMessage.GetSender()); auto view = m_world.view(); const auto it = view.find(static_cast(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(*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(acEntity); if (pActorValuesComponent) { actorData.InitialActorValues = pActorValuesComponent->CurrentActorValues; } const auto* pInventoryComponent = m_world.try_get(acEntity); if (pInventoryComponent) { actorData.InitialInventory = pInventoryComponent->Content; } actorData.IsDead = false; const auto* pCharacterComponent = m_world.try_get(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(acEntity); if (pActorValuesComponent) { pActorValuesComponent->CurrentActorValues = acActorData.InitialActorValues; } auto* pInventoryComponent = m_world.try_get(acEntity); if (pInventoryComponent) { pInventoryComponent->Content = acActorData.InitialInventory; } auto* pCharacterComponent = m_world.try_get(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(); TiltedPhoques::Map messages; for (auto entity : characterView) { auto& characterComponent = characterView.get(entity); auto& cellIdComponent = characterView.get(entity); auto& ownerComponent = characterView.get(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(); TiltedPhoques::Map messages; for (auto pPlayer : m_world.GetPlayerManager()) { auto& message = messages[pPlayer]; message.Tick = GameServer::Get()->GetTick(); } for (auto entity : characterView) { auto& characterComponent = characterView.get(entity); auto& movementComponent = characterView.get(entity); auto& cellIdComponent = characterView.get(entity); auto& ownerComponent = characterView.get(entity); auto& animationComponent = characterView.get(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().each([](AnimationComponent& animationComponent) { // Remove actions we've sent animationComponent.Actions.clear(); }); m_world.view().each([](MovementComponent& movementComponent) { movementComponent.Sent = true; }); for (auto& [pPlayer, message] : messages) { if (!message.Updates.empty()) pPlayer->Send(message); } }