#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ActorValueService::ActorValueService(World& aWorld, entt::dispatcher& aDispatcher, TransportService& aTransport) noexcept : m_world(aWorld) , m_dispatcher(aDispatcher) , m_transport(aTransport) { m_world.on_construct().connect<&ActorValueService::OnLocalComponentAdded>(this); m_dispatcher.sink().connect<&ActorValueService::OnDisconnected>(this); m_dispatcher.sink().connect<&ActorValueService::OnActorRemoved>(this); m_dispatcher.sink().connect<&ActorValueService::OnUpdate>(this); m_dispatcher.sink().connect<&ActorValueService::OnActorValueChanges>(this); m_dispatcher.sink().connect<&ActorValueService::OnActorMaxValueChanges>(this); m_dispatcher.sink().connect<&ActorValueService::OnHealthChange>(this); m_dispatcher.sink().connect<&ActorValueService::OnHealthChangeBroadcast>(this); m_dispatcher.sink().connect<&ActorValueService::OnDeathStateChange>(this); } void ActorValueService::CreateActorValuesComponent(const entt::entity aEntity, Actor* apActor) noexcept { auto& actorValuesComponent = m_world.emplace_or_replace(aEntity); for (int i = 0; i < ActorValueInfo::kActorValueCount; i++) { float value = apActor->GetActorValue(i); actorValuesComponent.CurrentActorValues.ActorValuesList.insert({i, value}); float maxValue = apActor->GetActorPermanentValue(i); actorValuesComponent.CurrentActorValues.ActorMaxValuesList.insert({i, maxValue}); } } void ActorValueService::OnLocalComponentAdded(entt::registry& aRegistry, const entt::entity aEntity) noexcept { const auto& formIdComponent = aRegistry.get(aEntity); Actor* pActor = Cast(TESForm::GetById(formIdComponent.Id)); if (pActor != NULL) { auto& localComponent = aRegistry.get(aEntity); localComponent.IsDead = pActor->IsDead(); localComponent.IsWeaponDrawn = pActor->actorState.IsWeaponDrawn(); CreateActorValuesComponent(aEntity, pActor); } } void ActorValueService::OnDisconnected(const DisconnectedEvent& acEvent) noexcept { // TODO: this crashes sometimes, no clue why m_world.clear(); } void ActorValueService::OnActorRemoved(const ActorRemovedEvent& acEvent) noexcept { if (!m_transport.IsConnected()) return; auto view = m_world.view(); const uint32_t formId = acEvent.FormId; const auto it = std::find_if( std::begin(view), std::end(view), [view, formId](auto entity) { const auto& formIdComponent = view.get(entity); return formIdComponent.Id == formId; }); if (it != std::end(view)) m_world.remove(*it); } void ActorValueService::OnUpdate(const UpdateEvent& acEvent) noexcept { RunSmallHealthUpdates(); RunDeathStateUpdates(); RunActorValuesUpdates(); } void ActorValueService::BroadcastActorValues() noexcept { if (!m_transport.IsConnected()) return; auto view = m_world.view(); for (auto entity : view) { auto& formIdComponent = view.get(entity); auto* pForm = TESForm::GetById(formIdComponent.Id); auto* pActor = Cast(pForm); if (!pActor) continue; auto& localComponent = view.get(entity); auto& actorValuesComponent = view.get(entity); RequestActorValueChanges requestValueChanges; requestValueChanges.Id = localComponent.Id; RequestActorMaxValueChanges requestMaxValueChanges; requestMaxValueChanges.Id = localComponent.Id; bool isPlayer = pActor->GetExtension() && pActor->GetExtension()->IsPlayer(); for (int i = 0; i < ActorValueInfo::kActorValueCount; i++) { if (isPlayer && i == ActorValueInfo::kDragonSouls) continue; float newValue = pActor->GetActorValue(i); float oldValue = actorValuesComponent.CurrentActorValues.ActorValuesList[i]; if (newValue != oldValue) { requestValueChanges.Values.insert({i, newValue}); actorValuesComponent.CurrentActorValues.ActorValuesList[i] = newValue; } float newMaxValue = pActor->GetActorPermanentValue(i); float oldMaxValue = actorValuesComponent.CurrentActorValues.ActorMaxValuesList[i]; if (newMaxValue != oldMaxValue) { requestMaxValueChanges.Values.insert({i, newMaxValue}); actorValuesComponent.CurrentActorValues.ActorMaxValuesList[i] = newMaxValue; } } if (requestValueChanges.Values.size() > 0) { m_transport.Send(requestValueChanges); } if (requestMaxValueChanges.Values.size() > 0) { m_transport.Send(requestMaxValueChanges); } } } void ActorValueService::OnHealthChange(const HealthChangeEvent& acEvent) noexcept { if (!m_transport.IsConnected()) return; auto view = m_world.view(); const auto hitteeIt = std::find_if(std::begin(view), std::end(view), [id = acEvent.HitteeId, view](entt::entity entity) { return view.get(entity).Id == id; }); if (hitteeIt == std::end(view)) { spdlog::warn("Health change event form id component not found, form id: {:X}", acEvent.HitteeId); return; } std::optional serverIdRes = Utils::GetServerId(*hitteeIt); if (!serverIdRes.has_value()) { spdlog::error("{}: failed to find server id", __FUNCTION__); return; } uint32_t serverId = serverIdRes.value(); if (acEvent.DeltaHealth > -1.0f && acEvent.DeltaHealth < 1.0f) { if (m_smallHealthChanges.find(serverId) == m_smallHealthChanges.end()) m_smallHealthChanges[serverId] = acEvent.DeltaHealth; else m_smallHealthChanges[serverId] += acEvent.DeltaHealth; return; } RequestHealthChangeBroadcast requestHealthChange; requestHealthChange.Id = serverId; requestHealthChange.DeltaHealth = acEvent.DeltaHealth; m_transport.Send(requestHealthChange); spdlog::debug("Sent out delta health through collection: {:X}:{:f}", serverId, acEvent.DeltaHealth); } void ActorValueService::RunSmallHealthUpdates() noexcept { static std::chrono::steady_clock::time_point lastSendTimePoint; constexpr auto cDelayBetweenUpdates = 250ms; const auto now = std::chrono::steady_clock::now(); if (now - lastSendTimePoint < cDelayBetweenUpdates) return; lastSendTimePoint = now; if (!m_smallHealthChanges.empty()) { for (auto& value : m_smallHealthChanges) { RequestHealthChangeBroadcast requestHealthChange; requestHealthChange.Id = value.first; requestHealthChange.DeltaHealth = value.second; m_transport.Send(requestHealthChange); spdlog::debug("Sent out delta health through timer, {:X}:{:f}", value.first, value.second); } m_smallHealthChanges.clear(); } } void ActorValueService::RunDeathStateUpdates() noexcept { static std::chrono::steady_clock::time_point lastSendTimePoint; constexpr auto cDelayBetweenUpdates = 250ms; const auto now = std::chrono::steady_clock::now(); if (now - lastSendTimePoint < cDelayBetweenUpdates) return; lastSendTimePoint = now; auto localView = m_world.view(); for (auto entity : localView) { const auto& formIdComponent = localView.get(entity); Actor* const pActor = Cast(TESForm::GetById(formIdComponent.Id)); if (!pActor) continue; auto& localComponent = localView.get(entity); bool isDead = pActor->IsDead(); if (isDead != localComponent.IsDead) { localComponent.IsDead = isDead; RequestDeathStateChange requestChange; requestChange.Id = localComponent.Id; requestChange.IsDead = isDead; m_transport.Send(requestChange); } } } void ActorValueService::RunActorValuesUpdates() noexcept { static std::chrono::steady_clock::time_point lastSendTimePoint; constexpr auto cDelayBetweenUpdates = 1000ms; const auto now = std::chrono::steady_clock::now(); if (now - lastSendTimePoint < cDelayBetweenUpdates) return; lastSendTimePoint = now; BroadcastActorValues(); } void ActorValueService::OnHealthChangeBroadcast(const NotifyHealthChangeBroadcast& acMessage) const noexcept { Actor* pActor = Utils::GetByServerId(acMessage.Id); if (!pActor) { spdlog::error("{}: could not find actor server id {:X}", __FUNCTION__, acMessage.Id); return; } const float newHealth = pActor->GetActorValue(ActorValueInfo::kHealth) + acMessage.DeltaHealth; pActor->ForceActorValue(ActorValueOwner::ForceMode::DAMAGE, ActorValueInfo::kHealth, newHealth); const float health = pActor->GetActorValue(ActorValueInfo::kHealth); if (!pActor->IsDead() && health <= 0.f) { ActorExtension* pExtension = pActor->GetExtension(); // Players should never be killed if (!pExtension->IsPlayer()) pActor->Kill(); } // TODO(cosideci): find fix for player health sync so this can be used again /* if (pActor->GetExtension()->IsRemotePlayer()) World::Get().GetOverlayService().SetPlayerHealthPercentage(pActor->formID); */ } void ActorValueService::OnActorValueChanges(const NotifyActorValueChanges& acMessage) const noexcept { auto view = m_world.view(); const auto itor = std::find_if(std::begin(view), std::end(view), [id = acMessage.Id, view](entt::entity entity) { return view.get(entity).Id == id; }); if (itor == std::end(view)) return; auto& formIdComponent = view.get(*itor); Actor* const pActor = Cast(TESForm::GetById(formIdComponent.Id)); if (!pActor) return; for (const auto& [key, value] : acMessage.Values) { // Syncing dragon souls triggers "Dragon soul collected" event if (key == ActorValueInfo::kDragonSouls || key == ActorValueInfo::kHealth) continue; spdlog::debug("Actor value update, server ID: {:X}, key: {}, value: {}", acMessage.Id, key, value); if (key == ActorValueInfo::kStamina || key == ActorValueInfo::kMagicka || key == ActorValueInfo::kHealth) { pActor->ForceActorValue(ActorValueOwner::ForceMode::DAMAGE, key, value); continue; } pActor->SetActorValue(key, value); } } void ActorValueService::OnActorMaxValueChanges(const NotifyActorMaxValueChanges& acMessage) const noexcept { auto view = m_world.view(); const auto it = std::find_if(std::begin(view), std::end(view), [id = acMessage.Id, view](entt::entity entity) { return view.get(entity).Id == id; }); if (it == std::end(view)) return; auto& formIdComponent = view.get(*it); Actor* pActor = Cast(TESForm::GetById(formIdComponent.Id)); if (!pActor) return; for (const auto& [key, value] : acMessage.Values) { if (key == ActorValueInfo::kDragonSouls) continue; spdlog::debug("Actor max value update, server ID: {:X}, key: {}, value: {}", acMessage.Id, key, value); pActor->ForceActorValue(ActorValueOwner::ForceMode::PERMANENT, key, value); } } void ActorValueService::OnDeathStateChange(const NotifyDeathStateChange& acMessage) const noexcept { auto view = m_world.view(); const auto it = std::find_if(std::begin(view), std::end(view), [id = acMessage.Id, view](entt::entity entity) { return view.get(entity).Id == id; }); if (it == std::end(view)) return; auto& formIdComponent = view.get(*it); Actor* pActor = Cast(TESForm::GetById(formIdComponent.Id)); if (!pActor) return; ActorExtension* pExtension = pActor->GetExtension(); // Players should never be killed if (pExtension->IsPlayer()) return; if (pActor->IsDead() != acMessage.IsDead) acMessage.IsDead ? pActor->Kill() : pActor->Respawn(); }