Reversing Conversation commands (#1077)
* START_SETTING_UP_CONVERSATION(0717) * FINISH_SETTING_UP_CONVERSATION(0719) * IS_CONVERSATION_AT_NODE(071A) * IS_PLAYER_IN_POSITION_FOR_CONVERSATION(089B) * ENABLE_CONVERSATION(089C) * CLEAR_CONVERSATION_FOR_CHAR(08ED) * SET_UP_CONVERSATION_NODE_WITH_SPEECH(09A4) * SET_UP_CONVERSATION_END_NODE_WITH_SPEECH(09AA) * FINISH_SETTING_UP_CONVERSATION_NO_SUBTITLES(0A47) * SET_UP_CONVERSATION_NODE_WITH_SCRIPTED_SPEECH(0A18) * SET_UP_CONVERSATION_END_NODE_WITH_SCRIPTED_SPEECH(0A3C)
This commit is contained in:
parent
f432d91ff6
commit
14cdbf265e
|
|
@ -125,6 +125,7 @@ target_include_directories(${RE_PROJECT_LIB_NAME} PRIVATE
|
|||
game_sa/Audio/Utility
|
||||
|
||||
game_sa/Collision/
|
||||
game_sa/Conversations/
|
||||
game_sa/Core/
|
||||
game_sa/Entity/
|
||||
game_sa/Entity/Dummy/
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@
|
|||
#include "PedIK.h"
|
||||
#include "HandShaker.h"
|
||||
#include "TempColModels.h"
|
||||
#include "Conversations.h"
|
||||
|
||||
// Plant
|
||||
#include "PlantMgr.h"
|
||||
|
|
@ -813,6 +814,7 @@ void InjectHooksMain() {
|
|||
CCustomBuildingRenderer::InjectHooks();
|
||||
CCustomBuildingDNPipeline::InjectHooks();
|
||||
CCustomCarEnvMapPipeline::InjectHooks();
|
||||
CConversations::InjectHooks();
|
||||
|
||||
const auto Pools = [] {
|
||||
CPools::InjectHooks();
|
||||
|
|
|
|||
|
|
@ -522,8 +522,8 @@ bool CAudioEngine::IsAmbienceRadioActive() {
|
|||
}
|
||||
|
||||
// 0x507290
|
||||
void CAudioEngine::PreloadMissionAudio(uint8 slotId, int32 sampleId) {
|
||||
m_ScriptAE.PreloadMissionAudio(slotId, sampleId);
|
||||
void CAudioEngine::PreloadMissionAudio(uint8 slotId, int32 scriptSlotAudioEvent) {
|
||||
m_ScriptAE.PreloadMissionAudio(slotId, scriptSlotAudioEvent);
|
||||
}
|
||||
|
||||
// 0x5072B0
|
||||
|
|
@ -532,38 +532,38 @@ void CAudioEngine::PlayLoadedMissionAudio(uint8 slotId) {
|
|||
}
|
||||
|
||||
// 0x5072D0
|
||||
int32 CAudioEngine::GetMissionAudioEvent(uint8 sampleId) {
|
||||
return m_ScriptAE.GetMissionAudioEvent(sampleId);
|
||||
int32 CAudioEngine::GetMissionAudioEvent(uint8 slotId) {
|
||||
return m_ScriptAE.GetMissionAudioEvent(slotId);
|
||||
}
|
||||
|
||||
// 0x5072E0
|
||||
CVector* CAudioEngine::GetMissionAudioPosition(uint8 sampleId) {
|
||||
return m_ScriptAE.GetMissionAudioPosition(sampleId);
|
||||
CVector* CAudioEngine::GetMissionAudioPosition(uint8 slotId) {
|
||||
return m_ScriptAE.GetMissionAudioPosition(slotId);
|
||||
}
|
||||
|
||||
// 0x5072F0
|
||||
void CAudioEngine::ClearMissionAudio(uint8 sampleId) {
|
||||
m_ScriptAE.ClearMissionAudio(sampleId);
|
||||
void CAudioEngine::ClearMissionAudio(uint8 slotId) {
|
||||
m_ScriptAE.ClearMissionAudio(slotId);
|
||||
}
|
||||
|
||||
// 0x507300
|
||||
void CAudioEngine::SetMissionAudioPosition(uint8 sampleId, CVector& posn) {
|
||||
m_ScriptAE.SetMissionAudioPosition(sampleId, posn);
|
||||
void CAudioEngine::SetMissionAudioPosition(uint8 slotId, CVector& posn) {
|
||||
m_ScriptAE.SetMissionAudioPosition(slotId, posn);
|
||||
}
|
||||
|
||||
// 0x507310
|
||||
CVector* CAudioEngine::AttachMissionAudioToPed(uint8 sampleId, CPed* ped) {
|
||||
return m_ScriptAE.AttachMissionAudioToPhysical(sampleId, ped);
|
||||
CVector* CAudioEngine::AttachMissionAudioToPed(uint8 slotId, CPed* ped) {
|
||||
return m_ScriptAE.AttachMissionAudioToPhysical(slotId, ped);
|
||||
}
|
||||
|
||||
// 0x507320
|
||||
CVector* CAudioEngine::AttachMissionAudioToObject(uint8 sampleId, CObject* object) {
|
||||
return m_ScriptAE.AttachMissionAudioToPhysical(sampleId, object);
|
||||
CVector* CAudioEngine::AttachMissionAudioToObject(uint8 slotId, CObject* object) {
|
||||
return m_ScriptAE.AttachMissionAudioToPhysical(slotId, object);
|
||||
}
|
||||
|
||||
// 0x507330
|
||||
CVector* CAudioEngine::AttachMissionAudioToPhysical(uint8 sampleId, CPhysical* physical) {
|
||||
return m_ScriptAE.AttachMissionAudioToPhysical(sampleId, physical);
|
||||
CVector* CAudioEngine::AttachMissionAudioToPhysical(uint8 slotId, CPhysical* physical) {
|
||||
return m_ScriptAE.AttachMissionAudioToPhysical(slotId, physical);
|
||||
}
|
||||
|
||||
// 0x5073C0
|
||||
|
|
|
|||
|
|
@ -118,17 +118,17 @@ public:
|
|||
void PreloadBeatTrack(int16 trackId);
|
||||
void StopAmbienceTrack(bool a1);
|
||||
static bool DoesAmbienceTrackOverrideRadio();
|
||||
void PreloadMissionAudio(uint8 slotId, int32 sampleId);
|
||||
int8 GetMissionAudioLoadingStatus(uint8 sampleId);
|
||||
void PreloadMissionAudio(uint8 slotId, int32 scriptSlotAudioEvent);
|
||||
int8 GetMissionAudioLoadingStatus(uint8 slotId);
|
||||
void PlayLoadedMissionAudio(uint8 slotId);
|
||||
int32 GetMissionAudioEvent(uint8 sampleId);
|
||||
CVector* GetMissionAudioPosition(uint8 sampleId);
|
||||
void ClearMissionAudio(uint8 sampleId);
|
||||
void SetMissionAudioPosition(uint8 sampleId, CVector& posn);
|
||||
int32 GetMissionAudioEvent(uint8 slotId);
|
||||
CVector* GetMissionAudioPosition(uint8 slotId);
|
||||
void ClearMissionAudio(uint8 slotId);
|
||||
void SetMissionAudioPosition(uint8 slotId, CVector& posn);
|
||||
|
||||
CVector* AttachMissionAudioToPed(uint8 sampleId, CPed* ped);
|
||||
CVector* AttachMissionAudioToObject(uint8 sampleId, CObject* object);
|
||||
CVector* AttachMissionAudioToPhysical(uint8 sampleId, CPhysical* physical);
|
||||
CVector* AttachMissionAudioToPed(uint8 slotId, CPed* ped);
|
||||
CVector* AttachMissionAudioToObject(uint8 slotId, CObject* object);
|
||||
CVector* AttachMissionAudioToPhysical(uint8 slotId, CPhysical* physical);
|
||||
|
||||
void SayPedless(eAudioEvents audioEvent, eGlobalSpeechContext gCtx, CEntity* attachTo, uint32 startTimeDelayMs, float probability, bool overrideSilence, bool isForceAudible, bool isFrontEnd);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
#include "StdInc.h"
|
||||
|
||||
#include "Conversations.h"
|
||||
|
||||
void CPedToPlayerConversations::Clear() {
|
||||
ZoneScoped;
|
||||
|
||||
plugin::Call<0x43AAE0>();
|
||||
}
|
||||
|
||||
void CPedToPlayerConversations::Update() {
|
||||
plugin::Call<0x43B0F0>();
|
||||
}
|
||||
|
||||
void CConversations::Clear() {
|
||||
ZoneScoped;
|
||||
|
||||
plugin::Call<0x43A7B0>();
|
||||
}
|
||||
|
||||
void CConversations::Update() {
|
||||
ZoneScoped;
|
||||
|
||||
plugin::Call<0x43C590>();
|
||||
}
|
||||
|
||||
// 0x43A870
|
||||
void CConversations::SetUpConversationNode(
|
||||
const char* questionKey,
|
||||
const char* answerYesKey,
|
||||
const char* answerNoKey,
|
||||
int32 questionWAV,
|
||||
int32 answerYesWAV,
|
||||
int32 answerNoWAV) {
|
||||
plugin::Call<0x43A870, const char*, const char*, const char*, int32, int32, int32>(questionKey, answerYesKey, answerNoKey, questionWAV, answerYesWAV, answerNoWAV);
|
||||
}
|
||||
|
||||
// 0x43A960
|
||||
void CConversations::RemoveConversationForPed(CPed* ped) {
|
||||
plugin::Call<0x43A960, CPed*>(ped);
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
class CPed;
|
||||
|
||||
class CPedToPlayerConversations {
|
||||
public:
|
||||
static inline CPed*& m_pPed = *(CPed**)0x9691C0;
|
||||
|
||||
static void Clear();
|
||||
static void Update();
|
||||
};
|
||||
|
||||
class CConversations {
|
||||
public:
|
||||
static void Clear();
|
||||
static void RemoveConversationForPed(CPed* ped);
|
||||
|
||||
static void Update();
|
||||
static void SetUpConversationNode(const char*, const char*, const char*, int32, int32, int32);
|
||||
/* Check the signatures before starting work
|
||||
static bool IsPlayerInPositionForConversation(CPed* ped, bool);
|
||||
static bool IsConversationGoingOn();
|
||||
static bool IsConversationAtNode(char*, CPed* ped);
|
||||
static void GetConversationStatus(CPed* ped);
|
||||
static void EnableConversation(CPed* ped, bool);
|
||||
static void DoneSettingUpConversation(bool);
|
||||
static void FindFreeNodeSlot();
|
||||
static void FindConversationForPed(CPed* ped);
|
||||
static void FindFreeConversationSlot();
|
||||
static void StartSettingUpConversation(CPed* ped);
|
||||
static void AwkwardSay(int32, CPed* ped);
|
||||
*/
|
||||
};
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
#include "ConversationForPed.h"
|
||||
#include "Conversations.h"
|
||||
|
||||
void CConversationForPed::InjectHooks() {
|
||||
RH_ScopedClass(CConversationForPed);
|
||||
RH_ScopedCategory("Conversations");
|
||||
RH_ScopedInstall(Update, 0x43C190, {.reversed = false});
|
||||
RH_ScopedInstall(IsPlayerInPositionForConversation, 0x43AC40);
|
||||
}
|
||||
|
||||
void CConversationForPed::Clear(bool dontClearNodes) {
|
||||
if (!dontClearNodes) {
|
||||
CConversations::m_Nodes[m_FirstNode].ClearRecursively();
|
||||
}
|
||||
m_FirstNode = -1;
|
||||
m_CurrentNode = -1;
|
||||
m_pPed = nullptr;
|
||||
m_LastChange = 0;
|
||||
m_LastTimeWeWereCloseEnough = 0;
|
||||
}
|
||||
|
||||
// 0x43C190
|
||||
void CConversationForPed::Update() {
|
||||
plugin::CallMethod<0x43C190, CConversationForPed*>(this);
|
||||
}
|
||||
|
||||
// 0x43AC40
|
||||
bool CConversationForPed::IsPlayerInPositionForConversation(bool randomConversation) {
|
||||
return plugin::CallMethodAndReturn<bool, 0x43AC40, CConversationForPed*, bool>(this, randomConversation);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
class CPed;
|
||||
|
||||
class CConversationForPed {
|
||||
public:
|
||||
enum eStatus : uint32 {
|
||||
INACTIVE = 0,
|
||||
PLAYER_SPEAKING,
|
||||
PED_SPEAKING,
|
||||
WAITINGFORINPUT
|
||||
};
|
||||
|
||||
int32 m_FirstNode;
|
||||
int32 m_CurrentNode;
|
||||
CPed* m_pPed;
|
||||
uint32 m_LastChange;
|
||||
uint32 m_LastTimeWeWereCloseEnough;
|
||||
eStatus m_Status;
|
||||
bool m_Enabled;
|
||||
bool m_SuppressSubtitles;
|
||||
|
||||
static void InjectHooks();
|
||||
void Clear(bool dontClearNodes);
|
||||
void Update();
|
||||
bool IsPlayerInPositionForConversation(bool randomConversation);
|
||||
};
|
||||
|
||||
VALIDATE_SIZE(CConversationForPed, 0x1C);
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#include "ConversationNode.h"
|
||||
#include "Conversations.h"
|
||||
|
||||
void CConversationNode::InjectHooks() {
|
||||
RH_ScopedClass(CConversationNode);
|
||||
RH_ScopedCategory("Conversations");
|
||||
RH_ScopedInstall(ClearRecursively, 0x43A7A0);
|
||||
}
|
||||
|
||||
void CConversationNode::Clear() {
|
||||
m_Name[0] = '\0';
|
||||
m_NodeYes = -1;
|
||||
m_NodeNo = -1;
|
||||
m_Speech = 0;
|
||||
m_SpeechY = 0;
|
||||
m_SpeechN = 0;
|
||||
}
|
||||
|
||||
// 0x43A710
|
||||
void CConversationNode::ClearRecursively() {
|
||||
if (m_NodeYes >= 0) {
|
||||
CConversations::m_Nodes[m_NodeYes].ClearRecursively();
|
||||
}
|
||||
if (m_NodeNo >= 0) {
|
||||
CConversations::m_Nodes[m_NodeNo].ClearRecursively();
|
||||
}
|
||||
Clear();
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
class CConversationNode {
|
||||
public:
|
||||
char m_Name[8];
|
||||
int16 m_NodeYes;
|
||||
int16 m_NodeNo;
|
||||
int32 m_Speech;
|
||||
int32 m_SpeechY;
|
||||
int32 m_SpeechN;
|
||||
|
||||
static void InjectHooks();
|
||||
void Clear();
|
||||
void ClearRecursively();
|
||||
};
|
||||
|
||||
VALIDATE_SIZE(CConversationNode, 0x18);
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
#include "StdInc.h"
|
||||
#include "Conversations.h"
|
||||
#include "ConversationForPed.h"
|
||||
|
||||
void CConversations::InjectHooks() {
|
||||
RH_ScopedClass(CConversations);
|
||||
RH_ScopedCategory("Conversations");
|
||||
|
||||
RH_ScopedInstall(Clear, 0x43A7B0);
|
||||
RH_ScopedInstall(Update, 0x43C590);
|
||||
RH_ScopedInstall(SetUpConversationNode, 0x43A870);
|
||||
RH_ScopedInstall(RemoveConversationForPed, 0x43A960);
|
||||
RH_ScopedInstall(IsPlayerInPositionForConversation, 0x43B0B0);
|
||||
RH_ScopedInstall(IsConversationGoingOn, 0x43AAC0);
|
||||
RH_ScopedInstall(IsConversationAtNode, 0x43B000);
|
||||
RH_ScopedInstall(AwkwardSay, 0x43A810);
|
||||
RH_ScopedInstall(EnableConversation, 0x43A7F0);
|
||||
RH_ScopedInstall(StartSettingUpConversation, 0x43A840);
|
||||
RH_ScopedInstall(DoneSettingUpConversation, 0x43ADB0, {.reversed = false});
|
||||
}
|
||||
|
||||
// 0x43A7B0
|
||||
void CConversations::Clear() {
|
||||
ZoneScoped;
|
||||
|
||||
for (auto& conversation : m_Conversations) {
|
||||
conversation.Clear(true);
|
||||
}
|
||||
|
||||
for (auto& node : m_Nodes) {
|
||||
node.Clear();
|
||||
}
|
||||
|
||||
m_SettingUpConversation = 0;
|
||||
m_AwkwardSayStatus = eAwkwardSayStatus::INACTIVE;
|
||||
}
|
||||
|
||||
// 0x43C590
|
||||
void CConversations::Update() {
|
||||
ZoneScoped;
|
||||
|
||||
const auto updateConversations = [&]() {
|
||||
for (auto& conversation : m_Conversations) {
|
||||
conversation.Update();
|
||||
}
|
||||
};
|
||||
|
||||
switch (m_AwkwardSayStatus) {
|
||||
case eAwkwardSayStatus::LOADING:
|
||||
if (AudioEngine.GetMissionAudioLoadingStatus(0) == 1) {
|
||||
AudioEngine.PlayLoadedMissionAudio(0);
|
||||
m_AwkwardSayStatus = eAwkwardSayStatus::PLAYING;
|
||||
}
|
||||
break;
|
||||
case eAwkwardSayStatus::PLAYING:
|
||||
if (AudioEngine.IsMissionAudioSampleFinished(0)) {
|
||||
m_AwkwardSayStatus = eAwkwardSayStatus::INACTIVE;
|
||||
updateConversations();
|
||||
}
|
||||
break;
|
||||
case eAwkwardSayStatus::INACTIVE:
|
||||
updateConversations();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 0x43A870
|
||||
void CConversations::SetUpConversationNode(
|
||||
const char* questionKey,
|
||||
const char* answerYesKey,
|
||||
const char* answerNoKey,
|
||||
int32 questionWAV,
|
||||
int32 answerYesWAV,
|
||||
int32 answerNoWAV
|
||||
) {
|
||||
auto& node = CConversations::m_aTempNodes[CConversations::m_SettingUpConversationNumNodes];
|
||||
strncpy(node.m_Name, questionKey, 6u);
|
||||
MakeUpperCase(node.m_Name);
|
||||
|
||||
node.m_Speech = questionWAV;
|
||||
node.m_SpeechY = answerYesWAV;
|
||||
node.m_SpeechN = answerNoWAV;
|
||||
|
||||
if (answerYesKey) {
|
||||
strncpy(node.m_NameNodeYes, answerYesKey, 6u);
|
||||
MakeUpperCase(node.m_NameNodeYes);
|
||||
} else {
|
||||
node.m_NameNodeYes[0] = '\0';
|
||||
}
|
||||
if (answerNoKey) {
|
||||
strncpy(node.m_NameNodeNo, answerNoKey, 6u);
|
||||
MakeUpperCase(node.m_NameNodeNo);
|
||||
} else {
|
||||
node.m_NameNodeNo[0] = '\0';
|
||||
}
|
||||
++CConversations::m_SettingUpConversationNumNodes;
|
||||
}
|
||||
|
||||
// 0x43A960
|
||||
void CConversations::RemoveConversationForPed(CPed* ped) {
|
||||
for (auto& conversation : m_Conversations) {
|
||||
if (conversation.m_pPed == ped) {
|
||||
conversation.Clear(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0x43B0B0
|
||||
bool CConversations::IsPlayerInPositionForConversation(CPed* ped, bool randomConversation) {
|
||||
return FindConversationForPed(ped)->IsPlayerInPositionForConversation(randomConversation);
|
||||
}
|
||||
|
||||
// 0x43AAC0
|
||||
bool CConversations::IsConversationGoingOn() {
|
||||
for (const auto& conversation : m_Conversations) {
|
||||
if (conversation.m_Status != CConversationForPed::eStatus::INACTIVE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 0x43B000
|
||||
bool CConversations::IsConversationAtNode(const char* pName, CPed* pPed) {
|
||||
auto conversation = FindConversationForPed(pPed);
|
||||
assert(conversation);
|
||||
|
||||
if (conversation->m_CurrentNode < 0 || conversation->m_Status == CConversationForPed::eStatus::PLAYER_SPEAKING) {
|
||||
return false;
|
||||
}
|
||||
// NOTSA - using stricmp instead of MakeUpperCase + strcmp
|
||||
return !stricmp(pName, CConversations::m_Nodes[conversation->m_CurrentNode].m_Name);
|
||||
}
|
||||
|
||||
// 0x43A810
|
||||
void CConversations::AwkwardSay(int32 whatToSay, CPed* speaker) {
|
||||
AudioEngine.PreloadMissionAudio(0, whatToSay);
|
||||
AudioEngine.AttachMissionAudioToPed(0, speaker);
|
||||
m_AwkwardSayStatus = eAwkwardSayStatus::LOADING;
|
||||
}
|
||||
|
||||
// 0x43AA40
|
||||
void CConversations::EnableConversation(CPed* ped, bool enabled) {
|
||||
FindConversationForPed(ped)->m_Enabled = enabled;
|
||||
}
|
||||
|
||||
// 0x43A840
|
||||
void CConversations::StartSettingUpConversation(CPed* ped) {
|
||||
m_SettingUpConversationPed = ped;
|
||||
ped->RegisterReference(m_SettingUpConversationPed);
|
||||
m_SettingUpConversationNumNodes = 0;
|
||||
m_SettingUpConversation = true;
|
||||
}
|
||||
|
||||
// 0x43ADB0
|
||||
void CConversations::DoneSettingUpConversation(bool bSuppressSubtitles) {
|
||||
plugin::Call<0x43ADB0, bool>(bSuppressSubtitles);
|
||||
}
|
||||
|
||||
CConversationForPed* CConversations::FindConversationForPed(CPed* ped) {
|
||||
for (auto& conversation : m_Conversations) {
|
||||
if (conversation.m_pPed == ped) {
|
||||
return &conversation;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include "ConversationForPed.h"
|
||||
#include "PedToPlayerConversations.h"
|
||||
#include "ConversationNode.h"
|
||||
|
||||
class CPed;
|
||||
|
||||
enum {
|
||||
MAX_NUM_CONVERSATIONS = 14,
|
||||
MAX_NUM_CONVERSATION_NODES = 50,
|
||||
MAX_NUM_TEMP_CONVERSATION_NODES = 12,
|
||||
};
|
||||
|
||||
class CTempConversationNode {
|
||||
public:
|
||||
char m_Name[8];
|
||||
char m_NameNodeYes[8];
|
||||
char m_NameNodeNo[8];
|
||||
int32 m_FinalSlot;
|
||||
int16 m_NodeYes;
|
||||
int16 m_NodeNo;
|
||||
int32 m_Speech;
|
||||
int32 m_SpeechY;
|
||||
int32 m_SpeechN;
|
||||
//void Clear();
|
||||
};
|
||||
|
||||
VALIDATE_SIZE(CTempConversationNode, 0x2C);
|
||||
|
||||
class CConversations {
|
||||
public:
|
||||
enum class eAwkwardSayStatus : int32 {
|
||||
INACTIVE = 0,
|
||||
LOADING = 1,
|
||||
PLAYING = 2,
|
||||
};
|
||||
|
||||
static inline eAwkwardSayStatus& m_AwkwardSayStatus = StaticRef<eAwkwardSayStatus, 0x9691C4>();
|
||||
static inline bool& m_SettingUpConversation = StaticRef<bool, 0x9691D0>();
|
||||
static inline auto& m_Conversations = StaticRef<std::array<CConversationForPed, MAX_NUM_CONVERSATIONS>, 0x9691D8>();
|
||||
static inline auto& m_Nodes = StaticRef<std::array<CConversationNode, MAX_NUM_CONVERSATION_NODES>, 0x969570>();
|
||||
static inline auto& m_aTempNodes = StaticRef<std::array<CTempConversationNode, MAX_NUM_TEMP_CONVERSATION_NODES>, 0x969360>();
|
||||
static inline auto& m_SettingUpConversationNumNodes = StaticRef<int32, 0x9691C8>();
|
||||
static inline auto& m_SettingUpConversationPed = StaticRef<CPed*, 0x9691CC>();
|
||||
|
||||
static void InjectHooks();
|
||||
static void Clear();
|
||||
static void RemoveConversationForPed(CPed* ped);
|
||||
|
||||
static void Update();
|
||||
static void SetUpConversationNode(const char* questionKey, const char* answerYesKey, const char* answerNoKey, int32 questionWAV, int32 answerYesWAV, int32 answerNoWAV);
|
||||
static bool IsPlayerInPositionForConversation(CPed* ped, bool randomConversation);
|
||||
static bool IsConversationGoingOn();
|
||||
static CConversationForPed* FindConversationForPed(CPed* ped);
|
||||
static bool IsConversationAtNode(const char* pName, CPed* pPed);
|
||||
static void AwkwardSay(int32 whatToSay, CPed* speaker);
|
||||
static void EnableConversation(CPed* ped, bool enabled);
|
||||
static void StartSettingUpConversation(CPed* ped);
|
||||
static void DoneSettingUpConversation(bool bSuppressSubtitles);
|
||||
|
||||
/*
|
||||
static void FindFreeNodeSlot();
|
||||
static void FindFreeConversationSlot();
|
||||
|
||||
*/
|
||||
};
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#include "PedToPlayerConversations.h"
|
||||
#include "IKChainManager_c.h"
|
||||
|
||||
void CPedToPlayerConversations::InjectHooks() {
|
||||
RH_ScopedClass(CPedToPlayerConversations);
|
||||
RH_ScopedCategory("Conversations");
|
||||
RH_ScopedInstall(Clear, 0x43AAE0);
|
||||
RH_ScopedInstall(Update, 0x43B0F0, {.reversed = false});
|
||||
RH_ScopedInstall(EndConversation, 0x43AB10);
|
||||
}
|
||||
|
||||
// 0x43AAE0
|
||||
void CPedToPlayerConversations::Clear() {
|
||||
ZoneScoped;
|
||||
|
||||
if (m_State != eP2pState::INACTIVE) {
|
||||
CAEPedSpeechAudioEntity::ReleasePlayerConversation();
|
||||
m_State = eP2pState::INACTIVE;
|
||||
}
|
||||
|
||||
m_pPed = nullptr;
|
||||
m_TimeOfLastPlayerConversation = 0;
|
||||
}
|
||||
|
||||
// 0x43B0F0
|
||||
void CPedToPlayerConversations::Update() {
|
||||
plugin::Call<0x43B0F0>();
|
||||
}
|
||||
|
||||
// 0x43AB10
|
||||
void CPedToPlayerConversations::EndConversation() {
|
||||
m_State = eP2pState::INACTIVE;
|
||||
|
||||
CAEPedSpeechAudioEntity::ReleasePlayerConversation();
|
||||
|
||||
if (m_pPed) {
|
||||
m_pPed->EnablePedSpeech();
|
||||
}
|
||||
|
||||
const auto player = FindPlayerPed(-1);
|
||||
if (g_ikChainMan.IsLooking(player)) {
|
||||
g_ikChainMan.AbortLookAt(player, 250);
|
||||
}
|
||||
|
||||
if (m_pPed && g_ikChainMan.IsLooking(m_pPed)) {
|
||||
return g_ikChainMan.AbortLookAt(m_pPed, 250);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
class CPed;
|
||||
|
||||
class CPedToPlayerConversations {
|
||||
public:
|
||||
enum class eP2pState : uint32 {
|
||||
INACTIVE = 0,
|
||||
PEDHASOPENED,
|
||||
WAITINGFORFINALWORD,
|
||||
WAITINGTOFINISH,
|
||||
};
|
||||
|
||||
static inline eP2pState& m_State = StaticRef<eP2pState, 0x969A20>();
|
||||
static inline CPed*& m_pPed = StaticRef<CPed*, 0x9691C0>();
|
||||
static inline int32& m_Topic = StaticRef<int32, 0x9691BC>();
|
||||
static inline uint32& m_TimeOfLastPlayerConversation = StaticRef<uint32, 0x9691B4>();
|
||||
static inline uint32& m_StartTime = StaticRef<uint32, 0x9691B8>();
|
||||
static inline bool& m_bPositiveReply = StaticRef<bool, 0x9691B0>(); // unused
|
||||
static inline bool& m_bPositiveOpening = StaticRef<bool, 0x9691B1>();
|
||||
|
||||
static void InjectHooks();
|
||||
static void Clear();
|
||||
static void Update();
|
||||
static void EndConversation();
|
||||
};
|
||||
|
|
@ -7,6 +7,7 @@ namespace basic { void RegisterHandlers(); };
|
|||
namespace camera { void RegisterHandlers(); };
|
||||
namespace character { void RegisterHandlers(); };
|
||||
namespace clock { void RegisterHandlers(); };
|
||||
namespace conversation { void RegisterHandlers(); };
|
||||
namespace comparasion { void RegisterHandlers(); };
|
||||
namespace game { void RegisterHandlers(); };
|
||||
namespace generic { void RegisterHandlers(); };
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
#include <StdInc.h>
|
||||
|
||||
#include "Commands.hpp"
|
||||
#include <CommandParser/Parser.hpp>
|
||||
#include "Conversations.h"
|
||||
|
||||
using namespace notsa::script;
|
||||
|
||||
|
||||
/*!
|
||||
* Various conversation commands
|
||||
*/
|
||||
namespace {
|
||||
|
||||
/// START_SETTING_UP_CONVERSATION(0717)
|
||||
void StartSettingUpConversation(CPed& ped) {
|
||||
CConversations::StartSettingUpConversation(&ped);
|
||||
}
|
||||
|
||||
/// FINISH_SETTING_UP_CONVERSATION(0719)
|
||||
void FinishSettingUpConversation() {
|
||||
CConversations::DoneSettingUpConversation(false);
|
||||
}
|
||||
|
||||
/// IS_CONVERSATION_AT_NODE(071A)
|
||||
bool IsConversationAtNode(CPed& ped, const char* nodeName) {
|
||||
return CConversations::IsConversationAtNode(nodeName, &ped);
|
||||
}
|
||||
|
||||
/// IS_PLAYER_IN_POSITION_FOR_CONVERSATION(089B)
|
||||
bool IsPlayerInPositionForConversation(CPed& ped) {
|
||||
return CConversations::IsPlayerInPositionForConversation(&ped, false);
|
||||
}
|
||||
|
||||
/// ENABLE_CONVERSATION(089C)
|
||||
void EnableConversation(CPed& ped, bool state) {
|
||||
CConversations::EnableConversation(&ped, state);
|
||||
}
|
||||
|
||||
/// CLEAR_CONVERSATION_FOR_CHAR(08ED)
|
||||
void ClearConversationForChar(CPed* ped) {
|
||||
if (ped) {
|
||||
CConversations::RemoveConversationForPed(ped);
|
||||
}
|
||||
}
|
||||
|
||||
/// SET_UP_CONVERSATION_NODE_WITH_SPEECH(09A4)
|
||||
void SetUpConversationNodeWithSpeech(const char* questionKey, const char* answerYesKey, const char* answerNoKey, int32 questionWAV, int32 answerYesWAV, int32 answerNoWAV) {
|
||||
CConversations::SetUpConversationNode(questionKey, answerYesKey, answerNoKey, questionWAV, answerYesWAV, answerNoWAV);
|
||||
}
|
||||
|
||||
/// SET_UP_CONVERSATION_END_NODE_WITH_SPEECH(09AA)
|
||||
void SetUpConversationEndNodeWithSpeech(const char* questionKey, int32 questionWAV) {
|
||||
CConversations::SetUpConversationNode(questionKey, nullptr, nullptr, questionWAV, 0, 0);
|
||||
}
|
||||
|
||||
/// FINISH_SETTING_UP_CONVERSATION_NO_SUBTITLES(0A47)
|
||||
void FinishSettingUpConversationNoSubtitles() {
|
||||
CConversations::DoneSettingUpConversation(true);
|
||||
}
|
||||
|
||||
/// SET_UP_CONVERSATION_NODE_WITH_SCRIPTED_SPEECH(0A18)
|
||||
void SetUpConversationNodeWithScriptedSpeech(const char* questionKey, const char* answerYesKey, const char* answerNoKey, int32 questionWAV, int32 answerYesWAV, int32 answerNoWAV) {
|
||||
CConversations::SetUpConversationNode(questionKey, answerYesKey, answerNoKey, -questionWAV, -answerYesWAV, -answerNoWAV);
|
||||
}
|
||||
|
||||
/// SET_UP_CONVERSATION_END_NODE_WITH_SCRIPTED_SPEECH(0A3C)
|
||||
void SetUpConversationEndNodeWithScriptedSpeech(const char* questionKey, int32 questionWAV) {
|
||||
CConversations::SetUpConversationNode(questionKey, nullptr, nullptr, -questionWAV, 0, 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void notsa::script::commands::conversation::RegisterHandlers() {
|
||||
REGISTER_COMMAND_HANDLER_BEGIN("Conversation");
|
||||
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_IS_CONVERSATION_AT_NODE, IsConversationAtNode);
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_ENABLE_CONVERSATION, EnableConversation);
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_CLEAR_CONVERSATION_FOR_CHAR, ClearConversationForChar);
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_START_SETTING_UP_CONVERSATION, StartSettingUpConversation);
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_FINISH_SETTING_UP_CONVERSATION, FinishSettingUpConversation);
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_FINISH_SETTING_UP_CONVERSATION_NO_SUBTITLES, FinishSettingUpConversationNoSubtitles);
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_SET_UP_CONVERSATION_NODE_WITH_SPEECH, SetUpConversationNodeWithSpeech);
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_IS_PLAYER_IN_POSITION_FOR_CONVERSATION, IsPlayerInPositionForConversation);
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_SET_UP_CONVERSATION_END_NODE_WITH_SPEECH, SetUpConversationEndNodeWithSpeech);
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_SET_UP_CONVERSATION_NODE_WITH_SCRIPTED_SPEECH, SetUpConversationNodeWithScriptedSpeech);
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_SET_UP_CONVERSATION_END_NODE_WITH_SCRIPTED_SPEECH, SetUpConversationEndNodeWithScriptedSpeech);
|
||||
}
|
||||
|
|
@ -16,8 +16,10 @@
|
|||
|
||||
namespace {
|
||||
void TerminateAllScriptsWithThisName(const char* name) {
|
||||
std::string scriptName{name};
|
||||
rng::transform(scriptName, scriptName.begin(), [](char c) { return std::tolower(c); });
|
||||
std::string scriptName{ name };
|
||||
rng::transform(scriptName, scriptName.begin(), [](char c) {
|
||||
return std::tolower(c);
|
||||
});
|
||||
|
||||
for (auto* script = CTheScripts::pActiveScripts; script; script = script->m_pNext) {
|
||||
if (!strcmp(scriptName.c_str(), script->m_szName)) {
|
||||
|
|
@ -61,7 +63,6 @@ void ReportMissionAudioEventAtCar(CVehicle& vehicle, int eventId) {
|
|||
AudioEngine.ReportMissionAudioEvent(eventId, &vehicle);
|
||||
}
|
||||
|
||||
|
||||
void PlayMissionAudio(uint32 slotId) {
|
||||
AudioEngine.PlayLoadedMissionAudio(slotId - 1);
|
||||
}
|
||||
|
|
@ -86,15 +87,6 @@ void SetPlayerInStadium(bool enable) {
|
|||
CTheScripts::bPlayerIsOffTheMap = enable;
|
||||
}
|
||||
|
||||
void SetUpConversationNodeWithScriptedSpeech(
|
||||
const char* questionKey,
|
||||
const char* answerYesKey,
|
||||
const char* answerNoKey,
|
||||
int32 questionWAV,
|
||||
int32 answerYesWAV,
|
||||
int32 answerNoWAV) {
|
||||
CConversations::SetUpConversationNode(questionKey, answerYesKey, answerNoKey, questionWAV, answerYesWAV, answerNoWAV);
|
||||
}
|
||||
};
|
||||
|
||||
void notsa::script::commands::script::RegisterHandlers() {
|
||||
|
|
@ -114,5 +106,4 @@ void notsa::script::commands::script::RegisterHandlers() {
|
|||
REGISTER_COMMAND_HANDLER(COMMAND_DRAW_ODDJOB_TITLE_BEFORE_FADE, DrawOddJobTitleBeforeFade);
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_DRAW_SUBTITLES_BEFORE_FADE, DrawSubtitlesBeforeFade);
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_SET_PLAYER_IS_IN_STADIUM, SetPlayerInStadium);
|
||||
REGISTER_COMMAND_HANDLER(COMMAND_SET_UP_CONVERSATION_NODE_WITH_SCRIPTED_SPEECH, SetUpConversationNodeWithScriptedSpeech);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ void CRunningScript::InjectCustomCommandHooks() {
|
|||
c::vehicle::RegisterHandlers();
|
||||
c::zone::RegisterHandlers();
|
||||
c::stat::RegisterHandlers();
|
||||
c::conversation::RegisterHandlers();
|
||||
|
||||
#ifdef NOTSA_WITH_CLEO_SCRIPT_COMMANDS
|
||||
cleo::audiostream::RegisterHandlers();
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ void InjectCommonHooks() {
|
|||
RH_ScopedNamespaceName("Common");
|
||||
RH_ScopedCategory("Common");
|
||||
|
||||
RH_ScopedGlobalInstall(MakeUpperCase, 0x7186E0);
|
||||
RH_ScopedGlobalOverloadedInstall(MakeUpperCase, "", 0x7186E0, char* (*)(char*, const char*));
|
||||
RH_ScopedGlobalOverloadedInstall(MakeUpperCase, "in", 0x718710, char* (*)(char*));
|
||||
RH_ScopedGlobalInstall(AsciiToGxtChar, 0x718600);
|
||||
RH_ScopedGlobalInstall(WriteRaster, 0x005A4150);
|
||||
RH_ScopedGlobalOverloadedInstall(CalcScreenCoors, "VVff", 0x71DA00, bool(*)(const CVector&, CVector&, float&, float&), { .reversed = true });
|
||||
|
|
@ -77,6 +78,15 @@ char* MakeUpperCase(char* dest, const char* src) {
|
|||
return dest;
|
||||
}
|
||||
|
||||
// 0x718710
|
||||
// in-place version
|
||||
char* MakeUpperCase(char* dest) {
|
||||
for (char* p = dest; *p; p++) {
|
||||
*p = std::toupper(*p);
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
// NOTSA
|
||||
bool EndsWith(const char* str, const char* with, bool caseSensitive) {
|
||||
const auto strsz = strlen(str), withsz = strlen(with);
|
||||
|
|
|
|||
|
|
@ -247,6 +247,7 @@ constexpr uint32 MakeFourCC(const char fourcc[4]) {
|
|||
}
|
||||
|
||||
char* MakeUpperCase(char *dest, const char *src);
|
||||
char* MakeUpperCase(char* dest);
|
||||
bool EndsWith(const char* str, const char* with, bool caseSensitive = true);
|
||||
|
||||
RpAtomic* RemoveRefsCB(RpAtomic* atomic, void* _IGNORED_ data);
|
||||
|
|
|
|||
Loading…
Reference in New Issue