#include "ZoneMessages.h" #include "MovementMessages.h" #include "BitStreamUtils.h" #include "CDClientManager.h" #include "Character.h" #include "CharacterComponent.h" #include "CppScripts.h" #include "DestroyableComponent.h" #include "EffectsMessages.h" #include "Entity.h" #include "EntityManager.h" #include "Game.h" #include "GhostComponent.h" #include "InventoryComponent.h" #include "InventoryMessages.h" #include "LevelProgressionComponent.h" #include "RacingControlComponent.h" #include "WorldMigration.h" #include "BrickByBrick.h" #include "PropertyManagementComponent.h" #include "dConfig.h" #include "dZoneManager.h" #include "eReplicaComponentType.h" #include "eTriggerEventType.h" #include "ePlayerFlag.h" #include namespace GameMessages { void PlayerLoaded::Serialize(RakNet::BitStream& bitStream) const { bitStream.Write(playerID); } bool PlayerLoaded::Deserialize(RakNet::BitStream& bitStream) { return bitStream.Read(playerID); } void SendDoneLoading(const LWOOBJID player, const NiPoint3& savedCheckpoint, const NiPoint3& spawnPosition, const NiQuaternion& spawnRotation, const SystemAddress& sysAddr) { ServerDoneLoadingAllObjects doneLoading; doneLoading.target = player; doneLoading.SendToClient(sysAddr); PlayerReachedRespawnCheckpoint checkpoint; checkpoint.target = player; const bool hasSavedCheckpoint = savedCheckpoint != NiPoint3Constant::ZERO; checkpoint.pos = hasSavedCheckpoint ? savedCheckpoint : spawnPosition; checkpoint.rot = hasSavedCheckpoint ? QuatUtils::IDENTITY : spawnRotation; checkpoint.SendToClient(sysAddr); } void SendPlayerReady(const LWOOBJID player, const LWOOBJID zoneControl, const SystemAddress& sysAddr) { PlayerReady playerReady; playerReady.target = player; playerReady.SendToClient(sysAddr); if (zoneControl == LWOOBJID_EMPTY) return; playerReady.target = zoneControl; playerReady.SendToClient(sysAddr); } void PlayerLoaded::Handle(Entity& entityRef, const SystemAddress& sysAddr) { auto* entity = &entityRef; const auto* const zoneControlEntity = Game::entityManager->GetZoneControlEntity(); SendPlayerReady(entity->GetObjectID(), zoneControlEntity ? zoneControlEntity->GetObjectID() : LWOOBJID_EMPTY, sysAddr); entity->SetPlayerReadyForUpdates(); auto* ghostComponent = entity->GetComponent(); if (ghostComponent != nullptr) { ghostComponent->ConstructLimboEntities(); } InventoryComponent* inv = entity->GetComponent(); if (inv) { // Clear server-side skill state so AddItemSkills sends fresh AddSkill // packets to the now-ready client. Skills sent during entity construction // (Serialize) arrive before LWOSkillComponent is initialized and are dropped. inv->ClearSkills(); auto items = inv->GetEquippedItems(); for (auto pair : items) { const auto item = pair.second; inv->AddItemSkills(item.lot); } // Fixes a bug where testmapping too fast causes large item inventories to become invisible. // Only affects item inventory GameMessages::SetInventorySize setSize; setSize.target = entity->GetObjectID(); setSize.inventoryType = eInventoryType::ITEMS; setSize.size = inv->GetInventory(eInventoryType::ITEMS)->GetSize(); setSize.SendToClient(entity->GetSystemAddress()); } RestoreToPostLoadStats restoreStats; restoreStats.target = entity->GetObjectID(); restoreStats.SendToClient(sysAddr); auto* const destroyable = entity->GetComponent(); if (destroyable) destroyable->SetImagination(destroyable->GetImagination()); Game::entityManager->SerializeEntity(entity); std::vector racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); for (Entity* racingController : racingControllers) { auto* racingComponent = racingController->GetComponent(); if (racingComponent != nullptr) { racingComponent->OnPlayerLoaded(entity); } } Entity* zoneControl = Game::entityManager->GetZoneControlEntity(); if (zoneControl) { zoneControl->GetScript()->OnPlayerLoaded(zoneControl, entity); } std::vector scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPT); for (Entity* scriptEntity : scriptedActs) { if (!zoneControl || scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds scriptEntity->GetScript()->OnPlayerLoaded(scriptEntity, entity); } } //Kill player if health == 0 if (entity->GetIsDead()) { entity->Smash(entity->GetObjectID()); } //if the player has moved significantly, move them back: if ((entity->GetPosition().y - entity->GetCharacter()->GetOriginalPos().y) > 2.0f) { // Disabled until fixed //GameMessages::Teleport(entity->GetObjectID(), entity->GetCharacter()->GetOriginalPos(), entity->GetCharacter()->GetOriginalRot(), true).Send(entity->GetSystemAddress()); } /** * Invoke the OnZoneLoad event on the player character */ auto* character = entity->GetCharacter(); if (character != nullptr) { character->OnZoneLoad(); } // Moved here from another instance: put back what their save doesn't keep (the pet that was out) WorldMigration::OnPlayerLoaded(entity); // A brick by brick build that ended without a save (disconnect, crash): rebuild the autosave or give back the // models that were open BrickByBrick::OnPlayerLoaded(*entity); // On a property: whether they are a best friend of the owner, who may build (and the owner arriving lets them) if (auto* property = PropertyManagementComponent::Instance()) property->OnPlayerLoaded(*entity); LOG("Player %s (%llu) loaded.", entity->GetCharacter()->GetName().c_str(), entity->GetObjectID()); // After we've done our thing, tell the client they're ready PlayerReady zoneReady; zoneReady.target = Game::zoneManager->GetZoneControlObject()->GetObjectID(); zoneReady.SendToClient(sysAddr); if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1" || !entity->GetCharacter() || !entity->GetCharacter()->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS)) return; entity->AddCallbackTimer(0.5f, [entity, sysAddr]() { if (!entity) return; GameMessages::EndCinematic endCinematic; endCinematic.target = entity->GetObjectID(); endCinematic.Send(sysAddr); }); } void ReadyForUpdates::Serialize(RakNet::BitStream& bitStream) const { bitStream.Write(objectID); } bool ReadyForUpdates::Deserialize(RakNet::BitStream& bitStream) { return bitStream.Read(objectID); } void InvalidZoneTransferList::Serialize(RakNet::BitStream& bitStream) const { BitStreamUtils::WriteLengthPrefixed(bitStream, customerFeedbackURL); BitStreamUtils::WriteLengthPrefixed(bitStream, invalidMapTransferList); bitStream.Write(bCustomerFeedbackOnExit); bitStream.Write(bCustomerFeedbackOnInvalidMapTransfer); } bool InvalidZoneTransferList::Deserialize(RakNet::BitStream& bitStream) { VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, customerFeedbackURL)); VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, invalidMapTransferList)); VALIDATE_READ(bitStream.Read(bCustomerFeedbackOnExit)); VALIDATE_READ(bitStream.Read(bCustomerFeedbackOnInvalidMapTransfer)); return true; } void DisplayZoneSummary::Serialize(RakNet::BitStream& bitStream) const { bitStream.Write(isPropertyMap); bitStream.Write(isZoneStart); BitStreamUtils::WriteOptional(bitStream, sender, LWOOBJID_EMPTY); } bool DisplayZoneSummary::Deserialize(RakNet::BitStream& bitStream) { VALIDATE_READ(bitStream.Read(isPropertyMap)); VALIDATE_READ(bitStream.Read(isZoneStart)); VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, sender, LWOOBJID_EMPTY)); return true; } void ZoneSummaryDismissed::Serialize(RakNet::BitStream& bitStream) const { bitStream.Write(playerID); } bool ZoneSummaryDismissed::Deserialize(RakNet::BitStream& bitStream) { return bitStream.Read(playerID); } void ZoneSummaryDismissed::Handle(Entity& entity, const SystemAddress& sysAddr) { auto* player = Game::entityManager->GetEntity(playerID); entity.TriggerEvent(eTriggerEventType::ZONE_SUMMARY_DISMISSED, player); } void NotifyServerLevelProcessingComplete::Handle(Entity& entity, const SystemAddress& sysAddr) { auto* levelComp = entity.GetComponent(); if (!levelComp) return; auto* character = entity.GetComponent(); if (!character) return; //Update our character's level in memory: levelComp->SetLevel(levelComp->GetLevel() + 1); levelComp->HandleLevelUp(); auto* inventoryComponent = entity.GetComponent(); if (inventoryComponent != nullptr) { auto* inventory = inventoryComponent->GetInventory(ITEMS); if (inventory != nullptr && Game::config->GetValue("disable_extra_backpack") != "1") { inventory->SetSize(inventory->GetSize() + 2); } } //Play the level up effect: GameMessages::PlayFXEffect(entity.GetObjectID(), 7074, u"create", "7074").Send(UNASSIGNED_SYSTEM_ADDRESS); //Send a notification in chat: std::stringstream wss; wss << "level=1:"; wss << levelComp->GetLevel(); wss << "\n"; wss << "name=0:"; wss << character->GetName(); // FIXME: only really need utf8 conversion for the name, so move that up? std::u16string attrs = GeneralUtils::UTF8ToUTF16(wss.str()); std::u16string wsText = u"UI_LEVEL_PROGRESSION_LEVELUP_MESSAGE"; GameMessages::BroadcastTextToChatbox chatboxText; chatboxText.target = entity.GetObjectID(); chatboxText.attrs = attrs; chatboxText.wsText = wsText; chatboxText.Send(UNASSIGNED_SYSTEM_ADDRESS); } void ChangeObjectWorldState::Serialize(RakNet::BitStream& bitStream) const { BitStreamUtils::WriteOptional(bitStream, newState, eObjectWorldState::INWORLD); } bool ChangeObjectWorldState::Deserialize(RakNet::BitStream& bitStream) { VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, newState, eObjectWorldState::INWORLD)); return true; } void LocalizedAnnouncementServerToSingleClient::Serialize(RakNet::BitStream& bitStream) const { BitStreamUtils::WriteNameValueText(bitStream, bodyParams); bitStream.Write(bForceOpenChatBox); bitStream.Write(bShowAnnouncement); bitStream.Write(bShowInChat); BitStreamUtils::WriteLengthPrefixed(bitStream, body); BitStreamUtils::WriteLengthPrefixed(bitStream, title); BitStreamUtils::WriteNameValueText(bitStream, titleParams); } bool LocalizedAnnouncementServerToSingleClient::Deserialize(RakNet::BitStream& bitStream) { VALIDATE_READ(BitStreamUtils::ReadNameValueText(bitStream, bodyParams)); VALIDATE_READ(bitStream.Read(bForceOpenChatBox)); VALIDATE_READ(bitStream.Read(bShowAnnouncement)); VALIDATE_READ(bitStream.Read(bShowInChat)); VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, body)); VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, title)); VALIDATE_READ(BitStreamUtils::ReadNameValueText(bitStream, titleParams)); return true; } }