diff --git a/Code/client/Components/RemoteComponent.h b/Code/client/Components/RemoteComponent.h index a1dd83fc7..b18f2eebc 100644 --- a/Code/client/Components/RemoteComponent.h +++ b/Code/client/Components/RemoteComponent.h @@ -15,7 +15,4 @@ struct RemoteComponent uint32_t Id; uint32_t CachedRefId; CharacterSpawnRequest SpawnRequest{}; - // TODO: why is IsDead here (and by extension, IsWeaponDrawn)? - bool IsDead = false; - bool IsWeaponDrawn = false; }; diff --git a/Code/client/Events/InventoryChangeEvent.h b/Code/client/Events/InventoryChangeEvent.h index 49b0ed784..65c536a76 100644 --- a/Code/client/Events/InventoryChangeEvent.h +++ b/Code/client/Events/InventoryChangeEvent.h @@ -10,12 +10,12 @@ struct InventoryChangeEvent { } - InventoryChangeEvent(const uint32_t aFormId, Inventory::Entry&& arItem, bool aDropOrPickUp) - : FormId(aFormId), Item(std::move(arItem)), DropOrPickUp(aDropOrPickUp) + InventoryChangeEvent(const uint32_t aFormId, Inventory::Entry&& arItem, bool aDrop) + : FormId(aFormId), Item(std::move(arItem)), Drop(aDrop) { } uint32_t FormId{}; Inventory::Entry Item{}; - bool DropOrPickUp = false; + bool Drop = false; }; diff --git a/Code/client/Games/ExtraDataList.cpp b/Code/client/Games/ExtraDataList.cpp index 1e622b509..5ab7e4496 100644 --- a/Code/client/Games/ExtraDataList.cpp +++ b/Code/client/Games/ExtraDataList.cpp @@ -1,5 +1,7 @@ #include "ExtraDataList.h" +#include + ExtraDataList* ExtraDataList::New() noexcept { ExtraDataList* pExtraDataList = Memory::Allocate(); @@ -27,10 +29,15 @@ bool ExtraDataList::Contains(ExtraData aType) const BSExtraData* ExtraDataList::GetByType(ExtraData aType) const { - BSScopedLock _(lock); + if (!ScopedExtraDataOverride::IsOverriden()) + lock.Lock(); if (!Contains(aType)) + { + if (!ScopedExtraDataOverride::IsOverriden()) + lock.Unlock(); return nullptr; + } auto pEntry = data; #if TP_SKYRIM @@ -42,6 +49,9 @@ BSExtraData* ExtraDataList::GetByType(ExtraData aType) const pEntry = pEntry->next; } + if (!ScopedExtraDataOverride::IsOverriden()) + lock.Unlock(); + return pEntry; } diff --git a/Code/client/Games/Fallout4/Actor.h b/Code/client/Games/Fallout4/Actor.h index 0b1733707..bb6e5af38 100644 --- a/Code/client/Games/Fallout4/Actor.h +++ b/Code/client/Games/Fallout4/Actor.h @@ -102,7 +102,7 @@ struct Actor : TESObjectREFR Factions GetFactions() const noexcept; ActorValues GetEssentialActorValues() noexcept; float GetActorValue(uint32_t aId) const noexcept; - float GetActorMaxValue(uint32_t aId) const noexcept; + float GetActorPermanentValue(uint32_t aId) const noexcept; void* GetCurrentWeapon(void* apResult, uint32_t aEquipIndex) noexcept; // Setters diff --git a/Code/client/Games/Overrides.cpp b/Code/client/Games/Overrides.cpp index 85c9eca8e..3292aafcc 100644 --- a/Code/client/Games/Overrides.cpp +++ b/Code/client/Games/Overrides.cpp @@ -8,3 +8,4 @@ thread_local uint32_t ScopedSaveLoadOverride::s_refCount = 0; thread_local uint32_t ScopedExperienceOverride::s_refCount = 0; thread_local uint32_t ScopedActivateOverride::s_refCount = 0; thread_local uint32_t ScopedInventoryOverride::s_refCount = 0; +thread_local uint32_t ScopedExtraDataOverride::s_refCount = 0; diff --git a/Code/client/Games/Overrides.h b/Code/client/Games/Overrides.h index f77280862..3626b2162 100644 --- a/Code/client/Games/Overrides.h +++ b/Code/client/Games/Overrides.h @@ -33,6 +33,7 @@ namespace details struct Experience {}; struct Activate {}; struct Inventory {}; + struct ExtraData {}; } using ScopedEquipOverride = ScopedOverride; @@ -41,3 +42,4 @@ using ScopedSaveLoadOverride = ScopedOverride; using ScopedExperienceOverride = ScopedOverride; using ScopedActivateOverride = ScopedOverride; using ScopedInventoryOverride = ScopedOverride; +using ScopedExtraDataOverride = ScopedOverride; diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index 49d9d858d..77d6af243 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -242,7 +242,7 @@ ActorValues Actor::GetEssentialActorValues() const noexcept { float value = actorValueOwner.GetValue(i); actorValues.ActorValuesList.insert({i, value}); - float maxValue = actorValueOwner.GetMaxValue(i); + float maxValue = actorValueOwner.GetPermanentValue(i); actorValues.ActorMaxValuesList.insert({i, maxValue}); } @@ -254,9 +254,9 @@ float Actor::GetActorValue(uint32_t aId) const noexcept return actorValueOwner.GetValue(aId); } -float Actor::GetActorMaxValue(uint32_t aId) const noexcept +float Actor::GetActorPermanentValue(uint32_t aId) const noexcept { - return actorValueOwner.GetMaxValue(aId); + return actorValueOwner.GetPermanentValue(aId); } void Actor::SetActorValue(uint32_t aId, float aValue) noexcept @@ -264,7 +264,7 @@ void Actor::SetActorValue(uint32_t aId, float aValue) noexcept actorValueOwner.SetValue(aId, aValue); } -void Actor::ForceActorValue(uint32_t aMode, uint32_t aId, float aValue) noexcept +void Actor::ForceActorValue(ActorValueOwner::ForceMode aMode, uint32_t aId, float aValue) noexcept { const float current = GetActorValue(aId); actorValueOwner.ForceCurrent(aMode, aId, aValue - current); @@ -336,13 +336,13 @@ void Actor::SetActorValues(const ActorValues& acActorValues) noexcept for (auto& value : acActorValues.ActorMaxValuesList) { float current = actorValueOwner.GetValue(value.first); - actorValueOwner.ForceCurrent(0, value.first, value.second - current); + actorValueOwner.ForceCurrent(ActorValueOwner::ForceMode::PERMANENT, value.first, value.second - current); } for (auto& value : acActorValues.ActorValuesList) { float current = actorValueOwner.GetValue(value.first); - actorValueOwner.ForceCurrent(2, value.first, value.second - current); + actorValueOwner.ForceCurrent(ActorValueOwner::ForceMode::DAMAGE, value.first, value.second - current); } } diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index 7ca6ab55c..671018839 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -189,7 +189,7 @@ struct Actor : TESObjectREFR // in reality this is a BGSLocation TESForm *GetCurrentLocation(); float GetActorValue(uint32_t aId) const noexcept; - float GetActorMaxValue(uint32_t aId) const noexcept; + float GetActorPermanentValue(uint32_t aId) const noexcept; Inventory GetActorInventory() const noexcept; MagicEquipment GetMagicEquipment() const noexcept; @@ -200,7 +200,7 @@ struct Actor : TESObjectREFR void SetSpeed(float aSpeed) noexcept; void SetLevelMod(uint32_t aLevel) noexcept; void SetActorValue(uint32_t aId, float aValue) noexcept; - void ForceActorValue(uint32_t aMode, uint32_t aId, float aValue) noexcept; + void ForceActorValue(ActorValueOwner::ForceMode aMode, uint32_t aId, float aValue) noexcept; void SetActorValues(const ActorValues& acActorValues) noexcept; void SetFactions(const Factions& acFactions) noexcept; void SetFactionRank(const TESFaction* apFaction, int8_t aRank) noexcept; diff --git a/Code/client/Games/Skyrim/EquipManager.cpp b/Code/client/Games/Skyrim/EquipManager.cpp index e27a4f234..c7bd59f93 100644 --- a/Code/client/Games/Skyrim/EquipManager.cpp +++ b/Code/client/Games/Skyrim/EquipManager.cpp @@ -74,6 +74,8 @@ void* EquipManager::Equip(Actor* apActor, TESForm* apItem, ExtraDataList* apExtr ScopedEquipOverride equipOverride; + spdlog::debug("Call Actor[{:X}]::Equip(), item id: {:X}, extra data? {}, count: {}", apActor->formID, apItem->formID, (bool)apExtraDataList, aCount); + return ThisCall(s_equipFunc, this, apActor, apItem, apExtraDataList, aCount, apSlot, abQueueEquip, abForceEquip, abPlaySound, abApplyNow); } @@ -84,6 +86,8 @@ void* EquipManager::UnEquip(Actor* apActor, TESForm* apItem, ExtraDataList* apEx ScopedEquipOverride equipOverride; + spdlog::debug("Call Actor[{:X}]::UnEquip(), item id: {:X}, extra data? {}, count: {}", apActor->formID, apItem->formID, (bool)apExtraDataList, aCount); + return ThisCall(s_unequipFunc, this, apActor, apItem, apExtraDataList, aCount, apSlot, abQueueEquip, abForceEquip, abPlaySound, abApplyNow, apSlotToReplace); } @@ -165,7 +169,7 @@ void* TP_MAKE_THISCALL(UnEquipHook, EquipManager, Actor* apActor, TESForm* apIte const auto pExtension = apActor->GetExtension(); if (pExtension->IsRemote()) { - spdlog::info("Actor[{:X}]::Unequip(), item form id: {:X}, IsOverridden: {}", apActor->formID, apItem->formID, ScopedEquipOverride::IsOverriden()); + spdlog::info("Actor[{:X}]::Unequip(), item form id: {:X}, IsOverridden, equip: {}, inventory: {}", apActor->formID, apItem->formID, ScopedEquipOverride::IsOverriden(), ScopedInventoryOverride::IsOverriden()); // The ScopedInventoryOverride check is here to allow the item to be unequipped if it is removed // Without this check, the game will not accept null as a return, and it'll keep trying to unequip infinitely if (!ScopedEquipOverride::IsOverriden() && !ScopedInventoryOverride::IsOverriden()) diff --git a/Code/client/Games/Skyrim/Interface/UI.cpp b/Code/client/Games/Skyrim/Interface/UI.cpp index 7bc61e6e2..ecf0452ce 100644 --- a/Code/client/Games/Skyrim/Interface/UI.cpp +++ b/Code/client/Games/Skyrim/Interface/UI.cpp @@ -78,6 +78,7 @@ static void UnfreezeMenu(IMenu* apEntry) static constexpr const char* kAllowList[] = { "Console", "TweenMenu", "MagicMenu", "StatsMenu", "InventoryMenu", "MessageBoxMenu", + "ContainerMenu", "BarterMenu", "Book Menu", "FavoritesMenu" //"MapMenu", // MapMenu is disabled till we find a proper fix for first person. //"Journal Menu", // Journal menu, aka pause menu, is disabled until we find a fix for manual save crashing while unpaused. }; diff --git a/Code/client/Games/Skyrim/Misc/ActorValueOwner.h b/Code/client/Games/Skyrim/Misc/ActorValueOwner.h index d484c3b91..64aa722f4 100644 --- a/Code/client/Games/Skyrim/Misc/ActorValueOwner.h +++ b/Code/client/Games/Skyrim/Misc/ActorValueOwner.h @@ -2,14 +2,22 @@ struct ActorValueOwner { + enum class ForceMode : uint32_t + { + PERMANENT = 0, + TEMPORARY = 1, + DAMAGE = 2, + COUNT = 3, + }; + virtual ~ActorValueOwner(); virtual float GetValue(uint32_t aId) const noexcept; - virtual float GetMaxValue(uint32_t aId) const noexcept; - virtual float GetBaseValue(uint32_t aId); - virtual void sub_4(); - virtual void sub_5(); - virtual void ForceCurrent(uint32_t aMode, uint32_t aId, float aValue); // Pass 0 for aUnk1 in force, 2 in restore + virtual float GetPermanentValue(uint32_t aId) const noexcept; + virtual float GetBaseValue(uint32_t aId) const noexcept; + virtual void SetBaseValue(uint32_t aId); + virtual void ModValue(uint32_t aId, float aValue); + virtual void ForceCurrent(ForceMode aMode, uint32_t aId, float aValue); virtual void SetValue(uint32_t aId, float aValue) noexcept; - virtual void sub_8(); + virtual bool IsPlayerOwner(); }; diff --git a/Code/client/Games/Skyrim/PlayerCharacter.cpp b/Code/client/Games/Skyrim/PlayerCharacter.cpp index f802733ad..6cc0496a9 100644 --- a/Code/client/Games/Skyrim/PlayerCharacter.cpp +++ b/Code/client/Games/Skyrim/PlayerCharacter.cpp @@ -54,7 +54,7 @@ char TP_MAKE_THISCALL(HookPickUpObject, PlayerCharacter, TESObjectREFR* apObject if (apObject->GetExtraDataList()) apThis->GetItemFromExtraData(item, apObject->GetExtraDataList()); - World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID, std::move(item), true)); + World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID, std::move(item))); } ScopedInventoryOverride _; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 5e917c005..88e1b0ff4 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -505,6 +505,7 @@ void TESObjectREFR::AddOrRemoveItem(const Inventory::Entry& arEntry) noexcept isWornLeft = pExtraDataList->Contains(ExtraData::WornLeft); } + spdlog::debug("Adding item {:X}, count {}", pObject->formID, arEntry.Count); AddObjectToContainer(pObject, pExtraDataList, arEntry.Count, nullptr); if (isWorn) @@ -628,15 +629,20 @@ BSPointerHandle* TP_MAKE_THISCALL(HookRemoveInventoryItem, TESObj Inventory::Entry item{}; modSystem.GetServerModId(apItem->formID, item.BaseId); item.Count = -aCount; - + if (apExtraList) + { + ScopedExtraDataOverride _; apThis->GetItemFromExtraData(item, apExtraList); + } World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID, std::move(item))); } spdlog::debug("Removing inventory item {:X} from {:X}", apItem->formID, apThis->formID); + ScopedEquipOverride _; + return ThisCall(RealRemoveInventoryItem, apThis, apResult, apItem, aCount, aReason, apExtraList, apMoveToRef, apDropLoc, apRotate); } diff --git a/Code/client/Services/ActorValueService.h b/Code/client/Services/ActorValueService.h index 8561fe700..460e80769 100644 --- a/Code/client/Services/ActorValueService.h +++ b/Code/client/Services/ActorValueService.h @@ -21,7 +21,7 @@ struct Actor; struct ActorValueService { - public: +public: ActorValueService(World& aWorld, entt::dispatcher& aDispatcher, TransportService& aTransport) noexcept; ~ActorValueService() noexcept = default; @@ -34,13 +34,6 @@ struct ActorValueService kMaxValue }; - World& m_world; - entt::dispatcher& m_dispatcher; - TransportService& m_transport; - - Map m_smallHealthChanges; - double m_timeSinceDiff = 1; - void OnLocalComponentAdded(entt::registry& aRegistry, entt::entity aEntity) noexcept; void OnDisconnected(const DisconnectedEvent&) noexcept; void OnReferenceRemoved(const ReferenceRemovedEvent&) noexcept; @@ -56,4 +49,11 @@ struct ActorValueService void RunDeathStateUpdates() noexcept; void CreateActorValuesComponent(entt::entity aEntity, Actor* apActor) noexcept; void BroadcastActorValues() noexcept; + + World& m_world; + entt::dispatcher& m_dispatcher; + TransportService& m_transport; + + Map m_smallHealthChanges; + double m_timeSinceDiff = 1; }; diff --git a/Code/client/Services/Debug/TestService.cpp b/Code/client/Services/Debug/TestService.cpp index dc0b9d56d..1a2a96c18 100644 --- a/Code/client/Services/Debug/TestService.cpp +++ b/Code/client/Services/Debug/TestService.cpp @@ -40,6 +40,9 @@ #include +#include +#include + #if TP_SKYRIM64 #include #include @@ -134,8 +137,6 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept if (!s_f8Pressed) { s_f8Pressed = true; - - PlaceActorInWorld(); } } else @@ -162,6 +163,7 @@ static bool g_enableFormsWindow{false}; static bool g_enablePlayerWindow{false}; static bool g_enableSkillsWindow{false}; static bool g_enablePartyWindow{false}; +static bool g_enableActorValuesWindow{false}; void TestService::OnDraw() noexcept { @@ -169,6 +171,8 @@ void TestService::OnDraw() noexcept if (view.empty() || !m_showDebugStuff) return; + DrawEntitiesView(); + ImGui::BeginMainMenuBar(); if (ImGui::BeginMenu("Server")) { @@ -196,6 +200,16 @@ void TestService::OnDraw() noexcept if (ImGui::BeginMenu("UI")) { ImGui::MenuItem("Show build tag", nullptr, &m_showBuildTag); + if (ImGui::Button("Log all open windows")) + { + UI* pUI = UI::Get(); + for (const auto& it : pUI->menuMap) + { + if (pUI->GetMenuOpen(it.key)) + spdlog::info("{}", it.key.AsAscii()); + } + } + if (ImGui::Button("Close all menus")) { UI::Get()->CloseAllMenus(); @@ -231,6 +245,8 @@ void TestService::OnDraw() noexcept DrawSkillView(); if (g_enablePartyWindow) DrawPartyView(); + if (g_enableActorValuesWindow) + DrawActorValuesView(); if (m_toggleComponentWindow) DrawComponentDebugView(); diff --git a/Code/client/Services/Debug/Views/ActorValuesView.cpp b/Code/client/Services/Debug/Views/ActorValuesView.cpp new file mode 100644 index 000000000..5ea5b4f8b --- /dev/null +++ b/Code/client/Services/Debug/Views/ActorValuesView.cpp @@ -0,0 +1,20 @@ +#include + +#include + +#include + +void TestService::DrawActorValuesView() +{ + Actor* pActor = RTTI_CAST(TESForm::GetById(m_formId), TESForm, Actor); + if (!pActor) + return; + + //for (int i = 0; i < ActorValueInfo::kActorValueCount; i++) + { + ActorValueOwner& actorValueOwner = pActor->actorValueOwner; + float health[3] {actorValueOwner.GetValue(24), actorValueOwner.GetBaseValue(24), + actorValueOwner.GetPermanentValue(24)}; + ImGui::InputFloat3("Health (val/base/perm)", health, "%.3f", ImGuiInputTextFlags_ReadOnly); + } +} diff --git a/Code/client/Services/Debug/Views/EntitiesView.cpp b/Code/client/Services/Debug/Views/EntitiesView.cpp new file mode 100644 index 000000000..87b540443 --- /dev/null +++ b/Code/client/Services/Debug/Views/EntitiesView.cpp @@ -0,0 +1,168 @@ +#include + +#include +#include + +void TestService::DrawEntitiesView() +{ + const auto view = m_world.view(); + if (view.empty()) + return; + + ImGui::SetNextWindowSize(ImVec2(250, 440), ImGuiCond_FirstUseEver); + ImGui::Begin("Entities"); + + if (ImGui::BeginTabBar("##Tabs", ImGuiTabBarFlags_None)) + { + if (ImGui::BeginTabItem("Actors")) + { + DisplayEntities(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Objects")) + { + DisplayObjects(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + + ImGui::End(); +} + +void TestService::DisplayEntities() noexcept +{ + static uint32_t s_selected = 0; + + StackAllocator<1 << 12> allocator; + ScopedAllocator _{ allocator }; + + const auto view = m_world.view(entt::exclude); + + Vector entities(view.begin(), view.end()); + + ImGui::Text("Actor list (%d)", entities.size()); + + ImGui::BeginChild("Entities", ImVec2(0, 200), true); + + int i = 0; + for(auto it : entities) + { + auto& formComponent = view.get(it); + const auto pActor = RTTI_CAST(TESForm::GetById(formComponent.Id), TESForm, Actor); + + if (!pActor || !pActor->baseForm) + continue; + + char name[256]; + sprintf_s(name, std::size(name), "%s (%x)", pActor->baseForm->GetName(), formComponent.Id); + + if (ImGui::Selectable(name, m_formId == formComponent.Id)) + { + m_formId = formComponent.Id; + } + + if(m_formId == formComponent.Id) + s_selected = i; + + ++i; + } + + ImGui::EndChild(); + + if (s_selected < entities.size()) + DisplayEntityPanel(entities[s_selected]); +} + +void TestService::DisplayObjects() noexcept +{ + static uint32_t s_selected = 0; + + StackAllocator<1 << 12> allocator; + ScopedAllocator _{ allocator }; + + const auto view = m_world.view(); + + Vector entities(view.begin(), view.end()); + + ImGui::Text("Object list (%d)", entities.size()); + + ImGui::BeginChild("Entities", ImVec2(0, 200), true); + + int i = 0; + for(auto it : entities) + { + auto& formComponent = view.get(it); + const auto pRefr = RTTI_CAST(TESForm::GetById(formComponent.Id), TESForm, TESObjectREFR); + + if (!pRefr || !pRefr->baseForm) + continue; + + char name[256]; + sprintf_s(name, std::size(name), "%s (%x)", pRefr->baseForm->GetName(), formComponent.Id); + + if (ImGui::Selectable(name, m_formId == formComponent.Id)) + m_formId = formComponent.Id; + + if(m_formId == formComponent.Id) + s_selected = i; + + ++i; + } + + ImGui::EndChild(); +} + +void TestService::DisplayEntityPanel(entt::entity aEntity) noexcept +{ + const auto pFormIdComponent = m_world.try_get(aEntity); + const auto pLocalComponent = m_world.try_get(aEntity); + const auto pRemoteComponent = m_world.try_get(aEntity); + + if (pFormIdComponent) DisplayFormComponent(*pFormIdComponent); + if (pLocalComponent) DisplayLocalComponent(*pLocalComponent); + if (pRemoteComponent) DisplayRemoteComponent(*pRemoteComponent); +} + +void TestService::DisplayFormComponent(FormIdComponent& aFormComponent) const noexcept +{ + if (!ImGui::CollapsingHeader("Form Component", ImGuiTreeNodeFlags_DefaultOpen)) + return; + + const auto pActor = RTTI_CAST(TESForm::GetById(aFormComponent.Id), TESForm, Actor); + + if (!pActor) + return; + + ImGui::InputInt("Game Id", (int*)&aFormComponent.Id, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputFloat3("Position", pActor->position.AsArray(), "%.3f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat3("Rotation", pActor->rotation.AsArray(), "%.3f", ImGuiInputTextFlags_ReadOnly); + int isDead = int(pActor->IsDead()); + ImGui::InputInt("Is dead?", &isDead, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + int isWeaponDrawn = int(pActor->actorState.IsWeaponDrawn()); + ImGui::InputInt("Is weapon drawn?", &isWeaponDrawn, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); +#if TP_SKYRIM64 + float attributes[3] {pActor->GetActorValue(24), pActor->GetActorValue(25), pActor->GetActorValue(26)}; + ImGui::InputFloat3("Attributes (H/M/S)", attributes, "%.3f", ImGuiInputTextFlags_ReadOnly); +#endif +} + +void TestService::DisplayLocalComponent(LocalComponent& aLocalComponent) const noexcept +{ + if (!ImGui::CollapsingHeader("Local Component", ImGuiTreeNodeFlags_DefaultOpen)) + return; + + auto& action = aLocalComponent.CurrentAction; + ImGui::InputInt("Net Id", (int*)&aLocalComponent.Id, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputInt("Action Id", (int*)&action.ActionId, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputInt("Idle Id", (int*)&action.IdleId, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputScalarN("State", ImGuiDataType_U32, &action.State1, 2, nullptr, nullptr, "%x", ImGuiInputTextFlags_ReadOnly); +} + +void TestService::DisplayRemoteComponent(RemoteComponent& aLocalComponent) const noexcept +{ + if (!ImGui::CollapsingHeader("Remote Component", ImGuiTreeNodeFlags_DefaultOpen)) + return; + + ImGui::InputInt("Server Id", (int*)&aLocalComponent.Id, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); +} diff --git a/Code/client/Services/Debug/Views/FormDebugView.cpp b/Code/client/Services/Debug/Views/FormDebugView.cpp index 3fd6349c3..d655a299f 100644 --- a/Code/client/Services/Debug/Views/FormDebugView.cpp +++ b/Code/client/Services/Debug/Views/FormDebugView.cpp @@ -15,6 +15,7 @@ void TestService::DrawFormDebugView() { static TESObjectREFR* pRefr = nullptr; static TESForm* pFetchForm = nullptr; + static uint32_t formId = 0; ImGui::Begin("Form"); @@ -23,9 +24,9 @@ void TestService::DrawFormDebugView() if (ImGui::Button("Look up")) { - if (m_formId) + if (formId) { - pFetchForm = TESForm::GetById(m_formId); + pFetchForm = TESForm::GetById(formId); if (pFetchForm) pRefr = RTTI_CAST(pFetchForm, TESForm, TESObjectREFR); } diff --git a/Code/client/Services/Generic/ActorValueService.cpp b/Code/client/Services/Generic/ActorValueService.cpp index 7b6b5a77b..6d8a2dac6 100644 --- a/Code/client/Services/Generic/ActorValueService.cpp +++ b/Code/client/Services/Generic/ActorValueService.cpp @@ -53,7 +53,7 @@ void ActorValueService::CreateActorValuesComponent(const entt::entity aEntity, A float value = apActor->GetActorValue(i); actorValuesComponent.CurrentActorValues.ActorValuesList.insert({i, value}); - float maxValue = apActor->GetActorMaxValue(i); + float maxValue = apActor->GetActorPermanentValue(i); actorValuesComponent.CurrentActorValues.ActorMaxValuesList.insert({i, maxValue}); } } @@ -146,7 +146,7 @@ void ActorValueService::BroadcastActorValues() noexcept actorValuesComponent.CurrentActorValues.ActorValuesList[i] = newValue; } - float newMaxValue = pActor->GetActorMaxValue(i); + float newMaxValue = pActor->GetActorPermanentValue(i); float oldMaxValue = actorValuesComponent.CurrentActorValues.ActorMaxValuesList[i]; if (newMaxValue != oldMaxValue) { @@ -290,7 +290,7 @@ void ActorValueService::OnHealthChangeBroadcast(const NotifyHealthChangeBroadcas return; const float newHealth = pActor->GetActorValue(ActorValueInfo::kHealth) + acMessage.DeltaHealth; - pActor->ForceActorValue(2, ActorValueInfo::kHealth, newHealth); + pActor->ForceActorValue(ActorValueOwner::ForceMode::DAMAGE, ActorValueInfo::kHealth, newHealth); const float health = pActor->GetActorValue(ActorValueInfo::kHealth); if (health <= 0.f) @@ -326,7 +326,7 @@ void ActorValueService::OnActorValueChanges(const NotifyActorValueChanges& acMes if (key == ActorValueInfo::kStamina || key == ActorValueInfo::kMagicka || key == ActorValueInfo::kHealth) { - pActor->ForceActorValue(2, key, value); + pActor->ForceActorValue(ActorValueOwner::ForceMode::DAMAGE, key, value); continue; } #endif @@ -361,7 +361,7 @@ void ActorValueService::OnActorMaxValueChanges(const NotifyActorMaxValueChanges& spdlog::debug("Actor max value update, server ID: {:X}, key: {}, value: {}", acMessage.Id, key, value); - pActor->ForceActorValue(0, key, value); + pActor->ForceActorValue(ActorValueOwner::ForceMode::PERMANENT, key, value); } } diff --git a/Code/client/Services/Generic/InventoryService.cpp b/Code/client/Services/Generic/InventoryService.cpp index c33189e40..ac26f1d0d 100644 --- a/Code/client/Services/Generic/InventoryService.cpp +++ b/Code/client/Services/Generic/InventoryService.cpp @@ -30,12 +30,18 @@ InventoryService::InventoryService(World& aWorld, entt::dispatcher& aDispatcher, , m_dispatcher(aDispatcher) , m_transport(aTransport) { + m_updateConnection = m_dispatcher.sink().connect<&InventoryService::OnUpdate>(this); m_inventoryConnection = m_dispatcher.sink().connect<&InventoryService::OnInventoryChangeEvent>(this); m_equipmentConnection = m_dispatcher.sink().connect<&InventoryService::OnEquipmentChangeEvent>(this); m_inventoryChangeConnection = m_dispatcher.sink().connect<&InventoryService::OnNotifyInventoryChanges>(this); m_equipmentChangeConnection = m_dispatcher.sink().connect<&InventoryService::OnNotifyEquipmentChanges>(this); } +void InventoryService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept +{ + RunWeaponStateUpdates(); +} + void InventoryService::OnInventoryChangeEvent(const InventoryChangeEvent& acEvent) noexcept { if (!m_transport.IsConnected()) @@ -58,7 +64,7 @@ void InventoryService::OnInventoryChangeEvent(const InventoryChangeEvent& acEven RequestInventoryChanges request; request.ServerId = serverIdRes.value(); request.Item = std::move(acEvent.Item); - request.DropOrPickUp = acEvent.DropOrPickUp; + request.Drop = acEvent.Drop; m_transport.Send(request); @@ -111,7 +117,7 @@ void InventoryService::OnEquipmentChangeEvent(const EquipmentChangeEvent& acEven void InventoryService::OnNotifyInventoryChanges(const NotifyInventoryChanges& acMessage) noexcept { - if (acMessage.DropOrPickUp) + if (acMessage.Drop) { Actor* pActor = GetByServerId(Actor, acMessage.ServerId); if (!pActor) @@ -213,3 +219,39 @@ void InventoryService::OnNotifyEquipmentChanges(const NotifyEquipmentChanges& ac } } } + +void InventoryService::RunWeaponStateUpdates() noexcept +{ + if (!m_transport.IsConnected()) + return; + + static std::chrono::steady_clock::time_point lastSendTimePoint; + constexpr auto cDelayBetweenUpdates = 500ms; + + const auto now = std::chrono::steady_clock::now(); + if (now - lastSendTimePoint < cDelayBetweenUpdates) + return; + + lastSendTimePoint = now; + + auto view = m_world.view(); + + for (auto entity : view) + { + const auto& formIdComponent = view.get(entity); + Actor* const pActor = RTTI_CAST(TESForm::GetById(formIdComponent.Id), TESForm, Actor); + auto& localComponent = view.get(entity); + + bool isWeaponDrawn = pActor->actorState.IsWeaponDrawn(); + if (isWeaponDrawn != localComponent.IsWeaponDrawn) + { + localComponent.IsWeaponDrawn = isWeaponDrawn; + + DrawWeaponRequest request; + request.Id = localComponent.Id; + request.IsWeaponDrawn = isWeaponDrawn; + + m_transport.Send(request); + } + } +} diff --git a/Code/client/Services/Generic/ScriptService.cpp b/Code/client/Services/Generic/ScriptService.cpp index dd8d8ca72..795182aff 100644 --- a/Code/client/Services/Generic/ScriptService.cpp +++ b/Code/client/Services/Generic/ScriptService.cpp @@ -19,6 +19,8 @@ #include +#define DEBUG_SCRIPTS 0 + ScriptService::ScriptService(World& aWorld, entt::dispatcher& aDispatcher, ImguiService& aImguiService, TransportService& aTransportService) noexcept : ScriptStore(false) , m_dispatcher(aDispatcher) @@ -59,6 +61,7 @@ void ScriptService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept void ScriptService::OnDraw() noexcept { +#if DEBUG_SCRIPTS const auto view = m_world.view(); if (view.empty()) return; @@ -87,6 +90,7 @@ void ScriptService::OnDraw() noexcept } ImGui::End(); +#endif } void ScriptService::OnScripts(const Scripts& acScripts) noexcept diff --git a/Code/client/Services/InventoryService.h b/Code/client/Services/InventoryService.h index c24695e5c..e1c91d9d4 100644 --- a/Code/client/Services/InventoryService.h +++ b/Code/client/Services/InventoryService.h @@ -3,6 +3,7 @@ struct World; struct TransportService; +struct UpdateEvent; struct NotifyObjectInventoryChanges; struct NotifyInventoryChanges; struct InventoryChangeEvent; @@ -15,7 +16,8 @@ struct InventoryService ~InventoryService() noexcept = default; TP_NOCOPYMOVE(InventoryService); - + + void OnUpdate(const UpdateEvent& acUpdateEvent) noexcept; void OnInventoryChangeEvent(const InventoryChangeEvent& acEvent) noexcept; void OnEquipmentChangeEvent(const EquipmentChangeEvent& acEvent) noexcept; @@ -23,11 +25,14 @@ struct InventoryService void OnNotifyEquipmentChanges(const NotifyEquipmentChanges& acMessage) noexcept; private: + + void RunWeaponStateUpdates() noexcept; World& m_world; entt::dispatcher& m_dispatcher; TransportService& m_transport; + entt::scoped_connection m_updateConnection; entt::scoped_connection m_inventoryConnection; entt::scoped_connection m_equipmentConnection; entt::scoped_connection m_inventoryChangeConnection; diff --git a/Code/client/Services/TestService.h b/Code/client/Services/TestService.h index 920029a8d..0338e876b 100644 --- a/Code/client/Services/TestService.h +++ b/Code/client/Services/TestService.h @@ -25,7 +25,14 @@ struct TestService private: void PlaceActorInWorld() noexcept; - + void DisplayEntities() noexcept; + void DisplayObjects() noexcept; + void DisplayEntityPanel(entt::entity aEntity) noexcept; + void DisplayFormComponent(FormIdComponent& aFormComponent) const noexcept; + void DisplayLocalComponent(LocalComponent& aLocalComponent) const noexcept; + void DisplayRemoteComponent(RemoteComponent& aLocalComponent) const noexcept; + + void DrawEntitiesView(); void DrawComponentDebugView(); void DrawPlayerDebugView(); void DrawAnimDebugView(); @@ -34,6 +41,7 @@ struct TestService void DrawSkillView(); void DrawNetworkView(); void DrawPartyView(); + void DrawActorValuesView(); uint64_t DisplayGraphDescriptorKey(BSAnimationGraphManager* pManager) noexcept; diff --git a/Code/client/TiltedOnlineApp.cpp b/Code/client/TiltedOnlineApp.cpp index 33c394cc3..6de33eab4 100644 --- a/Code/client/TiltedOnlineApp.cpp +++ b/Code/client/TiltedOnlineApp.cpp @@ -30,6 +30,7 @@ TiltedOnlineApp::TiltedOnlineApp() create_directory(logPath, ec); auto rotatingLogger = std::make_shared(logPath / "tp_client.log", 1048576 * 5, 3); + //rotatingLogger->set_level(spdlog::level::debug); auto console = std::make_shared(); console->set_pattern("%^[%H:%M:%S] [%l]%$ %v"); diff --git a/Code/encoding/Messages/NotifyInventoryChanges.cpp b/Code/encoding/Messages/NotifyInventoryChanges.cpp index 6c75a9140..9036ec5de 100644 --- a/Code/encoding/Messages/NotifyInventoryChanges.cpp +++ b/Code/encoding/Messages/NotifyInventoryChanges.cpp @@ -5,7 +5,7 @@ void NotifyInventoryChanges::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter { Serialization::WriteVarInt(aWriter, ServerId); Item.Serialize(aWriter); - Serialization::WriteBool(aWriter, DropOrPickUp); + Serialization::WriteBool(aWriter, Drop); } void NotifyInventoryChanges::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept @@ -14,5 +14,5 @@ void NotifyInventoryChanges::DeserializeRaw(TiltedPhoques::Buffer::Reader& aRead ServerId = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; Item.Deserialize(aReader); - DropOrPickUp = Serialization::ReadBool(aReader); + Drop = Serialization::ReadBool(aReader); } diff --git a/Code/encoding/Messages/NotifyInventoryChanges.h b/Code/encoding/Messages/NotifyInventoryChanges.h index ce03f2cc5..d4580ca83 100644 --- a/Code/encoding/Messages/NotifyInventoryChanges.h +++ b/Code/encoding/Messages/NotifyInventoryChanges.h @@ -20,10 +20,10 @@ struct NotifyInventoryChanges final : ServerMessage return GetOpcode() == acRhs.GetOpcode() && ServerId == acRhs.ServerId && Item == acRhs.Item && - DropOrPickUp == acRhs.DropOrPickUp; + Drop == acRhs.Drop; } uint32_t ServerId{}; Inventory::Entry Item{}; - bool DropOrPickUp = false; + bool Drop = false; }; diff --git a/Code/encoding/Messages/RequestInventoryChanges.cpp b/Code/encoding/Messages/RequestInventoryChanges.cpp index 41e9e9116..ab694e145 100644 --- a/Code/encoding/Messages/RequestInventoryChanges.cpp +++ b/Code/encoding/Messages/RequestInventoryChanges.cpp @@ -5,7 +5,7 @@ void RequestInventoryChanges::SerializeRaw(TiltedPhoques::Buffer::Writer& aWrite { Serialization::WriteVarInt(aWriter, ServerId); Item.Serialize(aWriter); - Serialization::WriteBool(aWriter, DropOrPickUp); + Serialization::WriteBool(aWriter, Drop); } void RequestInventoryChanges::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept @@ -14,5 +14,5 @@ void RequestInventoryChanges::DeserializeRaw(TiltedPhoques::Buffer::Reader& aRea ServerId = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; Item.Deserialize(aReader); - DropOrPickUp = Serialization::ReadBool(aReader); + Drop = Serialization::ReadBool(aReader); } diff --git a/Code/encoding/Messages/RequestInventoryChanges.h b/Code/encoding/Messages/RequestInventoryChanges.h index cd8d43341..eda028d30 100644 --- a/Code/encoding/Messages/RequestInventoryChanges.h +++ b/Code/encoding/Messages/RequestInventoryChanges.h @@ -21,10 +21,10 @@ struct RequestInventoryChanges final : ClientMessage return GetOpcode() == acRhs.GetOpcode() && ServerId == acRhs.ServerId && Item == acRhs.Item && - DropOrPickUp == acRhs.DropOrPickUp; + Drop == acRhs.Drop; } uint32_t ServerId{}; Inventory::Entry Item{}; - bool DropOrPickUp = false; + bool Drop = false; }; diff --git a/Code/immersive_launcher/oobe/SupportChecks.cpp b/Code/immersive_launcher/oobe/SupportChecks.cpp index b651d70b1..83919f604 100644 --- a/Code/immersive_launcher/oobe/SupportChecks.cpp +++ b/Code/immersive_launcher/oobe/SupportChecks.cpp @@ -52,8 +52,10 @@ CompatabilityStatus ReportModCompatabilityStatus() if (!IsWindows8Point1OrGreater()) return CompatabilityStatus::kOldOS; + /* if (!TestD3D11Support()) return CompatabilityStatus::kDX11Unsupported; + */ return CompatabilityStatus::kAllGood; } diff --git a/Code/server/Services/InventoryService.cpp b/Code/server/Services/InventoryService.cpp index 0e76f32fe..0388a0a14 100644 --- a/Code/server/Services/InventoryService.cpp +++ b/Code/server/Services/InventoryService.cpp @@ -10,13 +10,14 @@ #include #include #include -#include +#include InventoryService::InventoryService(World& aWorld, entt::dispatcher& aDispatcher) : m_world(aWorld) { m_inventoryChangeConnection = aDispatcher.sink>().connect<&InventoryService::OnInventoryChanges>(this); m_equipmentChangeConnection = aDispatcher.sink>().connect<&InventoryService::OnEquipmentChanges>(this); + m_drawWeaponConnection = aDispatcher.sink>().connect<&InventoryService::OnWeaponDrawnRequest>(this); } void InventoryService::OnInventoryChanges(const PacketEvent& acMessage) noexcept @@ -36,7 +37,7 @@ void InventoryService::OnInventoryChanges(const PacketEvent(message.ServerId); GameServer::Get()->SendToPlayersInRange(notify, cOrigin, acMessage.GetSender()); @@ -68,3 +69,20 @@ void InventoryService::OnEquipmentChanges(const PacketEvent(message.ServerId); GameServer::Get()->SendToPlayersInRange(notify, cOrigin, acMessage.GetSender()); } + +void InventoryService::OnWeaponDrawnRequest(const PacketEvent& acMessage) noexcept +{ + auto& message = acMessage.Packet; + + auto characterView = m_world.view(); + const auto it = characterView.find(static_cast(message.Id)); + + if (it != std::end(characterView) + && characterView.get(*it).GetOwner() == acMessage.pPlayer) + { + auto& characterComponent = characterView.get(*it); + characterComponent.IsWeaponDrawn = message.IsWeaponDrawn; + spdlog::debug("Updating weapon drawn state {:x}:{}", message.Id, message.IsWeaponDrawn); + } +} + diff --git a/Code/server/Services/InventoryService.h b/Code/server/Services/InventoryService.h index 98fd43b9c..83552f96f 100644 --- a/Code/server/Services/InventoryService.h +++ b/Code/server/Services/InventoryService.h @@ -7,6 +7,7 @@ struct UpdateEvent; struct RequestObjectInventoryChanges; struct RequestInventoryChanges; struct RequestEquipmentChanges; +struct DrawWeaponRequest; struct PlayerLeaveCellEvent; class InventoryService @@ -16,6 +17,7 @@ class InventoryService void OnInventoryChanges(const PacketEvent& acMessage) noexcept; void OnEquipmentChanges(const PacketEvent& acMessage) noexcept; + void OnWeaponDrawnRequest(const PacketEvent& acMessage) noexcept; private: @@ -23,4 +25,5 @@ class InventoryService entt::scoped_connection m_inventoryChangeConnection; entt::scoped_connection m_equipmentChangeConnection; + entt::scoped_connection m_drawWeaponConnection; };