Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Code/client/Components.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <Components/FaceGenComponent.h>
#include <Components/CacheComponent.h>
#include <Components/WaitingFor3D.h>
#include <Components/PendingInventoryComponent.h>
#include <Components/ActorValuesComponent.h>
#include <Components/ObjectComponent.h>
#include <Components/PlayerComponent.h>
Expand Down
22 changes: 22 additions & 0 deletions Code/client/Components/PendingInventoryComponent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#ifndef TP_INTERNAL_COMPONENTS_GUARD
#error Include Components.h instead
#endif

#include <Structs/Inventory.h>
#include <Messages/NotifyInventoryChanges.h>
#include <Messages/NotifyEquipmentChanges.h>
#include <variant>

struct PendingInventoryComponent
{
PendingInventoryComponent(const Inventory& acInventory, uint32_t aOwnershipEpoch)
: Content(acInventory), OwnershipEpoch(aOwnershipEpoch)
{
}

Inventory Content;
uint32_t OwnershipEpoch;
Vector<std::variant<NotifyInventoryChanges, NotifyEquipmentChanges>> Updates;
};
2 changes: 2 additions & 0 deletions Code/client/Components/RemoteComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ struct RemoteComponent
uint32_t Id;
uint32_t CachedRefId;
uint32_t OwnershipEpoch;
// Canonical death state survives loading and prevents living animation replay on corpses.
bool IsDead{false};
};
1 change: 1 addition & 0 deletions Code/client/Games/Skyrim/AI/AIProcess.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ struct HighProcessData
};

static_assert(offsetof(AIProcess, movementType) == 0x137);
static_assert(offsetof(AIProcess, middleProcess) == 0x8);
1 change: 1 addition & 0 deletions Code/client/Services/CharacterService.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ struct CharacterService
void RunSpawnUpdates() const noexcept;
void RunExperienceUpdates() noexcept;
void ApplyCachedWeaponDraws(const UpdateEvent& acUpdateEvent) noexcept;
void ApplyPendingInventories() noexcept;

World& m_world;
entt::dispatcher& m_dispatcher;
Expand Down
34 changes: 26 additions & 8 deletions Code/client/Services/Generic/ActorValueService.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include <TiltedOnlinePCH.h>

#include <Systems/RemoteDeathSystem.h>

#include <Services/ActorValueService.h>
#include <World.h>
#include <Forms/ActorValueInfo.h>
Expand Down Expand Up @@ -295,6 +297,21 @@ void ActorValueService::OnHealthChangeBroadcast(const NotifyHealthChangeBroadcas
}

const float newHealth = pActor->GetActorValue(ActorValueInfo::kHealth) + acMessage.DeltaHealth;
if (!pActor->GetExtension()->IsPlayer() && pActor->GetExtension()->IsRemote() && !pActor->IsDead() && newHealth <= 0.f)
{
const auto entity = Utils::FindEntityByServerId(acMessage.Id);
if (entity && m_world.all_of<RemoteComponent>(*entity))
{
m_world.get<RemoteComponent>(*entity).IsDead = true;
if (auto* pWaiting = m_world.try_get<WaitingFor3D>(*entity))
pWaiting->SpawnRequest.IsDead = true;
// Do not let zero health commit death before the physical body is ready.
pActor->ForceActorValue(ActorValueOwner::ForceMode::DAMAGE, ActorValueInfo::kHealth, 1.f);
if (!m_world.any_of<WaitingFor3D>(*entity))
RemoteDeathSystem::Apply(pActor, true);
return;
}
}
pActor->ForceActorValue(ActorValueOwner::ForceMode::DAMAGE, ActorValueInfo::kHealth, newHealth);

const float health = pActor->GetActorValue(ActorValueInfo::kHealth);
Expand Down Expand Up @@ -381,7 +398,7 @@ void ActorValueService::OnActorMaxValueChanges(const NotifyActorMaxValueChanges&

void ActorValueService::OnDeathStateChange(const NotifyDeathStateChange& acMessage) const noexcept
{
auto view = m_world.view<FormIdComponent, RemoteComponent>();
auto view = m_world.view<RemoteComponent>();

const auto it = std::find_if(std::begin(view), std::end(view), [&acMessage, view](entt::entity entity)
{
Expand All @@ -398,8 +415,12 @@ void ActorValueService::OnDeathStateChange(const NotifyDeathStateChange& acMessa
return;
}

auto& formIdComponent = view.get<FormIdComponent>(*it);
Actor* pActor = Cast<Actor>(TESForm::GetById(formIdComponent.Id));
view.get<RemoteComponent>(*it).IsDead = acMessage.IsDead;
if (auto* pWaiting = m_world.try_get<WaitingFor3D>(*it))
pWaiting->SpawnRequest.IsDead = acMessage.IsDead;

const auto* pFormId = m_world.try_get<FormIdComponent>(*it);
Actor* pActor = pFormId ? Cast<Actor>(TESForm::GetById(pFormId->Id)) : nullptr;

if (!pActor)
return;
Expand All @@ -409,9 +430,6 @@ void ActorValueService::OnDeathStateChange(const NotifyDeathStateChange& acMessa
if (pExtension->IsPlayer())
return;

if (pActor->IsDead() != acMessage.IsDead)
{
spdlog::info("Applying death state {} to remote actor {:X}, server id: {:X}", acMessage.IsDead, pActor->formID, acMessage.Id);
acMessage.IsDead ? pActor->Kill() : pActor->Respawn();
}
if (!m_world.any_of<WaitingFor3D>(*it))
RemoteDeathSystem::Apply(pActor, acMessage.IsDead);
}
Loading