#include #include #include #include #include #include #include #include #include #include WeatherService::WeatherService(World& aWorld, TransportService& aTransport, entt::dispatcher& aDispatcher) : m_world(aWorld) , m_transport(aTransport) { m_updateConnection = aDispatcher.sink().connect<&WeatherService::OnUpdate>(this); m_disconnectConnection = aDispatcher.sink().connect<&WeatherService::OnDisconnected>(this); m_partyJoinedConnection = aDispatcher.sink().connect<&WeatherService::OnPartyJoinedEvent>(this); m_partyLeftConnection = aDispatcher.sink().connect<&WeatherService::OnPartyLeftEvent>(this); m_playerAddedConnection = m_world.on_destroy().connect<&WeatherService::OnWaitingFor3DRemoved>(this); m_playerRemovedConnection = m_world.on_destroy().connect<&WeatherService::OnPlayerComponentRemoved>(this); m_weatherChangeConnection = aDispatcher.sink().connect<&WeatherService::OnWeatherChange>(this); } void WeatherService::OnUpdate(const UpdateEvent& acEvent) noexcept { RunWeatherUpdates(acEvent.Delta); } void WeatherService::OnDisconnected(const DisconnectedEvent& acEvent) noexcept { ToggleGameWeatherSystem(true); } void WeatherService::OnPartyJoinedEvent(const PartyJoinedEvent& acEvent) noexcept { if (!acEvent.IsLeader) { // TODO: why is this loop here? Party should always have a leader. auto view = m_world.view(); const auto& partyService = m_world.GetPartyService(); for (auto entity : view) { const auto& playerComponent = view.get(entity); if (playerComponent.Id == partyService.GetLeaderPlayerId()) { ToggleGameWeatherSystem(false); break; } } } else { Sky* pSky = Sky::Get(); if (!pSky) return; TESWeather* pWeather = pSky->GetWeather(); if (!pWeather) { m_cachedWeatherId = 0; return; } // Potentially sets cached weather to map weather. // When the player closes the map, it'll send out the proper weather on the next update. m_cachedWeatherId = pWeather->formID; // This is the map weather, should not be synced. if (pWeather->formID == 0xA6858) return; RequestWeatherChange request{}; auto& modSystem = m_world.GetModSystem(); if (!modSystem.GetServerModId(pWeather->formID, request.Id)) { spdlog::error(__FUNCTION__ ": weather server ID not found, form id: {:X}", pWeather->formID); return; } m_transport.Send(request); } } void WeatherService::OnPartyLeftEvent(const PartyLeftEvent& acEvent) noexcept { ToggleGameWeatherSystem(true); } // TODO: OnPlayerComponentAdded() instead? Does PlayerComponent exist already by then? void WeatherService::OnWaitingFor3DRemoved(entt::registry& aRegistry, entt::entity aEntity) noexcept { const auto* pPlayerComponent = m_world.try_get(aEntity); if (!pPlayerComponent) return; const auto& partyService = m_world.GetPartyService(); if (!partyService.IsInParty() || partyService.IsLeader()) return; if (partyService.GetLeaderPlayerId() == pPlayerComponent->Id) ToggleGameWeatherSystem(false); } void WeatherService::OnPlayerComponentRemoved(entt::registry& aRegistry, entt::entity aEntity) noexcept { const auto& playerComponent = m_world.get(aEntity); const auto& partyService = m_world.GetPartyService(); if (!partyService.IsInParty() || partyService.IsLeader()) return; if (partyService.GetLeaderPlayerId() == playerComponent.Id) ToggleGameWeatherSystem(true); } void WeatherService::OnWeatherChange(const NotifyWeatherChange& acMessage) noexcept { auto& modSystem = m_world.GetModSystem(); const uint32_t weatherId = modSystem.GetGameId(acMessage.Id); TESWeather* pWeather = Cast(TESForm::GetById(weatherId)); if (!pWeather) { spdlog::error(__FUNCTION__ ": weather not found, form id: {:X}", acMessage.Id.ModId + acMessage.Id.BaseId); return; } Sky::Get()->ForceWeather(pWeather); m_cachedWeatherId = weatherId; } void WeatherService::RunWeatherUpdates(const double acDelta) noexcept { Sky* pSky = Sky::Get(); if (!pSky) return; TESWeather* pWeather = pSky->GetWeather(); if (!pWeather) { if (m_world.GetPartyService().IsLeader()) m_cachedWeatherId = 0; else SetCachedWeather(); return; } // This is the map weather, should not be synced. if (pWeather->formID == 0xA6858) return; // Have to manually check each frame because there's no singular SetWeather being used in-game. if (pWeather->formID == m_cachedWeatherId) return; if (m_world.GetPartyService().IsLeader()) { m_cachedWeatherId = pWeather->formID; RequestWeatherChange request{}; auto& modSystem = m_world.GetModSystem(); if (!modSystem.GetServerModId(pWeather->formID, request.Id)) { spdlog::error(__FUNCTION__ ": weather server ID not found, form id: {:X}", pWeather->formID); return; } m_transport.Send(request); } else { SetCachedWeather(); } } void WeatherService::ToggleGameWeatherSystem(bool aToggle) noexcept { if (aToggle) Sky::Get()->ReleaseWeatherOverride(); else m_transport.Send(RequestCurrentWeather()); m_cachedWeatherId = 0; } void WeatherService::SetCachedWeather() noexcept { if (m_cachedWeatherId == 0) return; TESWeather* pWeather = Cast(TESForm::GetById(m_cachedWeatherId)); if (!pWeather) { spdlog::error(__FUNCTION__ ": weather not found, form id: {:X}", m_cachedWeatherId); return; } Sky::Get()->ForceWeather(pWeather); }