Skip to content

Commit

Permalink
feat: heal other players through spells
Browse files Browse the repository at this point in the history
  • Loading branch information
RobbeBryssinck committed Jan 4, 2022
1 parent 4f81fcd commit be4436f
Show file tree
Hide file tree
Showing 14 changed files with 168 additions and 37 deletions.
6 changes: 3 additions & 3 deletions Code/client/Events/SpellCastEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ struct MagicItem;

struct SpellCastEvent
{
SpellCastEvent(ActorMagicCaster* apCaster, MagicItem* apSpell)
: pCaster(apCaster), pSpell(apSpell)
SpellCastEvent(ActorMagicCaster* apCaster, MagicItem* apSpell, uint32_t aDesiredTargetID)
: pCaster(apCaster), pSpell(apSpell), DesiredTargetID(aDesiredTargetID)
{}

ActorMagicCaster* pCaster;
MagicItem* pSpell;
// TODO: bool interrupt
uint32_t DesiredTargetID;
};
10 changes: 10 additions & 0 deletions Code/client/Games/Magic/MagicSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ enum CastingType : int32_t
CASTING_COUNT = 0x3,
};

enum Delivery : int32_t
{
SELF = 0x0,
TOUCH = 0x1,
AIMED = 0x2,
TARGET_ACTOR = 0x3,
TARGET_LOCATION = 0x4,
DELIVERY_COUNT = 0x5,
};

}

namespace EffectArchetypes
Expand Down
5 changes: 5 additions & 0 deletions Code/client/Games/Primitives.h
Original file line number Diff line number Diff line change
Expand Up @@ -332,5 +332,10 @@ struct BSPointerHandle
{
BSPointerHandle() = default;

operator bool() const
{
return handle.iBits != 0;
}

THandle handle{};
};
28 changes: 28 additions & 0 deletions Code/client/Games/Skyrim/Actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,33 @@ void Actor::RemoveFromAllFactions() noexcept
s_pRemoveFromAllFactions(this);
}

TP_THIS_FUNCTION(TInitiateMountPackage, bool, Actor, Actor* apMount);
static TInitiateMountPackage* RealInitiateMountPackage = nullptr;

bool Actor::InitiateMountPackage(Actor* apMount) noexcept
{
return ThisCall(RealInitiateMountPackage, this, apMount);
}

void Actor::GenerateMagicCasters() noexcept
{
if (!leftHandCaster)
{
MagicCaster* pCaster = GetMagicCaster(MagicSystem::CastingSource::LEFT_HAND);
leftHandCaster = RTTI_CAST(pCaster, MagicCaster, ActorMagicCaster);
}
if (!rightHandCaster)
{
MagicCaster* pCaster = GetMagicCaster(MagicSystem::CastingSource::RIGHT_HAND);
rightHandCaster = RTTI_CAST(pCaster, MagicCaster, ActorMagicCaster);
}
if (!shoutCaster)
{
MagicCaster* pCaster = GetMagicCaster(MagicSystem::CastingSource::OTHER);
shoutCaster = RTTI_CAST(pCaster, MagicCaster, ActorMagicCaster);
}
}

bool Actor::IsDead() noexcept
{
PAPYRUS_FUNCTION(bool, Actor, IsDead);
Expand Down Expand Up @@ -582,6 +609,7 @@ void TP_MAKE_THISCALL(HookApplyActorEffect, ActiveEffect, Actor* apTarget, float
const auto pExTarget = apTarget->GetExtension();
if (pExTarget->IsLocal())
{
spdlog::error("sending out new health");
World::Get().GetRunner().Trigger(HealthChangeEvent(apTarget->formID, aEffectValue));
return ThisCall(RealApplyActorEffect, apThis, apTarget, aEffectValue, unk1);
}
Expand Down
2 changes: 2 additions & 0 deletions Code/client/Games/Skyrim/Actor.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ struct Actor : TESObjectREFR
void UnEquipAll() noexcept;
void RemoveFromAllFactions() noexcept;
void QueueUpdate() noexcept;
bool InitiateMountPackage(Actor* apMount) noexcept;
void GenerateMagicCasters() noexcept;

bool IsDead() noexcept;
void Kill() noexcept;
Expand Down
1 change: 1 addition & 0 deletions Code/client/Games/Skyrim/Forms/SpellItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct SpellItem : MagicItem
uint32_t unk6C[5];
float castTime;
MagicSystem::CastingType eCastingType;
MagicSystem::Delivery eDelivery;
// more stuff
};

15 changes: 13 additions & 2 deletions Code/client/Games/Skyrim/Magic/ActorMagicCaster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,20 @@ void TP_MAKE_THISCALL(HookSpellCast, ActorMagicCaster, bool abSuccess, int32_t a
if (pActor->GetExtension()->IsRemote())
return;

World::Get().GetRunner().Trigger(SpellCastEvent(apThis, apSpell));
uint32_t targetFormId = 0;

spdlog::debug("HookSpellCast, abSuccess: {}, auiTargetCount: {}, apSpell: {:X}", abSuccess, auiTargetCount, (uint64_t)apSpell);
if (apThis->hDesiredTarget)
{
TESObjectREFR* pDesiredTarget = TESObjectREFR::GetByHandle(apThis->hDesiredTarget.handle.iBits);
if (pDesiredTarget)
{
targetFormId = pDesiredTarget->formID;
}
}

World::Get().GetRunner().Trigger(SpellCastEvent(apThis, apSpell, targetFormId));

spdlog::info("HookSpellCast, abSuccess: {}, auiTargetCount: {}, apSpell: {:X}, desired target: {:X}", abSuccess, auiTargetCount, (uint64_t)apSpell, targetFormId);

ThisCall(RealSpellCast, apThis, abSuccess, auiTargetCount, apSpell);
}
Expand Down
2 changes: 1 addition & 1 deletion Code/client/Games/Skyrim/Magic/MagicCaster.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ struct MagicCaster
};

GameArray<uint64_t> hSounds; // BSTArray<BSSoundHandle> hSounds;
int32_t hDesiredTarget; // BSPointerHandle<TESObjectREFR,BSUntypedPointerHandle<21,5> > hDesiredTarget;
BSPointerHandle<TESObjectREFR*> hDesiredTarget;
MagicItem* pCurrentSpell;
MagicCaster::State eState;
float fCastingTimer;
Expand Down
16 changes: 15 additions & 1 deletion Code/client/Games/Skyrim/Magic/MagicTarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,26 @@ bool MagicTarget::AddTarget(AddTargetData& arData) noexcept
bool TP_MAKE_THISCALL(HookAddTarget, MagicTarget, MagicTarget::AddTargetData& arData)
{
// TODO: this can be fixed by properly implementing multiple inheritance
Actor* pTargetActor = (Actor*)((char*)apThis - 0x98);
Actor* pTargetActor = (Actor*)((uint8_t*)apThis - 0x98);
ActorExtension* pTargetActorEx = pTargetActor->GetExtension();

if (!pTargetActorEx)
return ThisCall(RealAddTarget, apThis, arData);

if (pTargetActorEx->IsRemotePlayer() && arData.pCaster)
{
ActorExtension* pCasterExtension = arData.pCaster->GetExtension();
if (pCasterExtension->IsLocalPlayer())
{
if (arData.pEffectItem->pEffectSetting->eArchetype == EffectArchetypes::ArchetypeID::VALUE_MODIFIER &&
arData.pEffectItem->data.fMagnitude > 0.0f)
{
spdlog::warn("sending out healing effect");
World::Get().GetRunner().Trigger(AddTargetEvent(pTargetActor->formID, arData.pSpell->formID));
}
}
}

if (pTargetActorEx->IsLocalPlayer())
{
bool result = ThisCall(RealAddTarget, apThis, arData);
Expand Down
98 changes: 72 additions & 26 deletions Code/client/Services/Generic/MagicService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@

#include <Actor.h>
#include <Magic/ActorMagicCaster.h>
#include <Games/ActorExtension.h>
#if TP_SKYRIM64
#include <Forms/SpellItem.h>
#include <PlayerCharacter.h>
#endif

MagicService::MagicService(World& aWorld, entt::dispatcher& aDispatcher, TransportService& aTransport) noexcept
Expand Down Expand Up @@ -101,10 +103,12 @@ void MagicService::OnSpellCastEvent(const SpellCastEvent& acSpellCastEvent) cons
}

// only sync concentration spells through spell cast sync, the rest through projectile sync
// TODO: not all fire and forget spells have a projectile (i.e. heal other)
if (auto* pSpell = RTTI_CAST(acSpellCastEvent.pSpell, MagicItem, SpellItem))
{
if (pSpell->eCastingType != MagicSystem::CastingType::CONCENTRATION)
if (pSpell->eCastingType != MagicSystem::CastingType::CONCENTRATION && pSpell->eDelivery == MagicSystem::Delivery::AIMED)
{
spdlog::warn("Canceled magic spell");
return;
}
}
Expand All @@ -122,15 +126,33 @@ void MagicService::OnSpellCastEvent(const SpellCastEvent& acSpellCastEvent) cons

auto& localComponent = view.get<LocalComponent>(*casterEntityIt);

SpellCastRequest request;
SpellCastRequest request{};
request.CasterId = localComponent.Id;
request.CastingSource = acSpellCastEvent.pCaster->GetCastingSource();
request.IsDualCasting = acSpellCastEvent.pCaster->GetIsDualCasting();
m_world.GetModSystem().GetServerModId(acSpellCastEvent.pSpell->formID, request.SpellFormId.ModId,
request.SpellFormId.BaseId);
if (acSpellCastEvent.DesiredTargetID != 0)
{
auto targetView = m_world.view<FormIdComponent>();
const auto targetEntityIt = std::find_if(std::begin(targetView), std::end(targetView), [id = acSpellCastEvent.DesiredTargetID, targetView](entt::entity entity)
{
return targetView.get<FormIdComponent>(entity).Id == id;
});

spdlog::info("Spell cast event sent, ID: {:X}, Source: {}, IsDualCasting: {}", request.CasterId,
request.CastingSource, request.IsDualCasting);
if (targetEntityIt != std::end(targetView))
{
auto desiredTargetIdRes = utils::GetServerId(*targetEntityIt);
if (desiredTargetIdRes.has_value())
{
spdlog::info("has_value");
request.DesiredTarget = desiredTargetIdRes.value();
}
}
}

spdlog::info("Spell cast event sent, ID: {:X}, Source: {}, IsDualCasting: {}, desired target: {:X}", request.CasterId,
request.CastingSource, request.IsDualCasting, request.DesiredTarget);

m_transport.Send(request);
#endif
Expand All @@ -152,16 +174,12 @@ void MagicService::OnNotifySpellCast(const NotifySpellCast& acMessage) const noe
}

auto formIdComponent = remoteView.get<FormIdComponent>(*remoteIt);
TESForm* pForm = TESForm::GetById(formIdComponent.Id);
TP_ASSERT(pForm, "Form not found.");
Actor* pActor = RTTI_CAST(pForm, TESForm, Actor);
TP_ASSERT(pActor, "Form is not actor.");

auto* pForm = TESForm::GetById(formIdComponent.Id);
auto* pActor = RTTI_CAST(pForm, TESForm, Actor);

if (!pActor->leftHandCaster)
pActor->leftHandCaster = (ActorMagicCaster*)pActor->GetMagicCaster(MagicSystem::CastingSource::LEFT_HAND);
if (!pActor->rightHandCaster)
pActor->rightHandCaster = (ActorMagicCaster*)pActor->GetMagicCaster(MagicSystem::CastingSource::RIGHT_HAND);
if (!pActor->shoutCaster)
pActor->shoutCaster = (ActorMagicCaster*)pActor->GetMagicCaster(MagicSystem::CastingSource::OTHER);
pActor->GenerateMagicCasters();

// Only left hand casters need dual casting (?)
pActor->leftHandCaster->SetDualCasting(acMessage.IsDualCasting);
Expand All @@ -171,42 +189,70 @@ void MagicService::OnNotifySpellCast(const NotifySpellCast& acMessage) const noe
if (acMessage.CastingSource >= 4)
{
spdlog::warn("Casting source out of bounds, trying form id");
}
else
{
pSpell = pActor->magicItems[acMessage.CastingSource];
}

if (!pSpell)
{
const uint32_t cSpellFormId = World::Get().GetModSystem().GetGameId(acMessage.SpellFormId);
if (cSpellFormId == 0)
{
spdlog::error("Could not find spell form id for GameId base {:X}, mod {:X}", acMessage.SpellFormId.BaseId,
acMessage.SpellFormId.ModId);
return;
}
auto* pSpellForm = TESForm::GetById(cSpellFormId);
TESForm* pSpellForm = TESForm::GetById(cSpellFormId);
if (!pSpellForm)
{
spdlog::error("Cannot find spell form");
spdlog::error("Cannot find spell form.");
return;
}
else
pSpell = RTTI_CAST(pSpellForm, TESForm, MagicItem);
}
else
{
pSpell = pActor->magicItems[acMessage.CastingSource];
}

if (!pSpell)
{
spdlog::error("Could not find spell.");
return;
}

TESForm* pDesiredTarget = nullptr;

if (acMessage.DesiredTarget != 0)
{
auto view = m_world.view<FormIdComponent>();
for (auto entity : view)
{
std::optional<uint32_t> serverIdRes = utils::GetServerId(entity);
if (!serverIdRes.has_value())
continue;

uint32_t serverId = serverIdRes.value();

if (serverId == acMessage.DesiredTarget)
{
const auto& formIdComponent = view.get<FormIdComponent>(entity);
pDesiredTarget = TESForm::GetById(formIdComponent.Id);
if (pDesiredTarget)
spdlog::info("Desired target: {:X}", pDesiredTarget->formID);
}
}

}

switch (acMessage.CastingSource)
{
case MagicSystem::CastingSource::LEFT_HAND:
pActor->leftHandCaster->CastSpellImmediate(pSpell, false, nullptr, 1.0f, false, 0.0f);
pActor->leftHandCaster->CastSpellImmediate(pSpell, false, (TESObjectREFR*)pDesiredTarget, 1.0f, false, 0.0f);
break;
case MagicSystem::CastingSource::RIGHT_HAND:
pActor->rightHandCaster->CastSpellImmediate(pSpell, false, nullptr, 1.0f, false, 0.0f);
pActor->rightHandCaster->CastSpellImmediate(pSpell, false, (TESObjectREFR*)pDesiredTarget, 1.0f, false, 0.0f);
break;
case MagicSystem::CastingSource::OTHER:
pActor->shoutCaster->CastSpellImmediate(pSpell, false, nullptr, 1.0f, false, 0.0f);
pActor->shoutCaster->CastSpellImmediate(pSpell, false, (TESObjectREFR*)pDesiredTarget, 1.0f, false, 0.0f);
break;
case MagicSystem::CastingSource::INSTANT:
// TODO: instant?
break;
}
#endif
Expand Down
7 changes: 7 additions & 0 deletions Code/client/Services/Generic/TestService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ void __declspec(noinline) TestService::PlaceActorInWorld() noexcept

pActor->SetInventory(PlayerCharacter::Get()->GetInventory());

pActor->GetExtension()->SetPlayer(true);
pActor->GetExtension()->SetRemote(true);

m_actors.emplace_back(pActor);
}

Expand Down Expand Up @@ -181,8 +184,12 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept
{
s_f8Pressed = true;

PlaceActorInWorld();

/*
Actor* pActor = (Actor*)TESForm::GetById(0xFF0015AD);
PlayerCharacter::Get()->InitiateMountPackage(pActor);
auto* pActor = (Actor*)TESForm::GetById(0xFF000DA5);
pActor->SetWeaponDrawnEx(true);
Expand Down
10 changes: 8 additions & 2 deletions Code/server/GameServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ void GameServer::SendToPlayers(const ServerMessage& acServerMessage) const
}
}

void GameServer::SendToPlayersInRange(const ServerMessage& acServerMessage, const entt::entity acOrigin) const
void GameServer::SendToPlayersInRange(const ServerMessage& acServerMessage, const entt::entity acOrigin, Player* apExcluded) const
{
const auto* cellIdComp = m_pWorld->try_get<CellIdComponent>(acOrigin);
const auto* ownerComp = m_pWorld->try_get<OwnerComponent>(acOrigin);
Expand All @@ -271,7 +271,13 @@ void GameServer::SendToPlayersInRange(const ServerMessage& acServerMessage, cons

for (auto pPlayer : m_pWorld->GetPlayerManager())
{
if (ownerComp->GetOwner() == pPlayer)
// TODO: this is a hack
if (apExcluded)
{
if (apExcluded == pPlayer)
continue;
}
else if (ownerComp->GetOwner() == pPlayer)
continue;

if (cellIdComp->IsInRange(pPlayer->GetCellComponent()))
Expand Down
2 changes: 1 addition & 1 deletion Code/server/GameServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct GameServer final : Server
void Send(ConnectionId_t aConnectionId, const ServerAdminMessage& acServerMessage) const;
void SendToLoaded(const ServerMessage& acServerMessage) const;
void SendToPlayers(const ServerMessage& acServerMessage) const;
void SendToPlayersInRange(const ServerMessage& acServerMessage, const entt::entity acOrigin) const;
void SendToPlayersInRange(const ServerMessage& acServerMessage, const entt::entity acOrigin, Player* apExcluded = nullptr) const;

const String& GetName() const noexcept;

Expand Down
Loading

0 comments on commit be4436f

Please sign in to comment.