feat: add attackers handling

This commit is contained in:
k0t9i 2023-11-11 15:44:03 +04:00
parent 129381e13c
commit e42ff2b5e7
10 changed files with 177 additions and 11 deletions

View File

@ -74,6 +74,13 @@ namespace Client.Application.ViewModels
return target; return target;
} }
} }
public List<uint> Attackers
{
get
{
return hero.AttackerIds.ToList();
}
}
public HeroSummaryInfoViewModel(Hero hero) public HeroSummaryInfoViewModel(Hero hero)
{ {
this.hero = hero; this.hero = hero;
@ -102,6 +109,10 @@ namespace Client.Application.ViewModels
OnPropertyChanged("Target"); OnPropertyChanged("Target");
} }
} }
else if (e.PropertyName == "AttackerIds")
{
OnPropertyChanged("Attackers");
}
} }
private void InventoryInfo_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) private void InventoryInfo_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)

View File

@ -221,6 +221,13 @@
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
</DockPanel> </DockPanel>
<ListBox Grid.Row="3" Margin="4" ItemsSource="{Binding Attackers, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Mode=OneWay}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" DataContext="{Binding Target, Mode=OneWay}" Visibility="{Binding Path=.,Converter={StaticResource NullToVisibilityConverter}}" Margin="4"> <StackPanel Grid.Column="1" DataContext="{Binding Target, Mode=OneWay}" Visibility="{Binding Path=.,Converter={StaticResource NullToVisibilityConverter}}" Margin="4">
<TextBlock FontSize="16" Text="{Binding Path=Name, Mode=OneWay}"></TextBlock> <TextBlock FontSize="16" Text="{Binding Path=Name, Mode=OneWay}"></TextBlock>
<TextBlock Text="{Binding Path=BriefInfo, Mode=OneWay}"></TextBlock> <TextBlock Text="{Binding Path=BriefInfo, Mode=OneWay}"></TextBlock>

View File

@ -1,8 +1,11 @@
using Client.Domain.Common; using Client.Domain.Common;
using Client.Domain.Enums; using Client.Domain.Enums;
using Client.Domain.ValueObjects; using Client.Domain.ValueObjects;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
namespace Client.Domain.Entities namespace Client.Domain.Entities
{ {
@ -63,6 +66,9 @@ namespace Client.Domain.Entities
} }
public uint AggroRadius { get; set; } = 0; public uint AggroRadius { get; set; } = 0;
public bool IsHostile { get; set; } = false; public bool IsHostile { get; set; } = false;
// TODO move from domain
[JsonProperty("AttackerIds", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<uint> 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) 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 Phenotype phenotype;
private CreatureInterface? target; private CreatureInterface? target;
private uint targetId; private uint targetId;
private List<uint> attackerIds = new List<uint>();
} }
} }

View File

@ -43,6 +43,14 @@ namespace L2Bot::Domain::Entities
} }
const size_t GetHash() const override const size_t GetHash() const override
{ {
size_t attackersHash = 0;
for (const auto& kvp : m_AttackerIds)
{
attackersHash = Helpers::CombineHashes({
std::hash<uint32_t>{}(kvp.first),
});
}
return Helpers::CombineHashes({ return Helpers::CombineHashes({
WorldObject::GetHash(), WorldObject::GetHash(),
m_FullName.GetHash(), m_FullName.GetHash(),
@ -54,7 +62,8 @@ namespace L2Bot::Domain::Entities
m_Reputation.GetHash(), m_Reputation.GetHash(),
m_InventoryInfo.GetHash(), m_InventoryInfo.GetHash(),
std::hash<uint32_t>{}(m_TargetId), std::hash<uint32_t>{}(m_TargetId),
std::hash<uint32_t>{}(m_IsStanding) std::hash<uint32_t>{}(m_IsStanding),
attackersHash
}); });
} }
const std::string GetEntityName() const override const std::string GetEntityName() const override
@ -65,6 +74,21 @@ namespace L2Bot::Domain::Entities
{ {
return m_FullName; 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<uint32_t, uint32_t>& GetAttackerIds() const
{
return m_AttackerIds;
}
const std::vector<Serializers::Node> BuildSerializationNodes() const override const std::vector<Serializers::Node> BuildSerializationNodes() const override
{ {
@ -80,6 +104,12 @@ namespace L2Bot::Domain::Entities
result.push_back({ L"inventoryInfo", m_InventoryInfo.BuildSerializationNodes() }); result.push_back({ L"inventoryInfo", m_InventoryInfo.BuildSerializationNodes() });
result.push_back({ L"targetId", std::to_wstring(m_TargetId) }); result.push_back({ L"targetId", std::to_wstring(m_TargetId) });
result.push_back({ L"isStanding", std::to_wstring(m_IsStanding) }); result.push_back({ L"isStanding", std::to_wstring(m_IsStanding) });
std::vector<Serializers::Node> 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; return result;
} }
@ -127,5 +157,6 @@ namespace L2Bot::Domain::Entities
ValueObjects::InventoryInfo m_InventoryInfo = ValueObjects::InventoryInfo(); ValueObjects::InventoryInfo m_InventoryInfo = ValueObjects::InventoryInfo();
uint32_t m_TargetId = 0; uint32_t m_TargetId = 0;
bool m_IsStanding = true; bool m_IsStanding = true;
std::map<uint32_t, uint32_t> m_AttackerIds;
}; };
} }

View File

@ -0,0 +1,43 @@
#pragma once
#include <cstdint>
#include <vector>
#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;
};
}

View File

@ -178,6 +178,7 @@
<ClInclude Include="Domain\Enums\ItemTypeEnum.h" /> <ClInclude Include="Domain\Enums\ItemTypeEnum.h" />
<ClInclude Include="Domain\Enums\WeaponTypeEnum.h" /> <ClInclude Include="Domain\Enums\WeaponTypeEnum.h" />
<ClInclude Include="Domain\Events\AbnormalEffectChangedEvent.h" /> <ClInclude Include="Domain\Events\AbnormalEffectChangedEvent.h" />
<ClInclude Include="Domain\Events\AttackedEvent.h" />
<ClInclude Include="Domain\Events\ChatMessageCreatedEvent.h" /> <ClInclude Include="Domain\Events\ChatMessageCreatedEvent.h" />
<ClInclude Include="Domain\Events\CreatureDiedEvent.h" /> <ClInclude Include="Domain\Events\CreatureDiedEvent.h" />
<ClInclude Include="Domain\Events\Event.h" /> <ClInclude Include="Domain\Events\Event.h" />

View File

@ -237,6 +237,9 @@
<ClInclude Include="Domain\Exceptions.h"> <ClInclude Include="Domain\Exceptions.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="Domain\Events\AttackedEvent.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="pch.cpp"> <ClCompile Include="pch.cpp">

View File

@ -15,6 +15,7 @@
#include "Domain/Events/ChatMessageCreatedEvent.h" #include "Domain/Events/ChatMessageCreatedEvent.h"
#include "Domain/Events/OnEndItemListEvent.h" #include "Domain/Events/OnEndItemListEvent.h"
#include "Domain/Events/CreatureDiedEvent.h" #include "Domain/Events/CreatureDiedEvent.h"
#include "Domain/Events/AttackedEvent.h"
#include "Domain/DTO/ItemData.h" #include "Domain/DTO/ItemData.h"
#include "Domain/DTO/ChatMessageData.h" #include "Domain/DTO/ChatMessageData.h"
#include "FName.h" #include "FName.h"
@ -38,6 +39,7 @@ namespace Interlude
void(__thiscall* GameEngineWrapper::__OnEndItemList)(GameEngine*) = 0; void(__thiscall* GameEngineWrapper::__OnEndItemList)(GameEngine*) = 0;
float(__thiscall* GameEngineWrapper::__GetMaxTickRate)(GameEngine*) = 0; float(__thiscall* GameEngineWrapper::__GetMaxTickRate)(GameEngine*) = 0;
int(__thiscall* GameEngineWrapper::__OnDie)(GameEngine*, User*, L2ParamStack&) = 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) void GameEngineWrapper::Init(HMODULE hModule)
@ -78,6 +80,10 @@ namespace Interlude
(FARPROC&)__OnDie = (FARPROC)splice( (FARPROC&)__OnDie = (FARPROC)splice(
GetProcAddress(hModule, "?OnDie@UGameEngine@@UAEHPAUUser@@AAVL2ParamStack@@@Z"), __OnDie_hook 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"); Services::ServiceLocator::GetInstance().GetLogger()->Info(L"UGameEngine hooks initialized");
} }
@ -95,6 +101,7 @@ namespace Interlude
restore((void*&)__GetMaxTickRate); restore((void*&)__GetMaxTickRate);
restore((void*&)__OnDie); restore((void*&)__OnDie);
restore((void*&)__Tick); restore((void*&)__Tick);
restore((void*&)__OnAttack);
Services::ServiceLocator::GetInstance().GetLogger()->Info(L"UGameEngine hooks restored"); Services::ServiceLocator::GetInstance().GetLogger()->Info(L"UGameEngine hooks restored");
} }
@ -104,10 +111,11 @@ namespace Interlude
(*__OnSkillListPacket)(This, stack); (*__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<int32_t>() }); Services::ServiceLocator::GetInstance().GetEventDispatcher()->Dispatch(Events::SkillUsedEvent{ stack.GetBufferAsVector<int32_t>() });
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) void __fastcall GameEngineWrapper::__OnReceiveMagicSkillCanceled_hook(GameEngine* This, uint32_t, User* user)
@ -215,7 +223,6 @@ namespace Interlude
(*__OnEndItemList)(This); (*__OnEndItemList)(This);
} }
// TODO ini // TODO ini
// 0 - ôïñ áåç îãðàíè÷åíèé
float __fastcall GameEngineWrapper::__GetMaxTickRate_hook(GameEngine* This, int) float __fastcall GameEngineWrapper::__GetMaxTickRate_hook(GameEngine* This, int)
{ {
float fps = (*__GetMaxTickRate)(This); float fps = (*__GetMaxTickRate)(This);
@ -228,4 +235,10 @@ namespace Interlude
return (*__OnDie)(This, creature, stack); 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);
}
} }

View File

@ -31,9 +31,10 @@ namespace Interlude
static void(__thiscall* __OnEndItemList)(GameEngine*); static void(__thiscall* __OnEndItemList)(GameEngine*);
static float(__thiscall* __GetMaxTickRate)(GameEngine*); static float(__thiscall* __GetMaxTickRate)(GameEngine*);
static int(__thiscall* __OnDie)(GameEngine*, User*, L2ParamStack&); 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 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 __OnReceiveMagicSkillCanceled_hook(GameEngine* This, uint32_t /*edx*/, User* user);
static void __fastcall __AddAbnormalStatus_hook(GameEngine* This, uint32_t /*edx*/, L2ParamStack& stack); static void __fastcall __AddAbnormalStatus_hook(GameEngine* This, uint32_t /*edx*/, L2ParamStack& stack);
static void __fastcall __AddInventoryItem_hook(GameEngine* This, uint32_t /*edx*/, ItemInfo& itemInfo); 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 void __fastcall __OnEndItemList_hook(GameEngine* This, uint32_t /*edx*/);
static int __fastcall __OnDie_hook(GameEngine* This, int /*edx*/, User* creature, L2ParamStack& stack); static int __fastcall __OnDie_hook(GameEngine* This, int /*edx*/, User* creature, L2ParamStack& stack);
static float __fastcall __GetMaxTickRate_hook(GameEngine* This, int /*edx*/); 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: private:
static GameEngine* _target; static GameEngine* _target;
}; };

View File

@ -6,6 +6,7 @@
#include "Domain/Events/HeroCreatedEvent.h" #include "Domain/Events/HeroCreatedEvent.h"
#include "Domain/Events/HeroDeletedEvent.h" #include "Domain/Events/HeroDeletedEvent.h"
#include "Domain/Events/CreatureDiedEvent.h" #include "Domain/Events/CreatureDiedEvent.h"
#include "Domain/Events/AttackedEvent.h"
#include "../GameStructs/NetworkHandlerWrapper.h" #include "../GameStructs/NetworkHandlerWrapper.h"
#include "Domain/Services/ServiceLocator.h" #include "Domain/Services/ServiceLocator.h"
@ -32,6 +33,16 @@ namespace Interlude
else else
{ {
m_Factory.Update(m_Hero, hero); m_Factory.Update(m_Hero, hero);
const auto attackers = std::map<uint32_t, uint32_t>(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; result[hero->objectId] = m_Hero;
} }
@ -53,16 +64,53 @@ namespace Interlude
void Init() override void Init() override
{ {
Services::ServiceLocator::GetInstance().GetEventDispatcher()->Subscribe(Events::CreatureDiedEvent::name, [this](const Events::Event& evt) { Services::ServiceLocator::GetInstance().GetEventDispatcher()->Subscribe(Events::CreatureDiedEvent::name, [this](const Events::Event& evt) {
std::unique_lock<std::shared_timed_mutex>(m_Mutex); OnCreatureDied(evt);
if (evt.GetName() == Events::CreatureDiedEvent::name) });
Services::ServiceLocator::GetInstance().GetEventDispatcher()->Subscribe(Events::AttackedEvent::name, [this](const Events::Event& evt) {
OnAttacked(evt);
});
}
void OnCreatureDied(const Events::Event& evt)
{
std::shared_lock<std::shared_timed_mutex>(m_Mutex);
if (evt.GetName() == Events::CreatureDiedEvent::name)
{
const auto casted = static_cast<const Events::CreatureDiedEvent&>(evt);
if (m_Hero)
{ {
const auto casted = static_cast<const Events::CreatureDiedEvent&>(evt); if (m_Hero->GetId() == casted.GetCreatureId())
if (m_Hero && m_Hero->GetId() == casted.GetCreatureId())
{ {
Services::ServiceLocator::GetInstance().GetLogger()->App(L"{} died", m_Hero->GetFullName().GetNickname()); 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<std::shared_timed_mutex>(m_Mutex);
if (evt.GetName() == Events::AttackedEvent::name)
{
const auto casted = static_cast<const Events::AttackedEvent&>(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) : HeroRepository(const NetworkHandlerWrapper& networkHandler, const HeroFactory& factory) :