diff --git a/Client/Application/ViewModels/HeroSummaryInfoViewModel.cs b/Client/Application/ViewModels/HeroSummaryInfoViewModel.cs index c3f4124..d401963 100644 --- a/Client/Application/ViewModels/HeroSummaryInfoViewModel.cs +++ b/Client/Application/ViewModels/HeroSummaryInfoViewModel.cs @@ -74,6 +74,13 @@ namespace Client.Application.ViewModels return target; } } + public List Attackers + { + get + { + return hero.AttackerIds.ToList(); + } + } public HeroSummaryInfoViewModel(Hero hero) { this.hero = hero; @@ -102,6 +109,10 @@ namespace Client.Application.ViewModels OnPropertyChanged("Target"); } } + else if (e.PropertyName == "AttackerIds") + { + OnPropertyChanged("Attackers"); + } } private void InventoryInfo_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) diff --git a/Client/Application/Views/MainWindow.xaml b/Client/Application/Views/MainWindow.xaml index 16011a9..e29ee24 100644 --- a/Client/Application/Views/MainWindow.xaml +++ b/Client/Application/Views/MainWindow.xaml @@ -221,6 +221,13 @@ + + + + + + + diff --git a/Client/Domain/Entities/Hero.cs b/Client/Domain/Entities/Hero.cs index a51df3b..e0c5853 100644 --- a/Client/Domain/Entities/Hero.cs +++ b/Client/Domain/Entities/Hero.cs @@ -1,8 +1,11 @@ using Client.Domain.Common; using Client.Domain.Enums; using Client.Domain.ValueObjects; +using Newtonsoft.Json; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Linq; namespace Client.Domain.Entities { @@ -63,6 +66,9 @@ namespace Client.Domain.Entities } public uint AggroRadius { get; set; } = 0; public bool IsHostile { get; set; } = false; + // TODO move from domain + [JsonProperty("AttackerIds", ObjectCreationHandling = ObjectCreationHandling.Replace)] + public List AttackerIds { get => attackerIds; set { if (!value.All(attackerIds.Contains) || !attackerIds.All(value.Contains)) { attackerIds = value; OnPropertyChanged("AttackerIds"); } } } public Hero(uint id, Transform transform, FullName fullName, VitalStats vitalStats, Phenotype phenotype, ExperienceInfo experienceInfo, PermanentStats permanentStats, VariableStats variableStats, Reputation reputation, InventoryInfo inventoryInfo, uint targetId, bool isStanding) { @@ -100,5 +106,6 @@ namespace Client.Domain.Entities private Phenotype phenotype; private CreatureInterface? target; private uint targetId; + private List attackerIds = new List(); } } diff --git a/L2BotCore/Domain/Entities/Hero.h b/L2BotCore/Domain/Entities/Hero.h index 670df66..ecd2d5e 100644 --- a/L2BotCore/Domain/Entities/Hero.h +++ b/L2BotCore/Domain/Entities/Hero.h @@ -43,6 +43,14 @@ namespace L2Bot::Domain::Entities } const size_t GetHash() const override { + size_t attackersHash = 0; + for (const auto& kvp : m_AttackerIds) + { + attackersHash = Helpers::CombineHashes({ + std::hash{}(kvp.first), + }); + } + return Helpers::CombineHashes({ WorldObject::GetHash(), m_FullName.GetHash(), @@ -54,7 +62,8 @@ namespace L2Bot::Domain::Entities m_Reputation.GetHash(), m_InventoryInfo.GetHash(), std::hash{}(m_TargetId), - std::hash{}(m_IsStanding) + std::hash{}(m_IsStanding), + attackersHash }); } const std::string GetEntityName() const override @@ -65,6 +74,21 @@ namespace L2Bot::Domain::Entities { return m_FullName; } + void AddAttacker(const uint32_t attackerId) + { + m_AttackerIds[attackerId] = attackerId; + } + void RemoveAttacker(const uint32_t attackerId) + { + if (m_AttackerIds.find(attackerId) != m_AttackerIds.end()) + { + m_AttackerIds.erase(attackerId); + } + } + const std::map& GetAttackerIds() const + { + return m_AttackerIds; + } const std::vector BuildSerializationNodes() const override { @@ -80,6 +104,12 @@ namespace L2Bot::Domain::Entities result.push_back({ L"inventoryInfo", m_InventoryInfo.BuildSerializationNodes() }); result.push_back({ L"targetId", std::to_wstring(m_TargetId) }); result.push_back({ L"isStanding", std::to_wstring(m_IsStanding) }); + std::vector attackers; + for (const auto& kvp : m_AttackerIds) + { + attackers.push_back({ std::to_wstring(kvp.first), std::to_wstring(kvp.first) }); + } + result.push_back({ L"attackerIds", attackers, true }); return result; } @@ -127,5 +157,6 @@ namespace L2Bot::Domain::Entities ValueObjects::InventoryInfo m_InventoryInfo = ValueObjects::InventoryInfo(); uint32_t m_TargetId = 0; bool m_IsStanding = true; + std::map m_AttackerIds; }; } diff --git a/L2BotCore/Domain/Events/AttackedEvent.h b/L2BotCore/Domain/Events/AttackedEvent.h new file mode 100644 index 0000000..39024f0 --- /dev/null +++ b/L2BotCore/Domain/Events/AttackedEvent.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include "Event.h" + +namespace L2Bot::Domain::Events +{ + class AttackedEvent : public Event + { + public: + static constexpr const char* name = "attacked"; + + const std::string GetName() const + { + return std::string(name); + } + + const uint32_t GetAttackerId() const + { + return m_AttackerId; + } + + const uint32_t GetTargetId() const + { + return m_TargetId; + } + + AttackedEvent(const uint32_t attackerId, const uint32_t targetId) : + m_AttackerId(attackerId), + m_TargetId(targetId) + { + + } + + AttackedEvent() = delete; + virtual ~AttackedEvent() = default; + + private: + const uint32_t m_AttackerId; + const uint32_t m_TargetId; + }; +} \ No newline at end of file diff --git a/L2BotCore/L2BotCore.vcxproj b/L2BotCore/L2BotCore.vcxproj index ed0e6f0..74676c7 100644 --- a/L2BotCore/L2BotCore.vcxproj +++ b/L2BotCore/L2BotCore.vcxproj @@ -178,6 +178,7 @@ + diff --git a/L2BotCore/L2BotCore.vcxproj.filters b/L2BotCore/L2BotCore.vcxproj.filters index 3aee58f..5b681ef 100644 --- a/L2BotCore/L2BotCore.vcxproj.filters +++ b/L2BotCore/L2BotCore.vcxproj.filters @@ -237,6 +237,9 @@ Header Files + + Header Files + diff --git a/L2BotDll/Versions/Interlude/GameStructs/GameEngineWrapper.cpp b/L2BotDll/Versions/Interlude/GameStructs/GameEngineWrapper.cpp index 019152e..193b9f6 100644 --- a/L2BotDll/Versions/Interlude/GameStructs/GameEngineWrapper.cpp +++ b/L2BotDll/Versions/Interlude/GameStructs/GameEngineWrapper.cpp @@ -15,6 +15,7 @@ #include "Domain/Events/ChatMessageCreatedEvent.h" #include "Domain/Events/OnEndItemListEvent.h" #include "Domain/Events/CreatureDiedEvent.h" +#include "Domain/Events/AttackedEvent.h" #include "Domain/DTO/ItemData.h" #include "Domain/DTO/ChatMessageData.h" #include "FName.h" @@ -38,6 +39,7 @@ namespace Interlude void(__thiscall* GameEngineWrapper::__OnEndItemList)(GameEngine*) = 0; float(__thiscall* GameEngineWrapper::__GetMaxTickRate)(GameEngine*) = 0; int(__thiscall* GameEngineWrapper::__OnDie)(GameEngine*, User*, L2ParamStack&) = 0; + int(__thiscall* GameEngineWrapper::__OnAttack)(GameEngine*, User*, User*, int, int, int, int, int, L2::FVector, int) = 0; void GameEngineWrapper::Init(HMODULE hModule) @@ -78,6 +80,10 @@ namespace Interlude (FARPROC&)__OnDie = (FARPROC)splice( GetProcAddress(hModule, "?OnDie@UGameEngine@@UAEHPAUUser@@AAVL2ParamStack@@@Z"), __OnDie_hook ); + (FARPROC&)__OnAttack = (FARPROC)splice( + GetProcAddress(hModule, "?OnAttack@UGameEngine@@UAEHPAUUser@@0HHHHHVFVector@@H@Z"), __OnAttack_hook + ); + Services::ServiceLocator::GetInstance().GetLogger()->Info(L"UGameEngine hooks initialized"); } @@ -95,6 +101,7 @@ namespace Interlude restore((void*&)__GetMaxTickRate); restore((void*&)__OnDie); restore((void*&)__Tick); + restore((void*&)__OnAttack); Services::ServiceLocator::GetInstance().GetLogger()->Info(L"UGameEngine hooks restored"); } @@ -104,10 +111,11 @@ namespace Interlude (*__OnSkillListPacket)(This, stack); } - int __fastcall GameEngineWrapper::__OnReceiveMagicSkillUse_hook(GameEngine* This, uint32_t, User* u1, User* u2, L2ParamStack& stack) + int __fastcall GameEngineWrapper::__OnReceiveMagicSkillUse_hook(GameEngine* This, uint32_t, User* attacker, User* target, L2ParamStack& stack) { Services::ServiceLocator::GetInstance().GetEventDispatcher()->Dispatch(Events::SkillUsedEvent{ stack.GetBufferAsVector() }); - return (*__OnReceiveMagicSkillUse)(This, u1, u2, stack); + Services::ServiceLocator::GetInstance().GetEventDispatcher()->Dispatch(Events::AttackedEvent{ attacker->objectId, target->objectId }); + return (*__OnReceiveMagicSkillUse)(This, attacker, target, stack); } void __fastcall GameEngineWrapper::__OnReceiveMagicSkillCanceled_hook(GameEngine* This, uint32_t, User* user) @@ -215,7 +223,6 @@ namespace Interlude (*__OnEndItemList)(This); } // TODO ini - // 0 - ôïñ áåç îãðàíè÷åíèé float __fastcall GameEngineWrapper::__GetMaxTickRate_hook(GameEngine* This, int) { float fps = (*__GetMaxTickRate)(This); @@ -228,4 +235,10 @@ namespace Interlude return (*__OnDie)(This, creature, stack); } + + int __fastcall GameEngineWrapper::__OnAttack_hook(GameEngine* This, int, User* attacker, User* target, int unk0, int unk1, int unk2, int unk3, int unk4, L2::FVector unk5, int unk6) + { + Services::ServiceLocator::GetInstance().GetEventDispatcher()->Dispatch(Events::AttackedEvent{ attacker->objectId, target->objectId }); + return (*__OnAttack)(This, attacker, target, unk0, unk1, unk2, unk3, unk4, unk5, unk6); + } } \ No newline at end of file diff --git a/L2BotDll/Versions/Interlude/GameStructs/GameEngineWrapper.h b/L2BotDll/Versions/Interlude/GameStructs/GameEngineWrapper.h index 3863d98..9fb2621 100644 --- a/L2BotDll/Versions/Interlude/GameStructs/GameEngineWrapper.h +++ b/L2BotDll/Versions/Interlude/GameStructs/GameEngineWrapper.h @@ -31,9 +31,10 @@ namespace Interlude static void(__thiscall* __OnEndItemList)(GameEngine*); static float(__thiscall* __GetMaxTickRate)(GameEngine*); static int(__thiscall* __OnDie)(GameEngine*, User*, L2ParamStack&); + static int(__thiscall* __OnAttack)(GameEngine*, User*, User*, int, int, int, int, int, L2::FVector, int); static void __fastcall __OnSkillListPacket_hook(GameEngine* This, uint32_t /*edx*/, L2ParamStack& stack); - static int __fastcall __OnReceiveMagicSkillUse_hook(GameEngine* This, uint32_t /*edx*/, User* u1, User* u2, L2ParamStack& stack); + static int __fastcall __OnReceiveMagicSkillUse_hook(GameEngine* This, uint32_t /*edx*/, User* attacker, User* target, L2ParamStack& stack); static void __fastcall __OnReceiveMagicSkillCanceled_hook(GameEngine* This, uint32_t /*edx*/, User* user); static void __fastcall __AddAbnormalStatus_hook(GameEngine* This, uint32_t /*edx*/, L2ParamStack& stack); static void __fastcall __AddInventoryItem_hook(GameEngine* This, uint32_t /*edx*/, ItemInfo& itemInfo); @@ -44,7 +45,8 @@ namespace Interlude static void __fastcall __OnEndItemList_hook(GameEngine* This, uint32_t /*edx*/); static int __fastcall __OnDie_hook(GameEngine* This, int /*edx*/, User* creature, L2ParamStack& stack); static float __fastcall __GetMaxTickRate_hook(GameEngine* This, int /*edx*/); - + static int __fastcall __OnAttack_hook(GameEngine* This, int /*edx*/, User* attacker, User* target, int unk0, int unk1, int unk2, int unk3, int unk4, L2::FVector unk5, int unk6); + private: static GameEngine* _target; }; diff --git a/L2BotDll/Versions/Interlude/Repositories/HeroRepository.h b/L2BotDll/Versions/Interlude/Repositories/HeroRepository.h index 122dd26..6c34a80 100644 --- a/L2BotDll/Versions/Interlude/Repositories/HeroRepository.h +++ b/L2BotDll/Versions/Interlude/Repositories/HeroRepository.h @@ -6,6 +6,7 @@ #include "Domain/Events/HeroCreatedEvent.h" #include "Domain/Events/HeroDeletedEvent.h" #include "Domain/Events/CreatureDiedEvent.h" +#include "Domain/Events/AttackedEvent.h" #include "../GameStructs/NetworkHandlerWrapper.h" #include "Domain/Services/ServiceLocator.h" @@ -32,6 +33,16 @@ namespace Interlude else { m_Factory.Update(m_Hero, hero); + const auto attackers = std::map(m_Hero->GetAttackerIds()); + for (const auto kvp : attackers) + { + const auto attacker = m_NetworkHandler.GetUser(kvp.first); + // try to remove creature out of sight from the attackers + if (attacker == nullptr) + { + m_Hero->RemoveAttacker(kvp.first); + } + } } result[hero->objectId] = m_Hero; } @@ -53,16 +64,53 @@ namespace Interlude void Init() override { Services::ServiceLocator::GetInstance().GetEventDispatcher()->Subscribe(Events::CreatureDiedEvent::name, [this](const Events::Event& evt) { - std::unique_lock(m_Mutex); - if (evt.GetName() == Events::CreatureDiedEvent::name) + OnCreatureDied(evt); + }); + Services::ServiceLocator::GetInstance().GetEventDispatcher()->Subscribe(Events::AttackedEvent::name, [this](const Events::Event& evt) { + OnAttacked(evt); + }); + } + + void OnCreatureDied(const Events::Event& evt) + { + std::shared_lock(m_Mutex); + if (evt.GetName() == Events::CreatureDiedEvent::name) + { + const auto casted = static_cast(evt); + if (m_Hero) { - const auto casted = static_cast(evt); - if (m_Hero && m_Hero->GetId() == casted.GetCreatureId()) + if (m_Hero->GetId() == casted.GetCreatureId()) { Services::ServiceLocator::GetInstance().GetLogger()->App(L"{} died", m_Hero->GetFullName().GetNickname()); } + else + { + // try to remove dead creature from the attackers + m_Hero->RemoveAttacker(casted.GetCreatureId()); + } } - }); + } + } + + void OnAttacked(const Events::Event& evt) + { + std::shared_lock(m_Mutex); + if (evt.GetName() == Events::AttackedEvent::name) + { + const auto casted = static_cast(evt); + if (m_Hero && m_Hero->GetId() != casted.GetAttackerId()) + { + if (m_Hero->GetId() == casted.GetTargetId()) + { + m_Hero->AddAttacker(casted.GetAttackerId()); + } + else + { + // try to remove creature that is attacking another target from the attackers + m_Hero->RemoveAttacker(casted.GetAttackerId()); + } + } + } } HeroRepository(const NetworkHandlerWrapper& networkHandler, const HeroFactory& factory) :