mirror of
https://github.com/Jous99/F4MP.git
synced 2026-01-13 00:20:54 +01:00
1335 lines
42 KiB
C++
1335 lines
42 KiB
C++
#include <Games/References.h>
|
|
#include <Games/Skyrim/EquipManager.h>
|
|
#include <AI/AIProcess.h>
|
|
#include <Misc/MiddleProcess.h>
|
|
#include <Misc/GameVM.h>
|
|
#include <DefaultObjectManager.h>
|
|
#include <Forms/TESNPC.h>
|
|
#include <Forms/TESFaction.h>
|
|
#include <Components/TESActorBaseData.h>
|
|
#include <ExtraData/ExtraFactionChanges.h>
|
|
#include <Games/Memory.h>
|
|
#include <Forms/TESLevItem.h>
|
|
#include <Combat/CombatController.h>
|
|
|
|
#include <Events/HealthChangeEvent.h>
|
|
#include <Events/InventoryChangeEvent.h>
|
|
#include <Events/MountEvent.h>
|
|
#include <Events/DialogueEvent.h>
|
|
#include <Events/HitEvent.h>
|
|
#include <Events/RemoveSpellEvent.h>
|
|
|
|
#include <Games/TES.h>
|
|
#include <World.h>
|
|
#include <Services/PapyrusService.h>
|
|
|
|
#include <Forms/ActorValueInfo.h>
|
|
#include <Forms/TESRace.h>
|
|
|
|
#include <Effects/ValueModifierEffect.h>
|
|
|
|
#include <Games/Skyrim/Misc/InventoryEntry.h>
|
|
#include <Games/Skyrim/ExtraData/ExtraCount.h>
|
|
#include <Games/Misc/ActorKnowledge.h>
|
|
|
|
#include <ExtraData/ExtraDataList.h>
|
|
#include <ExtraData/ExtraCharge.h>
|
|
#include <ExtraData/ExtraCount.h>
|
|
#include <ExtraData/ExtraEnchantment.h>
|
|
#include <ExtraData/ExtraHealth.h>
|
|
#include <ExtraData/ExtraPoison.h>
|
|
#include <ExtraData/ExtraSoul.h>
|
|
#include <ExtraData/ExtraTextDisplayData.h>
|
|
#include <Forms/EnchantmentItem.h>
|
|
#include <Forms/AlchemyItem.h>
|
|
#include <Forms/TESObjectCELL.h>
|
|
|
|
#include <Structs/Skyrim/AnimationGraphDescriptor_BHR_Master.h>
|
|
|
|
#include <Games/Overrides.h>
|
|
#include <Games/Skyrim/BSAnimationGraphManager.h>
|
|
#include <Havok/hkbStateMachine.h>
|
|
#include <Havok/hkbBehaviorGraph.h>
|
|
#include <Forms/BGSOutfit.h>
|
|
#include <Forms/TESObjectARMO.h>
|
|
|
|
#include <ModCompat/BehaviorVar.h>
|
|
|
|
#ifdef SAVE_STUFF
|
|
|
|
#include <Games/Skyrim/SaveLoad.h>
|
|
#include "Actor.h"
|
|
|
|
void Actor::Save_Reversed(const uint32_t aChangeFlags, Buffer::Writer& aWriter)
|
|
{
|
|
BGSSaveFormBuffer buffer;
|
|
|
|
Save(&buffer);
|
|
|
|
AIProcess* pProcess = currentProcess;
|
|
const int32_t handlerId = pProcess != nullptr ? pProcess->handlerId : -1;
|
|
|
|
aWriter.WriteBytes((uint8_t*)&handlerId, 4); // TODO: is this needed ?
|
|
aWriter.WriteBytes((uint8_t*)&flags1, 4);
|
|
|
|
// if (!handlerId
|
|
// && (uint8_t)AIProcess::GetBoolInSubStructure(pProcess))
|
|
// {
|
|
// Actor::SaveSkinFar(this);
|
|
// }
|
|
|
|
TESObjectREFR::Save_Reversed(aChangeFlags, aWriter);
|
|
|
|
if (pProcess)
|
|
; // Skyrim saves the process manager state, but we don't give a shit so skip !
|
|
|
|
aWriter.WriteBytes((uint8_t*)&unk194, 4);
|
|
aWriter.WriteBytes((uint8_t*)&headTrackingUpdateDelay, 4);
|
|
aWriter.WriteBytes((uint8_t*)&unk9C, 4);
|
|
// We skip 0x180 as it's not something we care about, some timer related data
|
|
|
|
aWriter.WriteBytes((uint8_t*)&unk98, 4);
|
|
// skip A8 - related to timers
|
|
// skip AC - related to timers as well
|
|
aWriter.WriteBytes((uint8_t*)&unkB0, 4);
|
|
// skip E4 - never seen this used
|
|
// skip E8 - same as E4
|
|
aWriter.WriteBytes((uint8_t*)&unk84, 4);
|
|
aWriter.WriteBytes((uint8_t*)&unkA4, 4);
|
|
// skip baseForm->weight
|
|
// skip 12C
|
|
|
|
// Save actor state sub_6F0FB0
|
|
}
|
|
|
|
#endif
|
|
|
|
TP_THIS_FUNCTION(TRemoveSpell, bool, Actor, MagicItem*);
|
|
TP_THIS_FUNCTION(TCharacterConstructor, Actor*, Actor);
|
|
TP_THIS_FUNCTION(TCharacterConstructor2, Actor*, Actor, uint8_t aUnk);
|
|
TP_THIS_FUNCTION(TCharacterDestructor, Actor*, Actor);
|
|
TP_THIS_FUNCTION(TAddInventoryItem, void, Actor, TESBoundObject* apItem, ExtraDataList* apExtraData, int32_t aCount, TESObjectREFR* apOldOwner);
|
|
TP_THIS_FUNCTION(TPickUpObject, void*, Actor, TESObjectREFR* apObject, int32_t aCount, bool aUnk1, float aUnk2);
|
|
TP_THIS_FUNCTION(TDropObject, void*, Actor, void* apResult, TESBoundObject* apObject, ExtraDataList* apExtraData, int32_t aCount, NiPoint3* apLocation, NiPoint3* apRotation);
|
|
TP_THIS_FUNCTION(TSetPosition, char, Actor, NiPoint3& acPosition);
|
|
TP_THIS_FUNCTION(TActorProcess, char, Actor, float aValue);
|
|
|
|
using TGetLocation = TESForm*(TESForm*);
|
|
static TGetLocation* FUNC_GetActorLocation;
|
|
|
|
TCharacterConstructor* RealCharacterConstructor;
|
|
TCharacterConstructor2* RealCharacterConstructor2;
|
|
TCharacterDestructor* RealCharacterDestructor;
|
|
|
|
static TRemoveSpell* RealRemoveSpell = nullptr;
|
|
static TAddInventoryItem* RealAddInventoryItem = nullptr;
|
|
static TPickUpObject* RealPickUpObject = nullptr;
|
|
static TDropObject* RealDropObject = nullptr;
|
|
static TSetPosition* RealSetPosition = nullptr;
|
|
static TActorProcess* RealActorProcess = nullptr;
|
|
|
|
float Actor::GetSpeed() noexcept
|
|
{
|
|
static BSFixedString speedSampledStr("SpeedSampled");
|
|
float speed = 0.f;
|
|
animationGraphHolder.GetVariableFloat(&speedSampledStr, &speed);
|
|
|
|
return speed;
|
|
}
|
|
|
|
void Actor::SetSpeed(float aSpeed) noexcept
|
|
{
|
|
static BSFixedString speedSampledStr("SpeedSampled");
|
|
animationGraphHolder.SetVariableFloat(&speedSampledStr, aSpeed);
|
|
}
|
|
|
|
uint16_t Actor::GetLevel() const noexcept
|
|
{
|
|
TP_THIS_FUNCTION(TGetLevel, uint16_t, const Actor);
|
|
POINTER_SKYRIMSE(TGetLevel, s_getLevel, 37334);
|
|
return TiltedPhoques::ThisCall(s_getLevel, this);
|
|
}
|
|
|
|
void Actor::ForcePosition(const NiPoint3& acPosition) noexcept
|
|
{
|
|
ScopedReferencesOverride recursionGuard;
|
|
|
|
// It just works TM
|
|
SetPosition(acPosition, true);
|
|
}
|
|
|
|
void Actor::QueueUpdate() noexcept
|
|
{
|
|
auto* pSetting = INISettingCollection::Get()->GetSetting("bUseFaceGenPreprocessedHeads:General");
|
|
const auto originalValue = pSetting->data;
|
|
pSetting->data = 0;
|
|
|
|
TP_THIS_FUNCTION(TQueueUpdate, void, Actor, bool);
|
|
POINTER_SKYRIMSE(TQueueUpdate, QueueUpdate, 40255);
|
|
|
|
TiltedPhoques::ThisCall(QueueUpdate, this, true);
|
|
|
|
pSetting->data = originalValue;
|
|
}
|
|
|
|
GamePtr<Actor> Actor::Create(TESNPC* apBaseForm) noexcept
|
|
{
|
|
auto pActor = New();
|
|
// Prevent saving
|
|
pActor->SetSkipSaveFlag(true);
|
|
pActor->GetExtension()->SetRemote(true);
|
|
|
|
const auto pPlayer = static_cast<Actor*>(GetById(0x14));
|
|
auto pCell = pPlayer->parentCell;
|
|
const auto pWorldSpace = pPlayer->GetWorldSpace();
|
|
|
|
pActor->SetLevelMod(4);
|
|
pActor->MarkChanged(0x40000000);
|
|
pActor->SetParentCell(pCell);
|
|
pActor->SetBaseForm(apBaseForm);
|
|
|
|
auto position = pPlayer->position;
|
|
auto rotation = pPlayer->rotation;
|
|
|
|
if (pCell && !(pCell->cellFlags[0] & 1))
|
|
pCell = nullptr;
|
|
|
|
ModManager::Get()->Spawn(position, rotation, pCell, pWorldSpace, pActor);
|
|
|
|
pActor->ForcePosition(position);
|
|
|
|
pActor->GetMagicCaster(MagicSystem::CastingSource::LEFT_HAND);
|
|
pActor->GetMagicCaster(MagicSystem::CastingSource::RIGHT_HAND);
|
|
pActor->GetMagicCaster(MagicSystem::CastingSource::OTHER);
|
|
|
|
pActor->flags &= 0xFFDFFFFF;
|
|
|
|
return pActor;
|
|
}
|
|
|
|
GamePtr<Actor> Actor::Spawn(uint32_t aBaseFormId) noexcept
|
|
{
|
|
TESNPC* pNpc = Cast<TESNPC>(TESForm::GetById(aBaseFormId));
|
|
return Actor::Create(pNpc);
|
|
}
|
|
|
|
void Actor::SetLevelMod(uint32_t aLevel) noexcept
|
|
{
|
|
TP_THIS_FUNCTION(TActorSetLevelMod, void, ExtraDataList, uint32_t);
|
|
POINTER_SKYRIMSE(TActorSetLevelMod, realSetLevelMod, 11806);
|
|
|
|
const auto pExtraDataList = &extraData;
|
|
|
|
TiltedPhoques::ThisCall(realSetLevelMod, pExtraDataList, aLevel);
|
|
}
|
|
|
|
ActorExtension* Actor::GetExtension() noexcept
|
|
{
|
|
if (AsExActor())
|
|
{
|
|
return static_cast<ActorExtension*>(AsExActor());
|
|
}
|
|
|
|
if (AsExPlayerCharacter())
|
|
{
|
|
return static_cast<ActorExtension*>(AsExPlayerCharacter());
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ExActor* Actor::AsExActor() noexcept
|
|
{
|
|
if (formType == Type && this != PlayerCharacter::Get())
|
|
return static_cast<ExActor*>(this);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ExPlayerCharacter* Actor::AsExPlayerCharacter() noexcept
|
|
{
|
|
if (this == PlayerCharacter::Get())
|
|
return static_cast<ExPlayerCharacter*>(this);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
extern thread_local bool g_forceAnimation;
|
|
|
|
void Actor::SetWeaponDrawnEx(bool aDraw) noexcept
|
|
{
|
|
spdlog::debug("Setting weapon drawn: {:X}:{}, current state: {}", formID, aDraw, actorState.IsWeaponDrawn());
|
|
|
|
if (actorState.IsWeaponDrawn() == aDraw)
|
|
{
|
|
actorState.SetWeaponDrawn(!aDraw);
|
|
|
|
spdlog::debug("Setting weapon drawn after update: {:X}:{}, current state: {}", formID, aDraw, actorState.IsWeaponDrawn());
|
|
}
|
|
|
|
g_forceAnimation = true;
|
|
SetWeaponDrawn(aDraw);
|
|
g_forceAnimation = false;
|
|
}
|
|
|
|
static thread_local bool s_execInitPackage = false;
|
|
|
|
void Actor::SetPackage(TESPackage* apPackage) noexcept
|
|
{
|
|
s_execInitPackage = true;
|
|
PutCreatedPackage(apPackage);
|
|
s_execInitPackage = false;
|
|
}
|
|
|
|
void Actor::SetPlayerRespawnMode(bool aSet) noexcept
|
|
{
|
|
SetEssentialEx(aSet);
|
|
// Makes the player go in an unrecoverable bleedout state
|
|
SetNoBleedoutRecovery(aSet);
|
|
|
|
if (formID != 0x14)
|
|
{
|
|
auto pPlayerFaction = Cast<TESFaction>(TESForm::GetById(0xDB1));
|
|
SetFactionRank(pPlayerFaction, 1);
|
|
}
|
|
}
|
|
|
|
void Actor::SetEssentialEx(bool aSet) noexcept
|
|
{
|
|
SetEssential(aSet);
|
|
TESNPC* pBase = Cast<TESNPC>(baseForm);
|
|
if (pBase)
|
|
pBase->actorData.SetEssential(aSet);
|
|
}
|
|
|
|
void Actor::SetNoBleedoutRecovery(bool aSet) noexcept
|
|
{
|
|
TP_THIS_FUNCTION(TSetNoBleedoutRecovery, void, Actor, bool);
|
|
POINTER_SKYRIMSE(TSetNoBleedoutRecovery, s_setNoBleedoutRecovery, 38533);
|
|
TiltedPhoques::ThisCall(s_setNoBleedoutRecovery, this, aSet);
|
|
}
|
|
|
|
void Actor::DispelAllSpells(bool aNow) noexcept
|
|
{
|
|
magicTarget.DispelAllSpells(aNow);
|
|
}
|
|
|
|
bool Actor::IsInCombat() const noexcept
|
|
{
|
|
PAPYRUS_FUNCTION(bool, Actor, IsInCombat);
|
|
return s_pIsInCombat(this);
|
|
}
|
|
|
|
Actor* Actor::GetCombatTarget() const noexcept
|
|
{
|
|
PAPYRUS_FUNCTION(Actor*, Actor, GetCombatTarget);
|
|
return s_pGetCombatTarget(this);
|
|
}
|
|
|
|
// TODO: this is a really hacky solution.
|
|
// The internal targeting system should be disabled instead.
|
|
void Actor::StartCombatEx(Actor* apTarget) noexcept
|
|
{
|
|
if (GetCombatTarget() != apTarget)
|
|
{
|
|
StopCombat();
|
|
StartCombat(apTarget);
|
|
}
|
|
}
|
|
|
|
void Actor::SetCombatTargetEx(Actor* apTarget) noexcept
|
|
{
|
|
if (pCombatController)
|
|
pCombatController->SetTarget(apTarget);
|
|
}
|
|
|
|
void Actor::StartCombat(Actor* apTarget) noexcept
|
|
{
|
|
PAPYRUS_FUNCTION(void, Actor, StartCombat, Actor*);
|
|
s_pStartCombat(this, apTarget);
|
|
}
|
|
|
|
void Actor::StopCombat() noexcept
|
|
{
|
|
PAPYRUS_FUNCTION(void, Actor, StopCombat);
|
|
s_pStopCombat(this);
|
|
}
|
|
|
|
bool Actor::RemoveSpell(MagicItem* apSpell) noexcept
|
|
{
|
|
if (!apSpell)
|
|
{
|
|
spdlog::error(__FUNCTION__ ": apSpell is null");
|
|
return false;
|
|
}
|
|
// spdlog::info(__FUNCTION__ ": removing: {} from actor: {}", apSpell->formID, formID);
|
|
return TiltedPhoques::ThisCall(RealRemoveSpell, this, apSpell);
|
|
}
|
|
|
|
bool Actor::HasPerk(uint32_t aPerkFormId) const noexcept
|
|
{
|
|
return GetPerkRank(aPerkFormId) != 0;
|
|
}
|
|
|
|
uint8_t Actor::GetPerkRank(uint32_t aPerkFormId) const noexcept
|
|
{
|
|
BGSPerk* pPerk = Cast<BGSPerk>(TESForm::GetById(aPerkFormId));
|
|
if (!pPerk)
|
|
return 0;
|
|
|
|
TP_THIS_FUNCTION(TGetPerkRank, uint8_t, const Actor, BGSPerk*);
|
|
POINTER_SKYRIMSE(TGetPerkRank, getPerkRank, 37698);
|
|
|
|
return TiltedPhoques::ThisCall(getPerkRank, this, pPerk);
|
|
}
|
|
|
|
bool TP_MAKE_THISCALL(HookRemoveSpell, Actor, MagicItem* apSpell)
|
|
{
|
|
bool result = TiltedPhoques::ThisCall(RealRemoveSpell, apThis, apSpell);
|
|
if (apThis->GetExtension()->IsLocalPlayer() && result)
|
|
{
|
|
//spdlog::info(__FUNCTION__ ": spell: {}, ID: {} from local player", apSpell->GetName() , apSpell->formID);
|
|
RemoveSpellEvent removalEvent;
|
|
|
|
removalEvent.TargetId = apThis->formID;
|
|
removalEvent.SpellId = apSpell->formID;
|
|
World::Get().GetRunner().Trigger(removalEvent);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Actor* TP_MAKE_THISCALL(HookCharacterConstructor, Actor)
|
|
{
|
|
TP_EMPTY_HOOK_PLACEHOLDER;
|
|
|
|
TiltedPhoques::ThisCall(RealCharacterConstructor, apThis);
|
|
|
|
return apThis;
|
|
}
|
|
|
|
Actor* TP_MAKE_THISCALL(HookCharacterConstructor2, Actor, uint8_t aUnk)
|
|
{
|
|
TP_EMPTY_HOOK_PLACEHOLDER;
|
|
|
|
TiltedPhoques::ThisCall(RealCharacterConstructor2, apThis, aUnk);
|
|
|
|
return apThis;
|
|
}
|
|
|
|
Actor* TP_MAKE_THISCALL(HookCharacterDestructor, Actor)
|
|
{
|
|
TP_EMPTY_HOOK_PLACEHOLDER;
|
|
|
|
auto pExtension = apThis->GetExtension();
|
|
|
|
if (pExtension)
|
|
{
|
|
pExtension->~ActorExtension();
|
|
}
|
|
|
|
TiltedPhoques::ThisCall(RealCharacterDestructor, apThis);
|
|
|
|
return apThis;
|
|
}
|
|
|
|
GamePtr<Actor> Actor::New() noexcept
|
|
{
|
|
auto* const pActor = Memory::Allocate<Actor>();
|
|
|
|
TiltedPhoques::ThisCall(RealCharacterConstructor, pActor);
|
|
|
|
return {pActor};
|
|
}
|
|
|
|
void Actor::InterruptCast(bool abRefund) noexcept
|
|
{
|
|
TP_THIS_FUNCTION(TInterruptCast, void, Actor, bool abRefund);
|
|
|
|
POINTER_SKYRIMSE(TInterruptCast, s_interruptCast, 38757);
|
|
|
|
TiltedPhoques::ThisCall(s_interruptCast, this, abRefund);
|
|
}
|
|
|
|
TESForm* Actor::GetEquippedWeapon(uint32_t aSlotId) const noexcept
|
|
{
|
|
if (currentProcess && currentProcess->middleProcess)
|
|
{
|
|
auto pMiddleProcess = currentProcess->middleProcess;
|
|
|
|
if (aSlotId == 0 && pMiddleProcess->leftEquippedObject)
|
|
return pMiddleProcess->leftEquippedObject->pObject;
|
|
|
|
else if (aSlotId == 1 && pMiddleProcess->rightEquippedObject)
|
|
return pMiddleProcess->rightEquippedObject->pObject;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
TESForm* Actor::GetEquippedAmmo() const noexcept
|
|
{
|
|
if (currentProcess && currentProcess->middleProcess && currentProcess->middleProcess->ammoEquippedObject)
|
|
{
|
|
// TODO: rtti cast to check if is ammo object? or actually, just call AIProcess::GetCurrentAmmo()
|
|
return currentProcess->middleProcess->ammoEquippedObject->pObject;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool Actor::IsWearingBodyPiece() const noexcept
|
|
{
|
|
return GetContainerChanges()->GetArmor(32) != nullptr;
|
|
}
|
|
|
|
bool Actor::ShouldWearBodyPiece() const noexcept
|
|
{
|
|
TESNPC* pBase = Cast<TESNPC>(baseForm);
|
|
if (!pBase)
|
|
return false;
|
|
|
|
BGSOutfit* pDefaultOutfit = pBase->outfits[0];
|
|
if (!pDefaultOutfit)
|
|
return false;
|
|
|
|
for (auto* pItem : pDefaultOutfit->outfitItems)
|
|
{
|
|
TESObjectARMO* pArmor = nullptr;
|
|
|
|
if (pItem->formType == FormType::Armor)
|
|
pArmor = Cast<TESObjectARMO>(pItem);
|
|
else if (pItem->formType == FormType::LeveledItem)
|
|
{
|
|
TESLevItem* pLevItem = Cast<TESLevItem>(pItem);
|
|
if (!pLevItem || !pLevItem->pLeveledListA || !pLevItem->pLeveledListA->pForm)
|
|
continue;
|
|
|
|
pArmor = Cast<TESObjectARMO>(pLevItem->pLeveledListA->pForm);
|
|
}
|
|
else
|
|
continue;
|
|
|
|
if (!pArmor)
|
|
continue;
|
|
|
|
if (pArmor->IsBodyPiece())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Get owner of a summon or raised corpse
|
|
Actor* Actor::GetCommandingActor() const noexcept
|
|
{
|
|
if (currentProcess && currentProcess->middleProcess && currentProcess->middleProcess->commandingActor)
|
|
{
|
|
auto handle = currentProcess->middleProcess->commandingActor.handle;
|
|
auto* pOwner = Cast<Actor>(TESObjectREFR::GetByHandle(handle.iBits));
|
|
return pOwner;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Get owner of a summon or raised corpse
|
|
void Actor::SetCommandingActor(BSPointerHandle<TESObjectREFR> aCommandingActor) noexcept
|
|
{
|
|
if (currentProcess && currentProcess->middleProcess)
|
|
{
|
|
currentProcess->middleProcess->commandingActor = aCommandingActor;
|
|
flags2 |= ActorFlags::IS_COMMANDED_ACTOR;
|
|
}
|
|
}
|
|
|
|
bool Actor::IsPlayerSummon() const noexcept
|
|
{
|
|
const Actor* pCommandingActor = GetCommandingActor();
|
|
return pCommandingActor && pCommandingActor->formID == 0x14;
|
|
}
|
|
|
|
TESForm* Actor::GetCurrentLocation()
|
|
{
|
|
// we use the safe function which also
|
|
// checks the form type
|
|
return FUNC_GetActorLocation(this);
|
|
}
|
|
|
|
Factions Actor::GetFactions() const noexcept
|
|
{
|
|
Factions result;
|
|
|
|
auto& modSystem = World::Get().GetModSystem();
|
|
|
|
auto* pNpc = Cast<TESNPC>(baseForm);
|
|
if (pNpc)
|
|
{
|
|
auto& factions = pNpc->actorData.factions;
|
|
|
|
for (auto i = 0u; i < factions.length; ++i)
|
|
{
|
|
Faction faction;
|
|
|
|
modSystem.GetServerModId(factions[i].faction->formID, faction.Id);
|
|
faction.Rank = factions[i].rank;
|
|
|
|
result.NpcFactions.push_back(faction);
|
|
}
|
|
}
|
|
|
|
auto* pChanges = Cast<ExtraFactionChanges>(extraData.GetByType(ExtraDataType::Faction));
|
|
if (pChanges)
|
|
{
|
|
for (auto i = 0u; i < pChanges->entries.length; ++i)
|
|
{
|
|
Faction faction;
|
|
|
|
modSystem.GetServerModId(pChanges->entries[i].faction->formID, faction.Id);
|
|
faction.Rank = pChanges->entries[i].rank;
|
|
|
|
result.ExtraFactions.push_back(faction);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ActorValues Actor::GetEssentialActorValues() const noexcept
|
|
{
|
|
ActorValues actorValues;
|
|
|
|
int essentialValues[] = {ActorValueInfo::kHealth, ActorValueInfo::kStamina, ActorValueInfo::kMagicka};
|
|
for (auto i : essentialValues)
|
|
{
|
|
float value = actorValueOwner.GetValue(i);
|
|
actorValues.ActorValuesList.insert({i, value});
|
|
float maxValue = actorValueOwner.GetPermanentValue(i);
|
|
actorValues.ActorMaxValuesList.insert({i, maxValue});
|
|
}
|
|
|
|
return actorValues;
|
|
}
|
|
|
|
float Actor::GetActorValue(uint32_t aId) const noexcept
|
|
{
|
|
return actorValueOwner.GetValue(aId);
|
|
}
|
|
|
|
float Actor::GetActorPermanentValue(uint32_t aId) const noexcept
|
|
{
|
|
return actorValueOwner.GetPermanentValue(aId);
|
|
}
|
|
|
|
void Actor::SetActorValue(uint32_t aId, float aValue) noexcept
|
|
{
|
|
actorValueOwner.SetValue(aId, aValue);
|
|
}
|
|
|
|
void Actor::ForceActorValue(ActorValueOwner::ForceMode aMode, uint32_t aId, float aValue) noexcept
|
|
{
|
|
float initialValue = aMode == ActorValueOwner::ForceMode::PERMANENT
|
|
? GetActorPermanentValue(aId)
|
|
: GetActorValue(aId);
|
|
|
|
if (aValue == initialValue)
|
|
return;
|
|
|
|
actorValueOwner.ForceCurrent(aMode, aId, aValue - initialValue);
|
|
}
|
|
|
|
Inventory Actor::GetActorInventory() const noexcept
|
|
{
|
|
Inventory inventory = GetInventory();
|
|
|
|
inventory.CurrentMagicEquipment = GetMagicEquipment();
|
|
|
|
return inventory;
|
|
}
|
|
|
|
MagicEquipment Actor::GetMagicEquipment() const noexcept
|
|
{
|
|
MagicEquipment equipment;
|
|
|
|
auto& modSystem = World::Get().GetModSystem();
|
|
|
|
uint32_t mainId = magicItems[0] ? magicItems[0]->formID : 0;
|
|
modSystem.GetServerModId(mainId, equipment.LeftHandSpell);
|
|
|
|
uint32_t secondaryId = magicItems[1] ? magicItems[1]->formID : 0;
|
|
modSystem.GetServerModId(secondaryId, equipment.RightHandSpell);
|
|
|
|
uint32_t shoutId = equippedShout ? equippedShout->formID : 0;
|
|
modSystem.GetServerModId(shoutId, equipment.Shout);
|
|
|
|
return equipment;
|
|
}
|
|
|
|
Inventory Actor::GetEquipment() const noexcept
|
|
{
|
|
Inventory inventory = GetInventory();
|
|
inventory.RemoveByFilter([](const auto& entry) { return !entry.IsWorn(); });
|
|
inventory.CurrentMagicEquipment = GetMagicEquipment();
|
|
return inventory;
|
|
}
|
|
|
|
int32_t Actor::GetGoldAmount() const noexcept
|
|
{
|
|
TP_THIS_FUNCTION(TGetGoldAmount, int32_t, const Actor);
|
|
POINTER_SKYRIMSE(TGetGoldAmount, s_getGoldAmount, 37527);
|
|
return TiltedPhoques::ThisCall(s_getGoldAmount, this);
|
|
}
|
|
|
|
void Actor::SetActorInventory(const Inventory& acInventory) noexcept
|
|
{
|
|
spdlog::info("Setting inventory for actor {:X}", formID);
|
|
|
|
// The UnEquipAll() that used to be here is redundant,
|
|
// as RemoveAllItems() unequips every item if needed.
|
|
// Placing this UnEquipAll() here seems to trigger a Skyrim bug/race.
|
|
|
|
Inventory currentInventory = GetActorInventory();
|
|
|
|
if (!this->GetExtension()->IsPlayer() && currentInventory.ContainsQuestItems())
|
|
SetInventoryRetainingQuestItems(currentInventory, acInventory);
|
|
else
|
|
SetInventory(acInventory);
|
|
|
|
SetMagicEquipment(acInventory.CurrentMagicEquipment);
|
|
}
|
|
|
|
void Actor::SetMagicEquipment(const MagicEquipment& acEquipment) noexcept
|
|
{
|
|
auto* pEquipManager = EquipManager::Get();
|
|
auto& modSystem = World::Get().GetModSystem();
|
|
|
|
if (acEquipment.LeftHandSpell)
|
|
{
|
|
uint32_t mainHandWeaponId = modSystem.GetGameId(acEquipment.LeftHandSpell);
|
|
spdlog::debug("Setting left hand spell: {:X}", mainHandWeaponId);
|
|
pEquipManager->EquipSpell(this, TESForm::GetById(mainHandWeaponId), 0);
|
|
}
|
|
|
|
if (acEquipment.RightHandSpell)
|
|
{
|
|
uint32_t secondaryHandWeaponId = modSystem.GetGameId(acEquipment.RightHandSpell);
|
|
spdlog::debug("Setting right hand spell: {:X}", secondaryHandWeaponId);
|
|
pEquipManager->EquipSpell(this, TESForm::GetById(secondaryHandWeaponId), 1);
|
|
}
|
|
|
|
if (acEquipment.Shout)
|
|
{
|
|
uint32_t shoutId = modSystem.GetGameId(acEquipment.Shout);
|
|
spdlog::debug("Setting shout: {:X}", shoutId);
|
|
pEquipManager->EquipShout(this, TESForm::GetById(shoutId));
|
|
}
|
|
}
|
|
|
|
void Actor::SetActorValues(const ActorValues& acActorValues) noexcept
|
|
{
|
|
for (auto& value : acActorValues.ActorMaxValuesList)
|
|
ForceActorValue(ActorValueOwner::ForceMode::PERMANENT, value.first, value.second);
|
|
|
|
for (auto& value : acActorValues.ActorValuesList)
|
|
ForceActorValue(ActorValueOwner::ForceMode::DAMAGE, value.first, value.second);
|
|
}
|
|
|
|
void Actor::SetFactions(const Factions& acFactions) noexcept
|
|
{
|
|
RemoveFromAllFactions();
|
|
|
|
auto& modSystem = World::Get().GetModSystem();
|
|
|
|
for (auto& entry : acFactions.NpcFactions)
|
|
{
|
|
auto pForm = GetById(modSystem.GetGameId(entry.Id));
|
|
auto pFaction = Cast<TESFaction>(pForm);
|
|
if (pFaction)
|
|
{
|
|
SetFactionRank(pFaction, entry.Rank);
|
|
}
|
|
}
|
|
|
|
for (auto& entry : acFactions.ExtraFactions)
|
|
{
|
|
auto pForm = GetById(modSystem.GetGameId(entry.Id));
|
|
auto pFaction = Cast<TESFaction>(pForm);
|
|
if (pFaction)
|
|
{
|
|
SetFactionRank(pFaction, entry.Rank);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Actor::SetFactionRank(const TESFaction* apFaction, int8_t aRank) noexcept
|
|
{
|
|
TP_THIS_FUNCTION(TSetFactionRankInternal, void, Actor, const TESFaction*, int8_t);
|
|
|
|
POINTER_SKYRIMSE(TSetFactionRankInternal, s_setFactionRankInternal, 37677);
|
|
|
|
TiltedPhoques::ThisCall(s_setFactionRankInternal, this, apFaction, aRank);
|
|
}
|
|
|
|
void Actor::SetPlayerTeammate(bool aSet) noexcept
|
|
{
|
|
TP_THIS_FUNCTION(TSetPlayerTeammate, void, Actor, bool aSet, bool abCanDoFavor);
|
|
POINTER_SKYRIMSE(TSetPlayerTeammate, setPlayerTeammate, 37717);
|
|
return TiltedPhoques::ThisCall(setPlayerTeammate, this, aSet, true);
|
|
}
|
|
|
|
void Actor::UnEquipAll() noexcept
|
|
{
|
|
EquipManager::Get()->UnequipAll(this);
|
|
|
|
// Taken from skyrim's code shouts can be two form types apparently
|
|
if (equippedShout && ((int)equippedShout->formType - 41) <= 1)
|
|
{
|
|
EquipManager::Get()->UnEquipShout(this, equippedShout);
|
|
equippedShout = nullptr;
|
|
}
|
|
}
|
|
|
|
void Actor::RemoveFromAllFactions() noexcept
|
|
{
|
|
PAPYRUS_FUNCTION(void, Actor, RemoveFromAllFactions);
|
|
|
|
s_pRemoveFromAllFactions(this);
|
|
}
|
|
|
|
TP_THIS_FUNCTION(TInitiateMountPackage, bool, Actor, Actor* apMount);
|
|
static TInitiateMountPackage* RealInitiateMountPackage = nullptr;
|
|
|
|
bool Actor::InitiateMountPackage(Actor* apMount) noexcept
|
|
{
|
|
return TiltedPhoques::ThisCall(RealInitiateMountPackage, this, apMount);
|
|
}
|
|
|
|
void Actor::GenerateMagicCasters() noexcept
|
|
{
|
|
using CS = MagicSystem::CastingSource;
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
if (casters[i] == nullptr)
|
|
casters[i] = Cast<ActorMagicCaster>(GetMagicCaster(static_cast<CS>(i)));
|
|
}
|
|
}
|
|
|
|
bool Actor::IsDead() const noexcept
|
|
{
|
|
PAPYRUS_FUNCTION(bool, Actor, IsDead);
|
|
|
|
return s_pIsDead(this);
|
|
}
|
|
|
|
bool Actor::IsDragon() const noexcept
|
|
{
|
|
const ActorExtension* pExtension = const_cast<Actor*>(this)->GetExtension();
|
|
return BehaviorVar::IsDragon(pExtension->GraphDescriptorHash);
|
|
}
|
|
|
|
void Actor::Kill() noexcept
|
|
{
|
|
// Never kill players
|
|
ActorExtension* pExtension = GetExtension();
|
|
if (pExtension->IsPlayer())
|
|
return;
|
|
|
|
// TODO: these args are kind of bogus of course
|
|
KillImpl(nullptr, 100.f, true, true);
|
|
|
|
// Papyrus kill will not go through if it is queued by a kill move
|
|
/*
|
|
PAPYRUS_FUNCTION(void, Actor, Kill, void*);
|
|
s_pKill(this, NULL);
|
|
*/
|
|
}
|
|
|
|
void Actor::Reset() noexcept
|
|
{
|
|
using ObjectReference = TESObjectREFR;
|
|
|
|
PAPYRUS_FUNCTION(void, ObjectReference, Reset, int, TESObjectREFR*);
|
|
|
|
s_pReset(this, 0, nullptr);
|
|
}
|
|
|
|
bool Actor::PlayIdle(TESIdleForm* apIdle) noexcept
|
|
{
|
|
PAPYRUS_FUNCTION(bool, Actor, PlayIdle, TESIdleForm*);
|
|
return s_pPlayIdle(this, apIdle);
|
|
}
|
|
|
|
void Actor::Respawn() noexcept
|
|
{
|
|
Resurrect(false);
|
|
Reset();
|
|
}
|
|
|
|
bool Actor::IsVampireLord() const noexcept
|
|
{
|
|
return race && race->formID == 0x200283A;
|
|
}
|
|
|
|
extern thread_local bool g_forceAnimation;
|
|
|
|
void Actor::FixVampireLordModel() noexcept
|
|
{
|
|
TESBoundObject* pLordArmor = Cast<TESBoundObject>(TESForm::GetById(0x2011a84));
|
|
if (!pLordArmor)
|
|
return;
|
|
|
|
{
|
|
ScopedInventoryOverride _;
|
|
AddObjectToContainer(pLordArmor, nullptr, 1, nullptr);
|
|
}
|
|
|
|
EquipManager::Get()->Equip(this, pLordArmor, nullptr, 1, nullptr, false, true, false, false);
|
|
|
|
g_forceAnimation = true;
|
|
|
|
BSFixedString str("isLevitating");
|
|
uint32_t isLevitating = GetAnimationVariableInt(&str);
|
|
spdlog::critical("isLevitating {}", isLevitating);
|
|
|
|
// By default, a loaded vampire lord is not levitating.
|
|
if (isLevitating)
|
|
{
|
|
BSFixedString levitation("LevitationToggle");
|
|
SendAnimationEvent(&levitation);
|
|
}
|
|
|
|
// TODO: weapon draw code does not seem to take care of this
|
|
//BSFixedString weapEquip("WeapEquip");
|
|
//SendAnimationEvent(&weapEquip);
|
|
|
|
g_forceAnimation = false;
|
|
}
|
|
|
|
char TP_MAKE_THISCALL(HookSetPosition, Actor, NiPoint3& aPosition)
|
|
{
|
|
const auto pExtension = apThis ? apThis->GetExtension() : nullptr;
|
|
const auto bIsRemote = pExtension && pExtension->IsRemote();
|
|
|
|
if (bIsRemote && !ScopedReferencesOverride::IsOverriden())
|
|
return 1;
|
|
|
|
// Don't interfere with non actor references, or the player, or if we are calling our self
|
|
if (apThis->formType != Actor::Type || apThis == PlayerCharacter::Get() || ScopedReferencesOverride::IsOverriden())
|
|
return TiltedPhoques::ThisCall(RealSetPosition, apThis, aPosition);
|
|
|
|
ScopedReferencesOverride recursionGuard;
|
|
|
|
// It just works TM
|
|
apThis->SetPosition(aPosition, false);
|
|
|
|
return 1;
|
|
}
|
|
|
|
TP_THIS_FUNCTION(TForceState, void, Actor, const NiPoint3&, float, float, TESObjectCELL*, TESWorldSpace*, bool);
|
|
static TForceState* RealForceState = nullptr;
|
|
|
|
void TP_MAKE_THISCALL(HookForceState, Actor, const NiPoint3& acPosition, float aX, float aZ, TESObjectCELL* apCell, TESWorldSpace* apWorldSpace, bool aUnkBool)
|
|
{
|
|
/*const auto pNpc = Cast<TESNPC>(apThis->baseForm);
|
|
if (pNpc)
|
|
{
|
|
spdlog::info("For TESNPC: {}, spawn at {} {} {}", pNpc->fullName.value, apPosition->m_x, apPosition->m_y,
|
|
apPosition->m_z);
|
|
}*/
|
|
|
|
// if (apThis != PlayerCharacter::Get())
|
|
// return;
|
|
|
|
return TiltedPhoques::ThisCall(RealForceState, apThis, acPosition, aX, aZ, apCell, apWorldSpace, aUnkBool);
|
|
}
|
|
|
|
TP_THIS_FUNCTION(TSpawnActorInWorld, bool, Actor);
|
|
static TSpawnActorInWorld* RealSpawnActorInWorld = nullptr;
|
|
|
|
// TODO: this isn't SpawnActorInWorld, this is TESObjectREFR::UpdateReference3D()
|
|
bool TP_MAKE_THISCALL(HookSpawnActorInWorld, Actor)
|
|
{
|
|
const auto* pNpc = Cast<TESNPC>(apThis->baseForm);
|
|
if (pNpc)
|
|
{
|
|
spdlog::info("Spawn Actor: {:X}, and NPC {}", apThis->formID, pNpc->fullName.value);
|
|
}
|
|
|
|
return TiltedPhoques::ThisCall(RealSpawnActorInWorld, apThis);
|
|
}
|
|
|
|
TP_THIS_FUNCTION(TDamageActor, bool, Actor, float aDamage, Actor* apHitter, bool aKillMove);
|
|
static TDamageActor* RealDamageActor = nullptr;
|
|
|
|
// TODO: this is flawed, since it does not account for invulnerable actors
|
|
bool TP_MAKE_THISCALL(HookDamageActor, Actor, float aDamage, Actor* apHitter, bool aKillMove)
|
|
{
|
|
if (apHitter)
|
|
World::Get().GetRunner().Trigger(HitEvent(apHitter->formID, apThis->formID));
|
|
|
|
float realDamage = GameplayFormulas::CalculateRealDamage(apThis, aDamage, aKillMove);
|
|
|
|
float currentHealth = apThis->GetActorValue(ActorValueInfo::kHealth);
|
|
bool wouldKill = (currentHealth - realDamage) <= 0.f;
|
|
|
|
const auto* pExHittee = apThis->GetExtension();
|
|
if (pExHittee->IsLocalPlayer())
|
|
{
|
|
if (!World::Get().GetServerSettings().PvpEnabled)
|
|
{
|
|
if (apHitter && apHitter->GetExtension()->IsRemotePlayer())
|
|
return false;
|
|
}
|
|
|
|
World::Get().GetRunner().Trigger(HealthChangeEvent(apThis->formID, -realDamage));
|
|
return TiltedPhoques::ThisCall(RealDamageActor, apThis, aDamage, apHitter, aKillMove);
|
|
}
|
|
else if (pExHittee->IsRemotePlayer())
|
|
{
|
|
return wouldKill;
|
|
}
|
|
|
|
if (apHitter)
|
|
{
|
|
const auto* pExHitter = apHitter->GetExtension();
|
|
if (pExHitter->IsLocalPlayer())
|
|
{
|
|
World::Get().GetRunner().Trigger(HealthChangeEvent(apThis->formID, -realDamage));
|
|
return TiltedPhoques::ThisCall(RealDamageActor, apThis, aDamage, apHitter, aKillMove);
|
|
}
|
|
if (pExHitter->IsRemotePlayer())
|
|
{
|
|
return wouldKill;
|
|
}
|
|
}
|
|
|
|
if (pExHittee->IsLocal())
|
|
{
|
|
World::Get().GetRunner().Trigger(HealthChangeEvent(apThis->formID, -realDamage));
|
|
return TiltedPhoques::ThisCall(RealDamageActor, apThis, aDamage, apHitter, aKillMove);
|
|
}
|
|
else
|
|
{
|
|
return wouldKill;
|
|
}
|
|
}
|
|
|
|
TP_THIS_FUNCTION(TApplyActorEffect, void, ActiveEffect, Actor* apTarget, float aEffectValue, unsigned int unk1);
|
|
static TApplyActorEffect* RealApplyActorEffect = nullptr;
|
|
|
|
void TP_MAKE_THISCALL(HookApplyActorEffect, ActiveEffect, Actor* apTarget, float aEffectValue, unsigned int unk1)
|
|
{
|
|
const auto* pValueModEffect = Cast<ValueModifierEffect>(apThis);
|
|
|
|
if (pValueModEffect)
|
|
{
|
|
if (pValueModEffect->actorValueIndex == ActorValueInfo::kHealth && aEffectValue > 0.0f)
|
|
{
|
|
if (apTarget && apTarget->GetExtension())
|
|
{
|
|
const auto pExTarget = apTarget->GetExtension();
|
|
if (pExTarget->IsLocal())
|
|
{
|
|
World::Get().GetRunner().Trigger(HealthChangeEvent(apTarget->formID, aEffectValue));
|
|
return TiltedPhoques::ThisCall(RealApplyActorEffect, apThis, apTarget, aEffectValue, unk1);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TiltedPhoques::ThisCall(RealApplyActorEffect, apThis, apTarget, aEffectValue, unk1);
|
|
}
|
|
|
|
TP_THIS_FUNCTION(TRegenAttributes, void*, Actor, int aId, float regenValue);
|
|
static TRegenAttributes* RealRegenAttributes = nullptr;
|
|
|
|
void* TP_MAKE_THISCALL(HookRegenAttributes, Actor, int aId, float aRegenValue)
|
|
{
|
|
if (aId != ActorValueInfo::kHealth)
|
|
{
|
|
return TiltedPhoques::ThisCall(RealRegenAttributes, apThis, aId, aRegenValue);
|
|
}
|
|
|
|
const auto* pExTarget = apThis->GetExtension();
|
|
if (pExTarget->IsRemote())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
World::Get().GetRunner().Trigger(HealthChangeEvent(apThis->formID, aRegenValue));
|
|
return TiltedPhoques::ThisCall(RealRegenAttributes, apThis, aId, aRegenValue);
|
|
}
|
|
|
|
void TP_MAKE_THISCALL(HookAddInventoryItem, Actor, TESBoundObject* apItem, ExtraDataList* apExtraData, int32_t aCount, TESObjectREFR* apOldOwner)
|
|
{
|
|
if (!ScopedInventoryOverride::IsOverriden())
|
|
{
|
|
auto& modSystem = World::Get().GetModSystem();
|
|
|
|
Inventory::Entry item{};
|
|
modSystem.GetServerModId(apItem->formID, item.BaseId);
|
|
item.Count = aCount;
|
|
|
|
if (apExtraData)
|
|
apThis->GetItemFromExtraData(item, apExtraData);
|
|
|
|
World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID, std::move(item)));
|
|
}
|
|
|
|
TiltedPhoques::ThisCall(RealAddInventoryItem, apThis, apItem, apExtraData, aCount, apOldOwner);
|
|
}
|
|
|
|
void* TP_MAKE_THISCALL(HookPickUpObject, Actor, TESObjectREFR* apObject, int32_t aCount, bool aUnk1, float aUnk2)
|
|
{
|
|
if (!ScopedInventoryOverride::IsOverriden())
|
|
{
|
|
auto& modSystem = World::Get().GetModSystem();
|
|
|
|
Inventory::Entry item{};
|
|
modSystem.GetServerModId(apObject->baseForm->formID, item.BaseId);
|
|
item.Count = aCount;
|
|
|
|
if (apObject->GetExtraDataList())
|
|
apThis->GetItemFromExtraData(item, apObject->GetExtraDataList());
|
|
|
|
// This is here so that objects that are picked up on both clients, aka non temps, are synced through activation sync.
|
|
// The inventory change event should always be sent to the server, otherwise the server inventory won't be updated.
|
|
bool shouldUpdateClients = apObject->IsTemporary() && !ScopedActivateOverride::IsOverriden();
|
|
|
|
World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID, std::move(item), false, shouldUpdateClients));
|
|
}
|
|
|
|
return TiltedPhoques::ThisCall(RealPickUpObject, apThis, apObject, aCount, aUnk1, aUnk2);
|
|
}
|
|
|
|
void Actor::PickUpObject(TESObjectREFR* apObject, int32_t aCount, bool aUnk1, float aUnk2) noexcept
|
|
{
|
|
TiltedPhoques::ThisCall(RealPickUpObject, this, apObject, aCount, aUnk1, aUnk2);
|
|
}
|
|
|
|
void* TP_MAKE_THISCALL(HookDropObject, Actor, void* apResult, TESBoundObject* apObject, ExtraDataList* apExtraData, int32_t aCount, NiPoint3* apLocation, NiPoint3* apRotation)
|
|
{
|
|
auto& modSystem = World::Get().GetModSystem();
|
|
|
|
Inventory::Entry item{};
|
|
modSystem.GetServerModId(apObject->formID, item.BaseId);
|
|
item.Count = -aCount;
|
|
|
|
if (apExtraData)
|
|
apThis->GetItemFromExtraData(item, apExtraData);
|
|
|
|
World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID, std::move(item), true));
|
|
|
|
ScopedInventoryOverride _;
|
|
|
|
return TiltedPhoques::ThisCall(RealDropObject, apThis, apResult, apObject, apExtraData, aCount, apLocation, apRotation);
|
|
}
|
|
|
|
void Actor::DropOrPickUpObject(const Inventory::Entry& arEntry, NiPoint3* apLocation, NiPoint3* apRotation) noexcept
|
|
{
|
|
ExtraDataList* pExtraData = GetExtraDataFromItem(arEntry);
|
|
|
|
ModSystem& modSystem = World::Get().GetModSystem();
|
|
|
|
uint32_t objectId = modSystem.GetGameId(arEntry.BaseId);
|
|
TESBoundObject* pObject = Cast<TESBoundObject>(TESForm::GetById(objectId));
|
|
if (!pObject)
|
|
{
|
|
spdlog::warn("Object to drop not found, {:X}:{:X}.", arEntry.BaseId.ModId, arEntry.BaseId.BaseId);
|
|
return;
|
|
}
|
|
|
|
if (arEntry.Count < 0)
|
|
DropObject(pObject, pExtraData, -arEntry.Count, apLocation, apRotation);
|
|
// TODO: pick up
|
|
}
|
|
|
|
void Actor::DropObject(TESBoundObject* apObject, ExtraDataList* apExtraData, int32_t aCount, NiPoint3* apLocation, NiPoint3* apRotation) noexcept
|
|
{
|
|
spdlog::debug("Dropping object, form id: {:X}, count: {}, actor: {:X}", apObject->formID, aCount, formID);
|
|
BSPointerHandle<TESObjectREFR> result{};
|
|
TiltedPhoques::ThisCall(RealDropObject, this, &result, apObject, apExtraData, aCount, apLocation, apRotation);
|
|
}
|
|
|
|
TP_THIS_FUNCTION(TUpdateDetectionState, void, ActorKnowledge, void*);
|
|
static TUpdateDetectionState* RealUpdateDetectionState = nullptr;
|
|
|
|
void TP_MAKE_THISCALL(HookUpdateDetectionState, ActorKnowledge, void* apState)
|
|
{
|
|
auto pOwner = TESObjectREFR::GetByHandle(apThis->hOwner);
|
|
auto pTarget = TESObjectREFR::GetByHandle(apThis->hTarget);
|
|
|
|
if (pOwner && pTarget)
|
|
{
|
|
auto pOwnerActor = Cast<Actor>(pOwner);
|
|
auto pTargetActor = Cast<Actor>(pTarget);
|
|
if (pOwnerActor && pTargetActor)
|
|
{
|
|
if (pOwnerActor->GetExtension()->IsRemotePlayer() && pTargetActor->GetExtension()->IsLocalPlayer())
|
|
{
|
|
spdlog::debug("Cancelling detection from remote player to local player, owner: {:X}, target: {:X}", pOwner->formID, pTarget->formID);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TiltedPhoques::ThisCall(RealUpdateDetectionState, apThis, apState);
|
|
}
|
|
|
|
struct DialogueItem;
|
|
|
|
// TODO: This is an AIProcess function
|
|
TP_THIS_FUNCTION(TProcessResponse, uint64_t, void, DialogueItem* apVoice, Actor* apTalkingActor, Actor* apTalkedToActor);
|
|
static TProcessResponse* RealProcessResponse = nullptr;
|
|
|
|
uint64_t TP_MAKE_THISCALL(HookProcessResponse, void, DialogueItem* apVoice, Actor* apTalkingActor, Actor* apTalkedToActor)
|
|
{
|
|
if (apTalkingActor)
|
|
{
|
|
if (apTalkingActor->GetExtension()->IsRemotePlayer())
|
|
return 0;
|
|
}
|
|
return TiltedPhoques::ThisCall(RealProcessResponse, apThis, apVoice, apTalkingActor, apTalkedToActor);
|
|
}
|
|
|
|
bool TP_MAKE_THISCALL(HookInitiateMountPackage, Actor, Actor* apMount)
|
|
{
|
|
if (apMount && apThis->GetExtension()->IsLocal())
|
|
World::Get().GetRunner().Trigger(MountEvent(apThis->formID, apMount->formID));
|
|
|
|
return TiltedPhoques::ThisCall(RealInitiateMountPackage, apThis, apMount);
|
|
}
|
|
|
|
TP_THIS_FUNCTION(TUnequipObject, void, Actor, void* apUnk1, TESBoundObject* apObject, int32_t aUnk2, void* apUnk3);
|
|
static TUnequipObject* RealUnequipObject = nullptr;
|
|
|
|
void TP_MAKE_THISCALL(HookUnequipObject, Actor, void* apUnk1, TESBoundObject* apObject, int32_t aUnk2, void* apUnk3)
|
|
{
|
|
TiltedPhoques::ThisCall(RealUnequipObject, apThis, apUnk1, apObject, aUnk2, apUnk3);
|
|
}
|
|
|
|
TP_THIS_FUNCTION(TSpeakSoundFunction, bool, Actor, const char* apName, uint32_t* a3, uint32_t a4, uint32_t a5, uint32_t a6, uint64_t a7, uint64_t a8, uint64_t a9, bool a10, uint64_t a11, bool a12, bool a13, bool a14);
|
|
static TSpeakSoundFunction* RealSpeakSoundFunction = nullptr;
|
|
|
|
bool TP_MAKE_THISCALL(HookSpeakSoundFunction, Actor, const char* apName, uint32_t* a3, uint32_t a4, uint32_t a5, uint32_t a6, uint64_t a7, uint64_t a8, uint64_t a9, bool a10, uint64_t a11, bool a12, bool a13, bool a14)
|
|
{
|
|
spdlog::debug("a3: {:X}, a4: {}, a5: {}, a6: {}, a7: {}, a8: {:X}, a9: {:X}, a10: {}, a11: {:X}, a12: {}, a13: {}, a14: {}", (uint64_t)a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14);
|
|
|
|
if (apThis->GetExtension()->IsLocal())
|
|
World::Get().GetRunner().Trigger(DialogueEvent(apThis->formID, apName));
|
|
|
|
return TiltedPhoques::ThisCall(RealSpeakSoundFunction, apThis, apName, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14);
|
|
}
|
|
|
|
void Actor::SpeakSound(const char* pFile)
|
|
{
|
|
uint32_t handle[3]{};
|
|
handle[0] = -1;
|
|
TiltedPhoques::ThisCall(RealSpeakSoundFunction, this, pFile, handle, 0, 0x32, 0, 0, 0, 0, 0, 0, 0, 1, 1);
|
|
}
|
|
|
|
char TP_MAKE_THISCALL(HookActorProcess, Actor, float a2)
|
|
{
|
|
// Don't process AI if we own the actor
|
|
|
|
if (apThis->GetExtension()->IsRemote())
|
|
return 0;
|
|
|
|
return TiltedPhoques::ThisCall(RealActorProcess, apThis, a2);
|
|
}
|
|
|
|
TP_THIS_FUNCTION(TAddDeathItems, void, Actor);
|
|
static TAddDeathItems* RealAddDeathItems = nullptr;
|
|
|
|
void TP_MAKE_THISCALL(HookAddDeathItems, Actor)
|
|
{
|
|
if (apThis->GetExtension()->IsRemote())
|
|
return;
|
|
|
|
TiltedPhoques::ThisCall(RealAddDeathItems, apThis);
|
|
}
|
|
|
|
TP_THIS_FUNCTION(TIsFleeing, bool, Actor);
|
|
static TIsFleeing* RealIsFleeing = nullptr;
|
|
|
|
bool TP_MAKE_THISCALL(HookIsFleeing, Actor)
|
|
{
|
|
// TODO: Player or RemotePlayer? Can players be in fleeing mode in skyrim?
|
|
// TODO: investigate why the flee flag is set at all on remote players sometimes.
|
|
if (apThis->GetExtension()->IsPlayer())
|
|
return false;
|
|
|
|
return TiltedPhoques::ThisCall(RealIsFleeing, apThis);
|
|
}
|
|
|
|
static TiltedPhoques::Initializer s_actorHooks(
|
|
[]()
|
|
{
|
|
POINTER_SKYRIMSE(TActorProcess, s_actorProcess, 37356);
|
|
POINTER_SKYRIMSE(TSetPosition, s_setPosition, 19790);
|
|
POINTER_SKYRIMSE(TRemoveSpell, s_removeSpell, 38717);
|
|
POINTER_SKYRIMSE(TCharacterConstructor, s_characterCtor, 40245);
|
|
POINTER_SKYRIMSE(TCharacterConstructor2, s_characterCtor2, 40246);
|
|
POINTER_SKYRIMSE(TCharacterDestructor, s_characterDtor, 37175);
|
|
POINTER_SKYRIMSE(TGetLocation, s_GetActorLocation, 19812);
|
|
POINTER_SKYRIMSE(TForceState, s_ForceState, 37313);
|
|
POINTER_SKYRIMSE(TSpawnActorInWorld, s_SpawnActorInWorld, 19742);
|
|
POINTER_SKYRIMSE(TDamageActor, s_damageActor, 37335);
|
|
POINTER_SKYRIMSE(TApplyActorEffect, s_applyActorEffect, 35086);
|
|
POINTER_SKYRIMSE(TRegenAttributes, s_regenAttributes, 37448);
|
|
POINTER_SKYRIMSE(TAddInventoryItem, s_addInventoryItem, 37525);
|
|
POINTER_SKYRIMSE(TPickUpObject, s_pickUpObject, 37521);
|
|
POINTER_SKYRIMSE(TDropObject, s_dropObject, 40454);
|
|
POINTER_SKYRIMSE(TUpdateDetectionState, s_updateDetectionState, 42704);
|
|
POINTER_SKYRIMSE(TProcessResponse, s_processResponse, 39643);
|
|
POINTER_SKYRIMSE(TInitiateMountPackage, s_initiateMountPackage, 37905);
|
|
POINTER_SKYRIMSE(TUnequipObject, s_unequipObject, 37975);
|
|
POINTER_SKYRIMSE(TSpeakSoundFunction, s_speakSoundFunction, 37542);
|
|
POINTER_SKYRIMSE(TAddDeathItems, addDeathItems, 37198);
|
|
POINTER_SKYRIMSE(TIsFleeing, isFleeing, 37577);
|
|
|
|
RealActorProcess = s_actorProcess.Get();
|
|
RealSetPosition = s_setPosition.Get();
|
|
RealRemoveSpell = s_removeSpell.Get();
|
|
FUNC_GetActorLocation = s_GetActorLocation.Get();
|
|
RealCharacterConstructor = s_characterCtor.Get();
|
|
RealCharacterConstructor2 = s_characterCtor2.Get();
|
|
RealForceState = s_ForceState.Get();
|
|
RealSpawnActorInWorld = s_SpawnActorInWorld.Get();
|
|
RealDamageActor = s_damageActor.Get();
|
|
RealApplyActorEffect = s_applyActorEffect.Get();
|
|
RealRegenAttributes = s_regenAttributes.Get();
|
|
RealAddInventoryItem = s_addInventoryItem.Get();
|
|
RealPickUpObject = s_pickUpObject.Get();
|
|
RealDropObject = s_dropObject.Get();
|
|
RealUpdateDetectionState = s_updateDetectionState.Get();
|
|
RealProcessResponse = s_processResponse.Get();
|
|
RealInitiateMountPackage = s_initiateMountPackage.Get();
|
|
RealUnequipObject = s_unequipObject.Get();
|
|
RealSpeakSoundFunction = s_speakSoundFunction.Get();
|
|
RealAddDeathItems = addDeathItems.Get();
|
|
RealIsFleeing = isFleeing.Get();
|
|
|
|
TP_HOOK(&RealActorProcess, HookActorProcess);
|
|
TP_HOOK(&RealSetPosition, HookSetPosition);
|
|
TP_HOOK(&RealRemoveSpell, HookRemoveSpell);
|
|
TP_HOOK(&RealCharacterConstructor, HookCharacterConstructor);
|
|
TP_HOOK(&RealCharacterConstructor2, HookCharacterConstructor2);
|
|
TP_HOOK(&RealForceState, HookForceState);
|
|
TP_HOOK(&RealSpawnActorInWorld, HookSpawnActorInWorld);
|
|
TP_HOOK(&RealDamageActor, HookDamageActor);
|
|
TP_HOOK(&RealApplyActorEffect, HookApplyActorEffect);
|
|
TP_HOOK(&RealRegenAttributes, HookRegenAttributes);
|
|
TP_HOOK(&RealAddInventoryItem, HookAddInventoryItem);
|
|
TP_HOOK(&RealPickUpObject, HookPickUpObject);
|
|
TP_HOOK(&RealDropObject, HookDropObject);
|
|
TP_HOOK(&RealUpdateDetectionState, HookUpdateDetectionState);
|
|
TP_HOOK(&RealProcessResponse, HookProcessResponse);
|
|
TP_HOOK(&RealInitiateMountPackage, HookInitiateMountPackage);
|
|
TP_HOOK(&RealUnequipObject, HookUnequipObject);
|
|
TP_HOOK(&RealSpeakSoundFunction, HookSpeakSoundFunction);
|
|
TP_HOOK(&RealAddDeathItems, HookAddDeathItems);
|
|
TP_HOOK(&RealIsFleeing, HookIsFleeing);
|
|
});
|