diff --git a/Code/client/Events/SpellCastEvent.h b/Code/client/Events/SpellCastEvent.h index c411ff8bd..64476b0d9 100644 --- a/Code/client/Events/SpellCastEvent.h +++ b/Code/client/Events/SpellCastEvent.h @@ -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; }; diff --git a/Code/client/Games/Magic/MagicSystem.h b/Code/client/Games/Magic/MagicSystem.h index 7bdf76d27..beacc27b5 100644 --- a/Code/client/Games/Magic/MagicSystem.h +++ b/Code/client/Games/Magic/MagicSystem.h @@ -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 diff --git a/Code/client/Games/Primitives.h b/Code/client/Games/Primitives.h index 5ab343024..86499dc0b 100644 --- a/Code/client/Games/Primitives.h +++ b/Code/client/Games/Primitives.h @@ -332,5 +332,10 @@ struct BSPointerHandle { BSPointerHandle() = default; + operator bool() const + { + return handle.iBits != 0; + } + THandle handle{}; }; diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index 1eb8d5129..b74b4e0a0 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -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); @@ -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); } diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index 6851e6b41..0551a5fe5 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -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; diff --git a/Code/client/Games/Skyrim/Forms/SpellItem.h b/Code/client/Games/Skyrim/Forms/SpellItem.h index 6163c22ea..a82819c28 100644 --- a/Code/client/Games/Skyrim/Forms/SpellItem.h +++ b/Code/client/Games/Skyrim/Forms/SpellItem.h @@ -14,6 +14,7 @@ struct SpellItem : MagicItem uint32_t unk6C[5]; float castTime; MagicSystem::CastingType eCastingType; + MagicSystem::Delivery eDelivery; // more stuff }; diff --git a/Code/client/Games/Skyrim/Magic/ActorMagicCaster.cpp b/Code/client/Games/Skyrim/Magic/ActorMagicCaster.cpp index c9269075f..f5991f13a 100644 --- a/Code/client/Games/Skyrim/Magic/ActorMagicCaster.cpp +++ b/Code/client/Games/Skyrim/Magic/ActorMagicCaster.cpp @@ -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); } diff --git a/Code/client/Games/Skyrim/Magic/MagicCaster.h b/Code/client/Games/Skyrim/Magic/MagicCaster.h index b606f301c..fd3afdb99 100644 --- a/Code/client/Games/Skyrim/Magic/MagicCaster.h +++ b/Code/client/Games/Skyrim/Magic/MagicCaster.h @@ -67,7 +67,7 @@ struct MagicCaster }; GameArray hSounds; // BSTArray hSounds; - int32_t hDesiredTarget; // BSPointerHandle > hDesiredTarget; + BSPointerHandle hDesiredTarget; MagicItem* pCurrentSpell; MagicCaster::State eState; float fCastingTimer; diff --git a/Code/client/Games/Skyrim/Magic/MagicTarget.cpp b/Code/client/Games/Skyrim/Magic/MagicTarget.cpp index 15f122612..3281a037e 100644 --- a/Code/client/Games/Skyrim/Magic/MagicTarget.cpp +++ b/Code/client/Games/Skyrim/Magic/MagicTarget.cpp @@ -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); diff --git a/Code/client/Services/Generic/MagicService.cpp b/Code/client/Services/Generic/MagicService.cpp index 070ecf684..782abd0fa 100644 --- a/Code/client/Services/Generic/MagicService.cpp +++ b/Code/client/Services/Generic/MagicService.cpp @@ -17,8 +17,10 @@ #include #include +#include #if TP_SKYRIM64 #include +#include #endif MagicService::MagicService(World& aWorld, entt::dispatcher& aDispatcher, TransportService& aTransport) noexcept @@ -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; } } @@ -122,15 +126,33 @@ void MagicService::OnSpellCastEvent(const SpellCastEvent& acSpellCastEvent) cons auto& localComponent = view.get(*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(); + const auto targetEntityIt = std::find_if(std::begin(targetView), std::end(targetView), [id = acSpellCastEvent.DesiredTargetID, targetView](entt::entity entity) + { + return targetView.get(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 @@ -152,16 +174,12 @@ void MagicService::OnNotifySpellCast(const NotifySpellCast& acMessage) const noe } auto formIdComponent = remoteView.get(*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); @@ -171,14 +189,6 @@ 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) { @@ -186,27 +196,63 @@ void MagicService::OnNotifySpellCast(const NotifySpellCast& acMessage) const noe 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(); + for (auto entity : view) + { + std::optional serverIdRes = utils::GetServerId(entity); + if (!serverIdRes.has_value()) + continue; + + uint32_t serverId = serverIdRes.value(); + + if (serverId == acMessage.DesiredTarget) + { + const auto& formIdComponent = view.get(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 diff --git a/Code/client/Services/Generic/TestService.cpp b/Code/client/Services/Generic/TestService.cpp index 76a3ad3ef..e16760890 100644 --- a/Code/client/Services/Generic/TestService.cpp +++ b/Code/client/Services/Generic/TestService.cpp @@ -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); } @@ -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); diff --git a/Code/server/GameServer.cpp b/Code/server/GameServer.cpp index c0eb419ca..a2c294bab 100644 --- a/Code/server/GameServer.cpp +++ b/Code/server/GameServer.cpp @@ -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(acOrigin); const auto* ownerComp = m_pWorld->try_get(acOrigin); @@ -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())) diff --git a/Code/server/GameServer.h b/Code/server/GameServer.h index 2d816b033..37eba6315 100644 --- a/Code/server/GameServer.h +++ b/Code/server/GameServer.h @@ -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; diff --git a/Code/server/Services/MagicService.cpp b/Code/server/Services/MagicService.cpp index 1a844191d..cc43ad47c 100644 --- a/Code/server/Services/MagicService.cpp +++ b/Code/server/Services/MagicService.cpp @@ -30,6 +30,7 @@ void MagicService::OnSpellCastRequest(const PacketEvent& acMes notify.SpellFormId = message.SpellFormId; notify.CastingSource = message.CastingSource; notify.IsDualCasting = message.IsDualCasting; + notify.DesiredTarget = message.DesiredTarget; const entt::entity cCasterEntity = static_cast(message.CasterId); GameServer::Get()->SendToPlayersInRange(notify, cCasterEntity); @@ -55,6 +56,6 @@ void MagicService::OnAddTargetRequest(const PacketEvent& acMes notify.SpellId = message.SpellId; const entt::entity cTargetEntity = static_cast(message.TargetId); - GameServer::Get()->SendToPlayersInRange(notify, cTargetEntity); + GameServer::Get()->SendToPlayersInRange(notify, cTargetEntity, acMessage.pPlayer); }