Skip to content

Commit 16c3786

Browse files
Merge pull request #735 from rfortier/fix/sync-remove-spell
Sync Remove Spell / Magic Cancel
2 parents 99f1320 + 8c11aa6 commit 16c3786

File tree

14 files changed

+229
-5
lines changed

14 files changed

+229
-5
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#pragma once
2+
3+
struct RemoveSpellEvent
4+
{
5+
RemoveSpellEvent() = default;
6+
7+
uint32_t TargetId{};
8+
uint32_t SpellId{};
9+
};

Code/client/Games/Skyrim/Actor.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <Events/MountEvent.h>
1818
#include <Events/DialogueEvent.h>
1919
#include <Events/HitEvent.h>
20+
#include <Events/RemoveSpellEvent.h>
2021

2122
#include <Games/TES.h>
2223
#include <World.h>
@@ -103,6 +104,7 @@ void Actor::Save_Reversed(const uint32_t aChangeFlags, Buffer::Writer& aWriter)
103104

104105
#endif
105106

107+
TP_THIS_FUNCTION(TRemoveSpell, bool, Actor, MagicItem*);
106108
TP_THIS_FUNCTION(TCharacterConstructor, Actor*, Actor);
107109
TP_THIS_FUNCTION(TCharacterConstructor2, Actor*, Actor, uint8_t aUnk);
108110
TP_THIS_FUNCTION(TCharacterDestructor, Actor*, Actor);
@@ -119,6 +121,7 @@ TCharacterConstructor* RealCharacterConstructor;
119121
TCharacterConstructor2* RealCharacterConstructor2;
120122
TCharacterDestructor* RealCharacterDestructor;
121123

124+
static TRemoveSpell* RealRemoveSpell = nullptr;
122125
static TAddInventoryItem* RealAddInventoryItem = nullptr;
123126
static TPickUpObject* RealPickUpObject = nullptr;
124127
static TDropObject* RealDropObject = nullptr;
@@ -352,6 +355,17 @@ void Actor::StopCombat() noexcept
352355
s_pStopCombat(this);
353356
}
354357

358+
bool Actor::RemoveSpell(MagicItem* apSpell) noexcept
359+
{
360+
if (!apSpell)
361+
{
362+
spdlog::error(__FUNCTION__ ": apSpell is null");
363+
return false;
364+
}
365+
// spdlog::info(__FUNCTION__ ": removing: {} from actor: {}", apSpell->formID, formID);
366+
return TiltedPhoques::ThisCall(RealRemoveSpell, this, apSpell);
367+
}
368+
355369
bool Actor::HasPerk(uint32_t aPerkFormId) const noexcept
356370
{
357371
return GetPerkRank(aPerkFormId) != 0;
@@ -369,6 +383,22 @@ uint8_t Actor::GetPerkRank(uint32_t aPerkFormId) const noexcept
369383
return TiltedPhoques::ThisCall(getPerkRank, this, pPerk);
370384
}
371385

386+
bool TP_MAKE_THISCALL(HookRemoveSpell, Actor, MagicItem* apSpell)
387+
{
388+
bool result = TiltedPhoques::ThisCall(RealRemoveSpell, apThis, apSpell);
389+
if (apThis->GetExtension()->IsLocalPlayer() && result)
390+
{
391+
//spdlog::info(__FUNCTION__ ": spell: {}, ID: {} from local player", apSpell->GetName() , apSpell->formID);
392+
RemoveSpellEvent removalEvent;
393+
394+
removalEvent.TargetId = apThis->formID;
395+
removalEvent.SpellId = apSpell->formID;
396+
World::Get().GetRunner().Trigger(removalEvent);
397+
}
398+
399+
return result;
400+
}
401+
372402
Actor* TP_MAKE_THISCALL(HookCharacterConstructor, Actor)
373403
{
374404
TP_EMPTY_HOOK_PLACEHOLDER;
@@ -1233,6 +1263,7 @@ static TiltedPhoques::Initializer s_actorHooks(
12331263
{
12341264
POINTER_SKYRIMSE(TActorProcess, s_actorProcess, 37356);
12351265
POINTER_SKYRIMSE(TSetPosition, s_setPosition, 19790);
1266+
POINTER_SKYRIMSE(TRemoveSpell, s_removeSpell, 38717);
12361267
POINTER_SKYRIMSE(TCharacterConstructor, s_characterCtor, 40245);
12371268
POINTER_SKYRIMSE(TCharacterConstructor2, s_characterCtor2, 40246);
12381269
POINTER_SKYRIMSE(TCharacterDestructor, s_characterDtor, 37175);
@@ -1255,6 +1286,7 @@ static TiltedPhoques::Initializer s_actorHooks(
12551286

12561287
RealActorProcess = s_actorProcess.Get();
12571288
RealSetPosition = s_setPosition.Get();
1289+
RealRemoveSpell = s_removeSpell.Get();
12581290
FUNC_GetActorLocation = s_GetActorLocation.Get();
12591291
RealCharacterConstructor = s_characterCtor.Get();
12601292
RealCharacterConstructor2 = s_characterCtor2.Get();
@@ -1276,6 +1308,7 @@ static TiltedPhoques::Initializer s_actorHooks(
12761308

12771309
TP_HOOK(&RealActorProcess, HookActorProcess);
12781310
TP_HOOK(&RealSetPosition, HookSetPosition);
1311+
TP_HOOK(&RealRemoveSpell, HookRemoveSpell);
12791312
TP_HOOK(&RealCharacterConstructor, HookCharacterConstructor);
12801313
TP_HOOK(&RealCharacterConstructor2, HookCharacterConstructor2);
12811314
TP_HOOK(&RealForceState, HookForceState);

Code/client/Games/Skyrim/Actor.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ struct Actor : TESObjectREFR
250250
void StopCombat() noexcept;
251251
bool PlayIdle(TESIdleForm* apIdle) noexcept;
252252
void FixVampireLordModel() noexcept;
253+
bool RemoveSpell(MagicItem* apSpell) noexcept;
253254

254255
enum ActorFlags
255256
{

Code/client/Services/Generic/MagicService.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
#include <Events/SpellCastEvent.h>
77
#include <Events/InterruptCastEvent.h>
88
#include <Events/AddTargetEvent.h>
9+
#include <Events/RemoveSpellEvent.h>
10+
11+
#include <Messages/RemoveSpellRequest.h>
912

1013
#include <Messages/SpellCastRequest.h>
1114
#include <Messages/InterruptCastRequest.h>
@@ -40,6 +43,8 @@ MagicService::MagicService(World& aWorld, entt::dispatcher& aDispatcher, Transpo
4043
m_notifyInterruptCastConnection = m_dispatcher.sink<NotifyInterruptCast>().connect<&MagicService::OnNotifyInterruptCast>(this);
4144
m_addTargetEventConnection = m_dispatcher.sink<AddTargetEvent>().connect<&MagicService::OnAddTargetEvent>(this);
4245
m_notifyAddTargetConnection = m_dispatcher.sink<NotifyAddTarget>().connect<&MagicService::OnNotifyAddTarget>(this);
46+
m_removeSpellEventConnection = m_dispatcher.sink<RemoveSpellEvent>().connect<&MagicService::OnRemoveSpellEvent>(this);
47+
m_notifyRemoveSpell = m_dispatcher.sink<NotifyRemoveSpell>().connect<&MagicService::OnNotifyRemoveSpell>(this);
4348
}
4449

4550
void MagicService::OnUpdate(const UpdateEvent& acEvent) noexcept
@@ -409,6 +414,75 @@ void MagicService::OnNotifyAddTarget(const NotifyAddTarget& acMessage) noexcept
409414
spdlog::debug("Applied remote magic effect");
410415
}
411416

417+
void MagicService::OnRemoveSpellEvent(const RemoveSpellEvent& acEvent) noexcept
418+
{
419+
if (!m_transport.IsConnected())
420+
return;
421+
422+
RemoveSpellRequest request{};
423+
424+
if (!m_world.GetModSystem().GetServerModId(acEvent.SpellId, request.SpellId.ModId, request.SpellId.BaseId))
425+
{
426+
spdlog::error("{}: Could not find spell with form {:X}", __FUNCTION__, acEvent.SpellId);
427+
return;
428+
}
429+
430+
auto view = m_world.view<FormIdComponent>();
431+
const auto it = std::find_if(std::begin(view), std::end(view), [id = acEvent.TargetId, view](auto entity) {
432+
return view.get<FormIdComponent>(entity).Id == id;
433+
});
434+
435+
if (it == std::end(view))
436+
{
437+
spdlog::warn("Form id not found for magic remove target, form id: {:X}", acEvent.TargetId);
438+
return;
439+
}
440+
441+
std::optional<uint32_t> serverIdRes = Utils::GetServerId(*it);
442+
if (!serverIdRes.has_value())
443+
{
444+
spdlog::warn("Server id not found for magic remove target, form id: {:X}", acEvent.TargetId);
445+
return;
446+
}
447+
448+
request.TargetId = serverIdRes.value();
449+
450+
//spdlog::info(__FUNCTION__ ": requesting remove spell with base id {:X} from actor with server id {:X}", request.SpellId.BaseId, request.TargetId);
451+
452+
m_transport.Send(request);
453+
}
454+
455+
void MagicService::OnNotifyRemoveSpell(const NotifyRemoveSpell& acMessage) noexcept
456+
{
457+
uint32_t targetFormId = acMessage.TargetId;
458+
459+
Actor* pActor = Utils::GetByServerId<Actor>(acMessage.TargetId);
460+
if (!pActor)
461+
{
462+
spdlog::warn(__FUNCTION__ ": could not find actor server id {:X}", acMessage.TargetId);
463+
return;
464+
}
465+
466+
const uint32_t cSpellId = World::Get().GetModSystem().GetGameId(acMessage.SpellId);
467+
if (cSpellId == 0)
468+
{
469+
spdlog::error("{}: failed to retrieve spell id, GameId base: {:X}, mod: {:X}", __FUNCTION__,
470+
acMessage.SpellId.BaseId, acMessage.SpellId.ModId);
471+
return;
472+
}
473+
474+
MagicItem* pSpell = Cast<MagicItem>(TESForm::GetById(cSpellId));
475+
if (!pSpell)
476+
{
477+
spdlog::error("{}: Failed to retrieve spell by id {:X}", __FUNCTION__, cSpellId);
478+
return;
479+
}
480+
481+
// Remove the spell from the actor
482+
//spdlog::info(__FUNCTION__ ": removing spell with form id {:X} from actor with form id {:X}", cSpellId, targetFormId);
483+
pActor->RemoveSpell(pSpell);
484+
}
485+
412486
void MagicService::ApplyQueuedEffects() noexcept
413487
{
414488
static std::chrono::steady_clock::time_point lastSendTimePoint;

Code/client/Services/MagicService.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <Events/EventDispatcher.h>
55
#include <Messages/AddTargetRequest.h>
66
#include <Messages/NotifyAddTarget.h>
7+
#include <Messages/NotifyRemoveSpell.h>
78

89
struct World;
910
struct TransportService;
@@ -12,6 +13,7 @@ struct UpdateEvent;
1213
struct SpellCastEvent;
1314
struct InterruptCastEvent;
1415
struct AddTargetEvent;
16+
struct RemoveSpellEvent;
1517

1618
struct NotifySpellCast;
1719
struct NotifyInterruptCast;
@@ -68,6 +70,14 @@ struct MagicService
6870
* @brief Applies a magic effect based on a server message.
6971
*/
7072
void OnNotifyAddTarget(const NotifyAddTarget& acMessage) noexcept;
73+
/**
74+
* @brief Sends a message to remove a spell from a player.
75+
*/
76+
void OnRemoveSpellEvent(const RemoveSpellEvent& acEvent) noexcept;
77+
/*
78+
* @brief Handles removal of a spell
79+
*/
80+
void OnNotifyRemoveSpell(const NotifyRemoveSpell& acMessage) noexcept;
7181

7282
private:
7383
/**
@@ -103,4 +113,6 @@ struct MagicService
103113
entt::scoped_connection m_notifyInterruptCastConnection;
104114
entt::scoped_connection m_addTargetEventConnection;
105115
entt::scoped_connection m_notifyAddTargetConnection;
116+
entt::scoped_connection m_removeSpellEventConnection;
117+
entt::scoped_connection m_notifyRemoveSpell;
106118
};

Code/encoding/Messages/ClientMessageFactory.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <Messages/SpellCastRequest.h>
3434
#include <Messages/InterruptCastRequest.h>
3535
#include <Messages/AddTargetRequest.h>
36+
#include <Messages/RemoveSpellRequest.h>
3637
#include <Messages/ProjectileLaunchRequest.h>
3738
#include <Messages/ScriptAnimationRequest.h>
3839
#include <Messages/DrawWeaponRequest.h>
@@ -66,10 +67,10 @@ struct ClientMessageFactory
6667
{
6768
auto s_visitor = CreateMessageVisitor<
6869
AuthenticationRequest, AssignCharacterRequest, CancelAssignmentRequest, ClientReferencesMoveRequest, EnterInteriorCellRequest, RequestInventoryChanges, RequestFactionsChanges, RequestQuestUpdate, PartyInviteRequest, PartyAcceptInviteRequest, PartyLeaveRequest, PartyCreateRequest,
69-
PartyChangeLeaderRequest, PartyKickRequest, RequestActorValueChanges, RequestActorMaxValueChanges, EnterExteriorCellRequest, RequestHealthChangeBroadcast, ActivateRequest, LockChangeRequest, AssignObjectsRequest, RequestDeathStateChange, ShiftGridCellRequest, RequestOwnershipTransfer,
70-
RequestOwnershipClaim, RequestObjectInventoryChanges, SpellCastRequest, ProjectileLaunchRequest, InterruptCastRequest, AddTargetRequest, ScriptAnimationRequest, DrawWeaponRequest, MountRequest, NewPackageRequest, RequestRespawn, SyncExperienceRequest, RequestEquipmentChanges,
71-
SendChatMessageRequest, TeleportCommandRequest, PlayerRespawnRequest, DialogueRequest, SubtitleRequest, PlayerDialogueRequest, PlayerLevelRequest, TeleportRequest, RequestPlayerHealthUpdate, RequestWeatherChange, RequestCurrentWeather, RequestSetWaypoint, RequestRemoveWaypoint,
72-
SetTimeCommandRequest>;
70+
PartyChangeLeaderRequest, PartyKickRequest, RequestActorValueChanges, RequestActorMaxValueChanges, EnterExteriorCellRequest, RequestHealthChangeBroadcast, ActivateRequest, LockChangeRequest, AssignObjectsRequest, RequestDeathStateChange, ShiftGridCellRequest,
71+
RequestOwnershipTransfer, RequestOwnershipClaim, RequestObjectInventoryChanges, SpellCastRequest, ProjectileLaunchRequest, InterruptCastRequest, AddTargetRequest, ScriptAnimationRequest, DrawWeaponRequest, MountRequest, NewPackageRequest, RequestRespawn, SyncExperienceRequest,
72+
RequestEquipmentChanges, SendChatMessageRequest, TeleportCommandRequest, PlayerRespawnRequest, DialogueRequest, SubtitleRequest, PlayerDialogueRequest, PlayerLevelRequest, TeleportRequest, RequestPlayerHealthUpdate, RequestWeatherChange, RequestCurrentWeather, RequestSetWaypoint,
73+
RequestRemoveWaypoint, RemoveSpellRequest, SetTimeCommandRequest>;
7374

7475
return s_visitor(std::forward<T>(func));
7576
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include "NotifyRemoveSpell.h"
2+
3+
void NotifyRemoveSpell::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept
4+
{
5+
Serialization::WriteVarInt(aWriter, TargetId);
6+
SpellId.Serialize(aWriter);
7+
}
8+
9+
void NotifyRemoveSpell::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept
10+
{
11+
TargetId = Serialization::ReadVarInt(aReader);
12+
SpellId.Deserialize(aReader);
13+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#pragma once
2+
#include "Message.h"
3+
#include <Structs/GameId.h>
4+
5+
struct NotifyRemoveSpell final : ServerMessage
6+
{
7+
static constexpr ServerOpcode Opcode = kNotifyRemoveSpell;
8+
9+
NotifyRemoveSpell() : ServerMessage(Opcode)
10+
{
11+
}
12+
13+
void SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept override;
14+
15+
void DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept override;
16+
17+
bool operator==(const NotifyRemoveSpell& acRhs) const noexcept
18+
{
19+
return GetOpcode() == acRhs.GetOpcode() && TargetId == acRhs.TargetId && SpellId == acRhs.SpellId;
20+
}
21+
22+
uint32_t TargetId{};
23+
GameId SpellId{};
24+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include "EncodingPch.h"
2+
#include "RemoveSpellRequest.h"
3+
4+
void RemoveSpellRequest::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept
5+
{
6+
Serialization::WriteVarInt(aWriter, TargetId);
7+
SpellId.Serialize(aWriter);
8+
}
9+
10+
void RemoveSpellRequest::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept
11+
{
12+
TargetId = Serialization::ReadVarInt(aReader);
13+
SpellId.Deserialize(aReader);
14+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
#include "Message.h"
3+
#include <Structs/GameId.h>
4+
5+
struct RemoveSpellRequest final: ClientMessage
6+
{
7+
static constexpr ClientOpcode Opcode = kRequestRemoveSpell;
8+
RemoveSpellRequest() : ClientMessage(Opcode) {}
9+
virtual ~RemoveSpellRequest() = default;
10+
void SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept override;
11+
void DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept override;
12+
bool operator==(const RemoveSpellRequest& achRhs) const noexcept { return Opcode == achRhs.Opcode && TargetId == achRhs.TargetId && SpellId == achRhs.SpellId; }
13+
14+
uint32_t TargetId{};
15+
GameId SpellId{};
16+
};

0 commit comments

Comments
 (0)