#include "Events/CharacterInteriorCellChangeEvent.h" #include "Events/CharacterExteriorCellChangeEvent.h" #include "Events/PlayerLeaveCellEvent.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { Console::Setting fGoldLossFactor{"Gameplay:fGoldLossFactor", "Factor of the amount of gold lost on death", 0.0f}; } PlayerService::PlayerService(World& aWorld, entt::dispatcher& aDispatcher) noexcept : m_world(aWorld) , m_interiorCellEnterConnection(aDispatcher.sink>().connect<&PlayerService::HandleInteriorCellEnter>(this)) , m_gridCellShiftConnection(aDispatcher.sink>().connect<&PlayerService::HandleGridCellShift>(this)) , m_exteriorCellEnterConnection(aDispatcher.sink>().connect<&PlayerService::HandleExteriorCellEnter>(this)) , m_playerRespawnConnection(aDispatcher.sink>().connect<&PlayerService::OnPlayerRespawnRequest>(this)) , m_playerLevelConnection(aDispatcher.sink>().connect<&PlayerService::OnPlayerLevelRequest>(this)) { } void SendPlayerCellChanged(const Player* apPlayer) noexcept { auto& cellComponent = apPlayer->GetCellComponent(); NotifyPlayerCellChanged notify{}; notify.PlayerId = apPlayer->GetId(); notify.WorldSpaceId = cellComponent.WorldSpaceId; notify.CellId = cellComponent.Cell; GameServer::Get()->SendToPlayers(notify, apPlayer); } void PlayerService::HandleGridCellShift(const PacketEvent& acMessage) const noexcept { auto* pPlayer = acMessage.pPlayer; auto& message = acMessage.Packet; const GameId oldCell = pPlayer->GetCellComponent().Cell; CellIdComponent cell = CellIdComponent{message.PlayerCell, message.WorldSpaceId, message.CenterCoords}; pPlayer->SetCellComponent(cell); m_world.GetDispatcher().trigger(PlayerLeaveCellEvent(oldCell)); auto characterView = m_world.view(); for (auto character : characterView) { const auto& ownedComponent = characterView.get(character); const auto& characterCellComponent = characterView.get(character); if (ownedComponent.GetOwner() == pPlayer) continue; const auto cellIt = std::find_if(std::begin(message.Cells), std::end(message.Cells), [Cells = message.Cells, CharacterCell = characterCellComponent.Cell](auto playerCell) { return playerCell == CharacterCell; }); if (cellIt == std::end(message.Cells)) { continue; } CharacterSpawnRequest spawnMessage; CharacterService::Serialize(m_world, character, &spawnMessage); pPlayer->Send(spawnMessage); } } void PlayerService::HandleExteriorCellEnter(const PacketEvent& acMessage) const noexcept { auto& message = acMessage.Packet; auto* pPlayer = acMessage.pPlayer; if (pPlayer->GetCharacter()) { auto entity = *pPlayer->GetCharacter(); auto cell = CellIdComponent{message.CellId, message.WorldSpaceId, message.CurrentCoords}; if (pPlayer->GetCellComponent()) { m_world.GetDispatcher().trigger(CharacterExteriorCellChangeEvent{pPlayer, entity, message.WorldSpaceId, message.CurrentCoords}); } pPlayer->SetCellComponent(cell); SendPlayerCellChanged(pPlayer); } } void PlayerService::HandleInteriorCellEnter(const PacketEvent& acMessage) const noexcept { auto* pPlayer = acMessage.pPlayer; auto& message = acMessage.Packet; const auto oldCell = pPlayer->GetCellComponent().Cell; auto cell = CellIdComponent{message.CellId, {}, {}}; pPlayer->SetCellComponent(cell); m_world.GetDispatcher().trigger(PlayerLeaveCellEvent(oldCell)); if (pPlayer->GetCharacter()) { auto entity = *pPlayer->GetCharacter(); if (auto pCellIdComponent = m_world.try_get(entity); pCellIdComponent) { m_world.GetDispatcher().trigger(CharacterInteriorCellChangeEvent{pPlayer, entity, message.CellId}); } } auto characterView = m_world.view(); for (auto character : characterView) { const auto& ownedComponent = characterView.get(character); if (ownedComponent.GetOwner() == pPlayer) continue; if (message.CellId != characterView.get(character).Cell) continue; CharacterSpawnRequest spawnMessage; CharacterService::Serialize(m_world, character, &spawnMessage); pPlayer->Send(spawnMessage); } SendPlayerCellChanged(pPlayer); } void PlayerService::OnPlayerRespawnRequest(const PacketEvent& acMessage) const noexcept { float goldLossFactor = fGoldLossFactor.as_float(); auto character = acMessage.pPlayer->GetCharacter(); if (!character) return; auto view = m_world.view(); const auto it = view.find(static_cast(*character)); if (it != view.end()) { if (goldLossFactor != 0.0) { auto& inventoryComponent = view.get(*it); GameId goldId(0, 0xF); int32_t goldCount = inventoryComponent.Content.GetEntryCountById(goldId); int32_t goldToRemove = static_cast(goldCount * goldLossFactor); Inventory::Entry entry{}; entry.BaseId = goldId; entry.Count = -goldToRemove; inventoryComponent.Content.AddOrRemoveEntry(entry); NotifyInventoryChanges notifyInventoryChanges{}; notifyInventoryChanges.ServerId = World::ToInteger(*character); notifyInventoryChanges.Item = entry; notifyInventoryChanges.Drop = false; // Exclude respawned player from inventory changes notification... if (!GameServer::Get()->SendToPlayersInRange(notifyInventoryChanges, *character, acMessage.GetSender())) spdlog::error("{}: SendToPlayersInRange failed", __FUNCTION__); // ...and instead, send NotifyPlayerRespawn so that the client can print a message. NotifyPlayerRespawn notifyPlayerRespawn{}; notifyPlayerRespawn.GoldLost = goldToRemove; acMessage.pPlayer->Send(notifyPlayerRespawn); } // Let all other players in cell respawn this player, since the body state seems to be bugged otherwise NotifyRespawn notifyRespawn{}; notifyRespawn.ActorId = World::ToInteger(*character); if (!GameServer::Get()->SendToPlayersInRange(notifyRespawn, *character, acMessage.GetSender())) spdlog::error("{}: SendToPlayersInRange failed", __FUNCTION__); } } void PlayerService::OnPlayerLevelRequest(const PacketEvent& acMessage) const noexcept { acMessage.pPlayer->SetLevel(acMessage.Packet.NewLevel); NotifyPlayerLevel notify{}; notify.PlayerId = acMessage.pPlayer->GetId(); notify.NewLevel = acMessage.Packet.NewLevel; GameServer::Get()->SendToPlayers(notify, acMessage.pPlayer); }