Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Code/client/Games/Animation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ static TPerformAction* RealPerformAction;
// TODO: make scoped override
thread_local bool g_forceAnimation = false;

// This is where the Actors AI is enabled/disabled: almost all of NPC AI/behavior is
// determined by Actions that are run on them.

uint8_t TP_MAKE_THISCALL(HookPerformAction, ActorMediator, TESActionData* apAction)
{
auto pActor = apAction->actor;
Expand Down
5 changes: 2 additions & 3 deletions Code/client/Games/Misc/SubtitleManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <TESObjectREFR.h>
#include <Games/ActorExtension.h>
#include <Forms/TESQuest.h>

#include <Forms/TESTopicInfo.h>
#include <Misc/BSFixedString.h>
Expand Down Expand Up @@ -31,10 +32,8 @@ void* SubtitleManager::HideSubtitle(TESObjectREFR* apSpeaker) noexcept

void TP_MAKE_THISCALL(HookShowSubtitle, SubtitleManager, TESObjectREFR* apSpeaker, const char* apSubtitleText, bool aIsInDialogue)
{
// spdlog::debug("Subtitle for actor {:X} (bool {}):\n\t{}", apSpeaker ? apSpeaker->formID : 0, aIsInDialogue, apSubtitleText);

Actor* pActor = Cast<Actor>(apSpeaker);
if (apSubtitleText && pActor && pActor->GetExtension()->IsLocal() && !pActor->GetExtension()->IsPlayer())
if (apSubtitleText && std::strlen(apSubtitleText) && pActor && !pActor->GetExtension()->IsPlayer())
World::Get().GetRunner().Trigger(SubtitleEvent(apSpeaker->formID, apSubtitleText));

TiltedPhoques::ThisCall(RealShowSubtitle, apThis, apSpeaker, apSubtitleText, aIsInDialogue);
Expand Down
7 changes: 7 additions & 0 deletions Code/client/Games/Skyrim/AI/Movement/PlayerControls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ void PlayerControls::SetCamSwitch(bool aSet) noexcept
Data.remapMode = aSet;
}

bool PlayerControls::IsMovementControlsEnabled() noexcept
{
using TIsMovementControlsEnabled = bool();
POINTER_SKYRIMSE(TIsMovementControlsEnabled, s_isMovementControlsEnabled, 55485);
return s_isMovementControlsEnabled.Get()();
}

BSInputEnableManager* BSInputEnableManager::Get()
{
POINTER_SKYRIMSE(BSInputEnableManager*, s_instance, 400863);
Expand Down
2 changes: 2 additions & 0 deletions Code/client/Games/Skyrim/AI/Movement/PlayerControls.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ struct PlayerControls

void SetCamSwitch(bool aSet) noexcept;

static bool IsMovementControlsEnabled() noexcept;

public:
char pad0[0x20];
PlayerControlsData Data;
Expand Down
73 changes: 63 additions & 10 deletions Code/client/Games/Skyrim/Actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <DefaultObjectManager.h>
#include <Forms/TESNPC.h>
#include <Forms/TESFaction.h>
#include <Forms/TESQuest.h>
#include <Components/TESActorBaseData.h>
#include <ExtraData/ExtraFactionChanges.h>
#include <Games/Memory.h>
Expand Down Expand Up @@ -55,6 +56,8 @@

#include <ModCompat/BehaviorVar.h>

#include <World.h>

#ifdef SAVE_STUFF

#include <Games/Skyrim/SaveLoad.h>
Expand Down Expand Up @@ -1189,7 +1192,7 @@ uint64_t TP_MAKE_THISCALL(HookProcessResponse, void, DialogueItem* apVoice, Acto
if (apTalkingActor)
{
if (apTalkingActor->GetExtension()->IsRemotePlayer())
return 0;
return 0;
}
return TiltedPhoques::ThisCall(RealProcessResponse, apThis, apVoice, apTalkingActor, apTalkedToActor);
}
Expand All @@ -1210,32 +1213,82 @@ void TP_MAKE_THISCALL(HookUnequipObject, Actor, void* apUnk1, TESBoundObject* ap
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);
TP_THIS_FUNCTION(TSpeakSoundFunction, float, 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)
float 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);
// Note most dialogues invoke SpeakSoundFunction twice, an initial call that queues it to an update thread, then a reinvocation.

if (apThis->GetExtension()->IsLocal())
World::Get().GetRunner().Trigger(DialogueEvent(apThis->formID, apName));
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);

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)
float 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);

return TiltedPhoques::ThisCall(RealSpeakSoundFunction, this, pFile, handle, 0, 0x32, 0, 0, 0, 0, 0, 0, 0, 1, 1);
}

bool Actor::IsTalking() noexcept
{
TP_THIS_FUNCTION(TIsTalking, bool, Actor);
POINTER_SKYRIMSE(TIsTalking, s_IsTalking, 37266);
return TiltedPhoques::ThisCall(s_IsTalking, this);
}

bool Actor::IsInScene() noexcept
{
// We don't have a solution for Condition Functions
//PAPYRUS_FUNCTION(bool, Actor, IsInScene);
//bool papyrusValue = s_pIsInScene(this);
bool flagsValue= (flags1 & ActorBoolBits::kHasSceneExtra) != 0;

return flagsValue;
}

bool Actor::IsInDialogueWithPlayer() noexcept
{
using ObjectReference = TESObjectREFR;
PAPYRUS_FUNCTION(bool, ObjectReference, IsInDialogueWithPlayer);
return s_pIsInDialogueWithPlayer(this);
}

float Actor::GetVoiceRecoveryTime() noexcept
{
// Doesn't work. And Wiki only describes w.r.t shouts
// PAPYRUS_FUNCTION(float, Actor, GetVoiceRecoveryTime);
// float fVRT = s_pGetVoiceRecoveryTime(this);
// if (fVRT != fVoiceTimer)
// spdlog::warn(__FUNCTION__ ": fVRT {}, fVoiceTimer {}", fVRT, fVoiceTimer);

return fVoiceTimer;
}

bool Actor::IsSpeakingInScene()
{
auto pScene = GetCurrentScene();
bool isSpeakingInScene = IsInScene();
isSpeakingInScene = isSpeakingInScene && GetVoiceRecoveryTime() > 0.0f;
const bool isTalking = IsTalking();
const bool isLeader = World::Get().GetPartyService().IsLeader(); // Helps distinguish logs in 2-party

spdlog::debug(__FUNCTION__ ": isSpeakingInScene {}, isTalking {}, voiceRecoveryTime {}, isLeader {}, formId {:X}, name {}", isSpeakingInScene, isTalking, GetVoiceRecoveryTime(), isLeader, formID, baseForm->GetName());

return isSpeakingInScene;
}

char TP_MAKE_THISCALL(HookActorProcess, Actor, float a2)
{
// Don't process AI if we own the actor
// Only process AI if we own the actor

if (apThis->GetExtension()->IsRemote())
return 0;
return 0;

return TiltedPhoques::ThisCall(RealActorProcess, apThis, a2);
}
Expand Down
14 changes: 13 additions & 1 deletion Code/client/Games/Skyrim/Actor.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@ struct Actor : TESObjectREFR
[[nodiscard]] bool IsWearingBodyPiece() const noexcept;
[[nodiscard]] bool ShouldWearBodyPiece() const noexcept;
[[nodiscard]] bool IsVampireLord() const noexcept;
[[nodiscard]] bool IsTalking() noexcept;
[[nodiscard]] bool IsInScene() noexcept;
[[nodiscard]] bool IsInDialogueWithPlayer() noexcept;
[[nodiscard]] float GetVoiceRecoveryTime() noexcept;
[[nodiscard]] bool IsSpeakingInScene();


// Setters
void SetSpeed(float aSpeed) noexcept;
Expand Down Expand Up @@ -243,7 +249,7 @@ struct Actor : TESObjectREFR
void PickUpObject(TESObjectREFR* apObject, int32_t aCount, bool aUnk1, float aUnk2) noexcept;
void DropObject(TESBoundObject* apObject, ExtraDataList* apExtraData, int32_t aCount, NiPoint3* apLocation, NiPoint3* apRotation) noexcept;
void DropOrPickUpObject(const Inventory::Entry& arEntry, NiPoint3* apPoint, NiPoint3* apRotate) noexcept;
void SpeakSound(const char* pFile);
float SpeakSound(const char* pFile);
void StartCombatEx(Actor* apTarget) noexcept;
void SetCombatTargetEx(Actor* apTarget) noexcept;
void StartCombat(Actor* apTarget) noexcept;
Expand All @@ -252,6 +258,12 @@ struct Actor : TESObjectREFR
void FixVampireLordModel() noexcept;
bool RemoveSpell(MagicItem* apSpell) noexcept;

enum ActorBoolBits
{
kNone = 0,
kHasSceneExtra = 1 << 3,
};

enum ActorFlags
{
IS_A_MOUNT = 1 << 1,
Expand Down
15 changes: 15 additions & 0 deletions Code/client/Games/Skyrim/Events/EventDispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,16 +188,31 @@ struct TESResolveNPCTemplatesEvent
{
};

// The RE'd fields in TESSceneEvent, TESSceneActionEvent and TESScenePhaseEvent may be incorrect

struct TESSceneEvent
{
void* ref;
uint32_t sceneFormId;
uint32_t sceneType; // BEGIN (0) or END (1)
};

struct TESSceneActionEvent
{
void* ref;
uint32_t sceneFormId;
uint32_t actionIndex;
uint32_t questFormId;
uint32_t actorAliasId;
};

struct TESScenePhaseEvent
{
void* callback;
uint32_t sceneFormId;
uint16_t phaseIndex;
uint32_t sceneType; // BEGIN (0) or END (1)
uint32_t questStageId;
};

struct TESSellEvent
Expand Down
107 changes: 88 additions & 19 deletions Code/client/Games/Skyrim/Forms/TESQuest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,9 @@ void TESQuest::SetActive(bool toggle)

bool TESQuest::IsStageDone(uint16_t stageIndex)
{
for (Stage* it : stages)
{
if (it->stageIndex == stageIndex)
return it->IsDone();
}

return false;
TP_THIS_FUNCTION(TIsStageDone, bool, TESQuest, uint16_t);
POINTER_SKYRIMSE(TIsStageDone, IsStageDone, 25011);
return IsStageDone(this, stageIndex);
}

bool TESQuest::Kill()
Expand Down Expand Up @@ -88,34 +84,107 @@ bool TESQuest::EnsureQuestStarted(bool& success, bool force)
return SetRunning(this, &success, force);
}

bool TESQuest::SetStage(uint16_t newStage)
bool TESQuest::SetStage(uint16_t stageIndex)
{
ScopedQuestOverride _;

// According to wiki, calling newStage == currentStage does nothing.
// Calling with newStage < currentStage, will rerun the stage actions
// IFF the target newStage is not marked IsCompleted(). Regardless,
// will not reduce currentStage (it stays the same).
// Actually reducing currentStage requires reset() to be called first.
TP_THIS_FUNCTION(TSetStage, bool, TESQuest, uint16_t);
POINTER_SKYRIMSE(TSetStage, SetStage, 25004);
return SetStage(this, newStage);
bool bSuccess = SetStage(this, stageIndex);
if (!bSuccess)
{
spdlog::warn(__FUNCTION__ ": returned false quest formId {:X}, currentStage {}, newStage {}, name {}",
formID, currentStage, stageIndex, fullName.value.AsAscii());
}
return bSuccess;
}

void TESQuest::ScriptSetStage(uint16_t stageIndex)
bool TESQuest::ScriptSetStage(uint16_t stageIndex, bool bForce)
{
if (currentStage == stageIndex || IsStageDone(stageIndex))
return;
// Since SetStage() rules are not well-known and hooks may be confused, filter rewind
// according to TESQuest::SetStage rules.
bool bSuccess = stageIndex > currentStage || stageIndex != currentStage && !IsStageDone(stageIndex) || bForce;

if (bSuccess)
{
using Quest = TESQuest;
PAPYRUS_FUNCTION(bool, Quest, SetCurrentStageID, int);
bSuccess = s_pSetCurrentStageID(this, stageIndex);
}

if (!bSuccess)
{
spdlog::warn(__FUNCTION__ ": returned false quest formId {:X}, currentStage {}, newStage {}, name {}", formID, currentStage, stageIndex, fullName.value.AsAscii());
}

return bSuccess;
}

void TESQuest::ScriptReset()
{
using Quest = TESQuest;
PAPYRUS_FUNCTION(void, Quest, SetCurrentStageID, int);
s_pSetCurrentStageID(this, stageIndex);
PAPYRUS_FUNCTION(void, Quest, Reset);
s_pReset(this);
}

void TESQuest::ScriptResetAndUpdate()
{
ScriptReset();

if (!IsEnabled())
{
if (!IsStopped())
SetStopped();
}

else
{
bool isStarted;
if (flags & StartsEnabled && !EnsureQuestStarted(isStarted, false))
spdlog::warn(__FUNCTION__ ": EnsureQuestStarted failed questId {:X}, is Started {}", formID, isStarted);
}
}


void TESQuest::SetStopped()
{
flags &= 0xFFFE;
MarkChanged(2);
}

bool TESQuest::IsAnyCutscenePlaying()
{
for (const auto& scene : scenes)
{
if (scene->isPlaying)
return true;
}
return false;
}

void BGSScene::ScriptForceStart()
{
spdlog::info(__FUNCTION__ ": force starting scene questId {:X}, sceneId: {:X}, isPlaying? {}", owningQuest->formID, formID, isPlaying);
using Scene = BGSScene;
PAPYRUS_FUNCTION(void, Scene, ForceStart);
s_pForceStart(this);
}

void BGSScene::ScriptStop()
{
spdlog::info(__FUNCTION__ ": stopping scene questId {:X}, sceneId: {:X}, isPlaying? {}", owningQuest->formID, formID, isPlaying);
using Scene = BGSScene;
PAPYRUS_FUNCTION(void, Scene, Stop);
s_pStop(this);
}

static TiltedPhoques::Initializer s_questInitHooks(
[]()
[]()
{
// kill quest init in cold blood
// TiltedPhoques::Write<uint8_t>(25003, 0xC3);
// kill quest init in cold blood
// TiltedPhoques::Write<uint8_t>(25003, 0xC3);
});

Loading
Loading