HughJanus 244 Posted August 12, 2020 Posted August 12, 2020 (edited) I was wondering if anybody knew how to make NPCs talk. I have been searching the game files with OpenIV and have found some speech samples I would like to use, but have no idea how. Under "x64\audio\sfx" there are multiple PED_XX.rpf files which hold awc files (for each NPC type like "white townfolk", there is a file) which again hold a lot of speech files. Those speech files have the same hashes/names throughout the different NPC types, so there must be a generic option of calling them (like the PLAY_PAIN - which automatically plays the pain sound fitting the NPC). Does anybody know which natives to use and how to use them to get NPCs to speak (like saying "Piss off!" or some other generic lines)? Edited November 15, 2020 by HughJanus Quote
LMS 676 Posted August 12, 2020 Posted August 12, 2020 You can use the native below. It needs a struct as argument though, which you need to set up properly first. Like this: var1[0] = speechline; var1[1] = voice; // Can be 0 var1[2] = 0; // uparam5 var1[3] = 291934926; // iparam11 var1[4] = target; // Ped target var1[5] = NativeFunction.Call<int>(0x10FAB35428CCC9D7); // network is game in progress var1[6] = 1; // bool at the end? Then you can use it with lines like "GENERIC_THANKS" or "GOING_WELL". 1 Quote
HughJanus 244 Posted August 14, 2020 Author Posted August 14, 2020 (edited) Thanks a lot. I'll try that. What is the second argument? If the first is the container including the speech line and the ped, what is the second one? Also which container are you using? In C++ an array of type Any cant hold e.g. char*. Edited August 14, 2020 by HughJanus Quote
LMS 676 Posted August 14, 2020 Posted August 14, 2020 The first is the ped, the second is the container. And there is no "type" for this, you can define your own struct or just use new memory and pass a pointer to that. 1 Quote
HughJanus 244 Posted August 15, 2020 Author Posted August 15, 2020 (edited) Thanks, LMS! Gonna try it out soon. Edit: Just tried it and it didnt work the way I did it. Short summary: I use the script hook natives and the wrapper for the function looks as follows static BOOL _PLAY_AMBIENT_SPEECH1(Ped ped, char* speechName) { return invoke<BOOL>(0x8E04FEDD28D42462, ped, speechName); } I copied that and adjusted it static BOOL x_PLAY_AMBIENT_SPEECH1(Ped ped, boost::any p1) { return invoke<BOOL>(0x8E04FEDD28D42462, ped, p1); } I used the type "any" from the boost-library, so I didnt have to make my own^^ The code in my helper function looks as follows: void PedFear(Ped ped) { //container for speechlines std::map<int, char*> speech; speech[1] = "PANIC_HELP"; speech[2] = "GENERIC_FRIGHTENED_HIGH"; speech[3] = "GUN"; speech[4] = "GUN_RUN"; speech[5] = "INTIMIDATED_GEN"; speech[6] = "INTIMIDATED_AGAIN_GEN"; speech[7] = "PLEAD"; speech[8] = "SCARED_HELP"; //iterator for the container std::map<int, char*>::iterator speechl_it = speech.begin(); //numbers for randomizing the speech lines int linerand = 1 + (std::rand() % (speech.size() - 1 + 1)); int linecounter = 1; while (speechl_it != speech.end()) { if (linerand == linecounter) { char* speechline = speechl_it->second; boost::any var[7]; var[0] = speechline; // speechline var[1] = 0; // voice - Can be 0 var[2] = 0; // uparam5 var[3] = 291934926; // iparam11 var[4] = ped; // Ped target var[5] = NETWORK::NETWORK_IS_GAME_IN_PROGRESS(); // network is game in progress var[6] = 1; // bool at the end? AUDIO::_PLAY_AMBIENT_SPEECH1(ped, var); return; } linecounter++; speechl_it++; } } But in game the game throws a script hook error as soon as I activate the mod. Am I not allowed to use the "any" type from the boost library? Edited August 15, 2020 by HughJanus Quote
LMS 676 Posted August 15, 2020 Posted August 15, 2020 Not sure how boost::any works internally, but I'd just use "raw" memory for it. It seems someone also defined a type for it though, so you can try this: struct ScriptedSpeechParams { const char* speechName; const char* voiceName; alignas(8) int v3; alignas(8) Hash speechParamHash; alignas(8) Entity entity; alignas(8) BOOL v6; alignas(8) int v7; alignas(8) int v8; }; static_assert(sizeof(ScriptedSpeechParams) == 0x40, "incorrect ScriptedSpeechParams size"); Example: ScriptedSpeechParams params{"RE_PH_RHD_V3_AGGRO", "0405_U_M_M_RhdSheriff_01", 1, 0x67F3AB43, 0, true, 1, 1}; _PLAY_AMBIENT_SPEECH1(PLAYER_PED_ID(), (Any*)¶ms); Quote
HughJanus 244 Posted August 16, 2020 Author Posted August 16, 2020 (edited) I tried the following (adapted the construct to the parameters you posted further above): struct ScriptedSpeechParams { const char* speechName; const char* voiceName; alignas(8) int v3; alignas(8) Hash speechParamHash; alignas(8) Entity entity; alignas(8) BOOL v6; alignas(8) BOOL v7; }; static_assert(sizeof(ScriptedSpeechParams) == 0x38, "incorrect ScriptedSpeechParams size"); //declaring the line (taken out of a container) char* speechline = speechl_it->second; ScriptedSpeechParams params{speechline, 0, 0, 291934926, ped, NETWORK::NETWORK_IS_GAME_IN_PROGRESS(), 1}; AUDIO::x_PLAY_AMBIENT_SPEECH1(ped, ¶ms); But unfortunately it didnt work. Then I tried it with the exact same construct, but different parameters: struct ScriptedSpeechParams { const char* speechName; const char* voiceName; alignas(8) int v3; alignas(8) Hash speechParamHash; alignas(8) Entity entity; alignas(8) BOOL v6; alignas(8) int v7; alignas(8) int v8; }; static_assert(sizeof(ScriptedSpeechParams) == 0x40, "incorrect ScriptedSpeechParams size"); //declaring the line (taken out of a container) char* speechline = speechl_it->second; ScriptedSpeechParams params{ speechline, 0, 1, 0x67F3AB43, 0, true, 1, 1 }; AUDIO::x_PLAY_AMBIENT_SPEECH1(ped, ¶ms); Still threw an error. Then I used the exact same thing: struct ScriptedSpeechParams { const char* speechName; const char* voiceName; alignas(8) int v3; alignas(8) Hash speechParamHash; alignas(8) Entity entity; alignas(8) BOOL v6; alignas(8) int v7; alignas(8) int v8; }; static_assert(sizeof(ScriptedSpeechParams) == 0x40, "incorrect ScriptedSpeechParams size"); //declaring the line (taken out of a container) char* speechline = speechl_it->second; ScriptedSpeechParams params{ "RE_PH_RHD_V3_AGGRO", "0405_U_M_M_RhdSheriff_01", 1, 0x67F3AB43, 0, true, 1, 1 }; AUDIO::x_PLAY_AMBIENT_SPEECH1(ped, ¶ms); Still didnt work. Then also tried this, but didnt work either: struct ScriptedSpeechParams { const char* speechName; const char* voiceName; alignas(8) Any a; alignas(8) Any b; alignas(8) Any c; alignas(8) Any d; alignas(8) Any e; }; static_assert(sizeof(ScriptedSpeechParams) == 0x38, "incorrect ScriptedSpeechParams size"); //declaring line (taken out of a container) char* speechline = speechl_it->second; ScriptedSpeechParams params{ speechline, 0, 0, 291934926, ped, NETWORK::NETWORK_IS_GAME_IN_PROGRESS(), 1 }; AUDIO::x_PLAY_AMBIENT_SPEECH1(ped, ¶ms); I also tried it with the player Ped, but to no avail 😕 Edit: went through the decompiled scripts and found the following construct being filled this way (like you posted it): Param0 = uParam0 //ped ? struct<7> Var0; Var0.f_5 = 1; Var0.f_6 = 1; Var0 = sParam1; //speechline "RE_PH_RHD_V3_AGGRO" Var0.f_1 = sParam5; //voice "0405_U_M_M_RhdSheriff_01" Var0.f_2 = iParam6; //int 1 Var0.f_3 = iParam2; //int 1744022339 Var0.f_4 = uParam3; //ped ? Var0.f_5 = iParam4; //int 1 Var0.f_6 = iParam7; //int 1 PLAY_AMBIENT_SPEECH(Param0, &Var0); Havent used it successfully yet, though. Edit 2: using it like this and its not crashing anymore, but it doesnt happen anything either^^ (no speaking of peds): char* speechline = speechl_it->second; struct SpeechParams { char* speechline; char* voice; int i1; int i2; Ped p3; int i4; int i5; }; SpeechParams params{ "RE_PH_RHD_V3_AGGRO", "0405_U_M_M_RhdSheriff_01", 1, 291934926, ped, 1, 1 }; AUDIO::x_PLAY_AMBIENT_SPEECH1(ped, params); Edited August 16, 2020 by HughJanus Quote
LMS 676 Posted August 16, 2020 Posted August 16, 2020 Unfortunately I have no idea why it is not working for you. That's all I am doing (but from RPH). I will test you code tomorrow in my game as well and see if I can narrow it down. Quote
HughJanus 244 Posted August 17, 2020 Author Posted August 17, 2020 5 hours ago, LMS said: Unfortunately I have no idea why it is not working for you. That's all I am doing (but from RPH). I will test you code tomorrow in my game as well and see if I can narrow it down. Thank you. If nothing helps, maybe I will look at porting the mod to RPH, if everything is so easy there - but I suppose, I will have to replace all the natives I used? Quote
LMS 676 Posted August 18, 2020 Posted August 18, 2020 I changed my model to U_M_M_ValSheriff_01 and only filled the first parameter (speech name) and the fourth: GENERIC_THANKS and 0xE7176FB2 respectively. The rest I left as their default values. It played just fine for me when calling the native with player ped and the params. RPH mods do not use C++, but C# so it is definitely quite some work to port it. 1 Quote
HughJanus 244 Posted August 18, 2020 Author Posted August 18, 2020 3 hours ago, LMS said: I changed my model to U_M_M_ValSheriff_01 and only filled the first parameter (speech name) and the fourth: GENERIC_THANKS and 0xE7176FB2 respectively. The rest I left as their default values. It played just fine for me when calling the native with player ped and the params. RPH mods do not use C++, but C# so it is definitely quite some work to port it. So like this? ScriptedSpeechParams params{"GENERIC_THANKS", 0, 1, 0xE7176FB2, 0, true, 1, 1}; What is the hash for (0xE7176FB2)? Quote
LMS 676 Posted August 18, 2020 Posted August 18, 2020 9 hours ago, HughJanus said: So like this? ScriptedSpeechParams params{"GENERIC_THANKS", 0, 1, 0xE7176FB2, 0, true, 1, 1}; What is the hash for (0xE7176FB2)? All the other parameters I left untouched, i.e. their default values (0 and false). The hash is some speech modifier, try it with that one. Quote
HughJanus 244 Posted August 18, 2020 Author Posted August 18, 2020 4 hours ago, LMS said: All the other parameters I left untouched, i.e. their default values (0 and false). The hash is some speech modifier, try it with that one. Would you be so kind to post a code snippet? I have tried this (8 parameters like the code snippet you posted): struct SpeechParams { char* speechName; char* voiceName; int v3; Hash speechParamHash; Entity entity; BOOL v6; int v7; int v8; }; struct SpeechParams params { "GENERIC_THANKS", 0, 0, 0xE7176FB2, 0, 0, 0, 0 }; AUDIO::x_PLAY_AMBIENT_SPEECH1(ped, params); And this (7 parameters like you said in one of your posts): struct SpeechParams { char* speech; char* voice; int i1; Hash h2; Entity targetPed; bool networkGameInProcess; bool b4; }; struct SpeechParams params { "GENERIC_THANKS", 0, 0, 0xE7176FB2, 0, 0, 0 }; AUDIO::x_PLAY_AMBIENT_SPEECH1(ped, params); When using the one with 7 parameters, I got an error. When using the one with 8, nothing happened. If you post a code snippet, I can use that and try to do something with the struct (if thats what youre doing different) or with the parameters (if we differ there). But at the moment I dont even know how many parameters I am supposed to have. Quote
LMS 676 Posted August 18, 2020 Posted August 18, 2020 I am genuinely confused as to why it is not working for you - it all looks good to me. I am doing it in C# so the syntax will be different, but this is my code to test (normally I'd just use Game.LocalPlayer.Character.PlayAmbientSpeech obviously): [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 96)] internal unsafe struct CSpeechStruct { [FieldOffset(0)] public sbyte* SpeechName; [FieldOffset(8)] public sbyte* VoiceName; [FieldOffset(16)] public uint UnknownUInt1; [FieldOffset(24)] public uint SpeechModifier; [FieldOffset(32)] public uint UnknownUInt2; [FieldOffset(40)] public bool UnknownBool1; [FieldOffset(48)] public int UnknownBool2; [FieldOffset(56)] public uint UnknownUInt3; } [ConsoleCommand] private static unsafe void Command_PlaySpeech() { CSpeechStruct s = new CSpeechStruct(); IntPtr speechNameAddress = Marshal.StringToHGlobalAnsi("GENERIC_THANKS"); s.SpeechName = (sbyte*)speechNameAddress; s.SpeechModifier = 0xE7176FB2; Game.CallNative<uint>(0x8E04FEDD28D42462, Game.LocalPlayer.Character, (IntPtr)(&s)); Marshal.FreeHGlobal(speechNameAddress); } Quote
HughJanus 244 Posted August 19, 2020 Author Posted August 19, 2020 17 hours ago, LMS said: I am genuinely confused as to why it is not working for you - it all looks good to me. I am doing it in C# so the syntax will be different, but this is my code to test (normally I'd just use Game.LocalPlayer.Character.PlayAmbientSpeech obviously): [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 96)] internal unsafe struct CSpeechStruct { [FieldOffset(0)] public sbyte* SpeechName; [FieldOffset(8)] public sbyte* VoiceName; [FieldOffset(16)] public uint UnknownUInt1; [FieldOffset(24)] public uint SpeechModifier; [FieldOffset(32)] public uint UnknownUInt2; [FieldOffset(40)] public bool UnknownBool1; [FieldOffset(48)] public int UnknownBool2; [FieldOffset(56)] public uint UnknownUInt3; } [ConsoleCommand] private static unsafe void Command_PlaySpeech() { CSpeechStruct s = new CSpeechStruct(); IntPtr speechNameAddress = Marshal.StringToHGlobalAnsi("GENERIC_THANKS"); s.SpeechName = (sbyte*)speechNameAddress; s.UnknownUInt2 = 0xE7176FB2; Game.CallNative<uint>(0x8E04FEDD28D42462, Game.LocalPlayer.Character, (IntPtr)(&s)); Marshal.FreeHGlobal(speechNameAddress); } Are you sure you filled the Hash into the right parameter? In your snippet its the fifth, in your post before you said the fourth. If I place the Hash into the fifth, scripthook throws an error. If I put it into the fourth, nothing happens (but at least there is no error). Quote
LMS 676 Posted August 19, 2020 Posted August 19, 2020 4 minutes ago, HughJanus said: Are you sure you filled the Hash into the right parameter? In your snippet its the fifth, in your post before you said the fourth. If I place the Hash into the fifth, scripthook throws an error. If I put it into the fourth, nothing happens (but at least there is no error). Good catch, I had renamed the variables from Unknown to what I think they are for the sake of this post, but forgot to change the assignment name below. In the actual code it is using the fourth parameter, though. I have fixed the snippet to reflect the code executed. Why it still does not work for you is beyond me, though. Quote
HughJanus 244 Posted August 19, 2020 Author Posted August 19, 2020 I now tried going around the wrapper and directly invoking the native hash, but it also threw an error (with or without filling the rest of the contruct with default values). struct CSpeechStruct { public: char* SpeechName; public: char* VoiceName; public: uint UnknownUInt1; public: uint SpeechModifier; public: uint UnknownUInt2; public: bool UnknownBool1; public: int UnknownBool2; public: uint UnknownUInt3; }; CSpeechStruct s; s.SpeechName = "GENERIC_THANKS"; s.VoiceName = 0; s.UnknownUInt1 = 0; s.SpeechModifier = 0xE7176FB2; s.UnknownUInt2 = 0; s.UnknownBool1 = false; s.UnknownBool2 = 0; s.UnknownUInt3 = 0; invoke<BOOL>(0x8E04FEDD28D42462, ped, s); I give up for now. Maybe I will find the passion to further try this later some time. I suppose it will take less time porting the whole mod to RPH than trying to make this native work^^ Thanks for the help, though. Quote
LMS 676 Posted August 19, 2020 Posted August 19, 2020 1 hour ago, HughJanus said: I now tried going around the wrapper and directly invoking the native hash, but it also threw an error (with or without filling the rest of the contruct with default values). struct CSpeechStruct { public: char* SpeechName; public: char* VoiceName; public: uint UnknownUInt1; public: uint SpeechModifier; public: uint UnknownUInt2; public: bool UnknownBool1; public: int UnknownBool2; public: uint UnknownUInt3; }; CSpeechStruct s; s.SpeechName = "GENERIC_THANKS"; s.VoiceName = 0; s.UnknownUInt1 = 0; s.SpeechModifier = 0xE7176FB2; s.UnknownUInt2 = 0; s.UnknownBool1 = false; s.UnknownBool2 = 0; s.UnknownUInt3 = 0; invoke<BOOL>(0x8E04FEDD28D42462, ped, s); I give up for now. Maybe I will find the passion to further try this later some time. I suppose it will take less time porting the whole mod to RPH than trying to make this native work^^ Thanks for the help, though. You would need to pass the reference, so &s, but you tried that before too iirc, so probably also not going to work. And no problem. 1 Quote
HughJanus 244 Posted September 12, 2020 Author Posted September 12, 2020 OK, so now I tried again. I started from the ground up and searched the mission scripts. In the script named "feud1.c" I found the following methods (getting called by each other): var func_2348(var uParam0, var uParam1) { return AUDIO::_PLAY_AMBIENT_SPEECH1(uParam0, uParam1); } bool func_1859(var uParam0, char* sParam1, int iParam2, int iParam3, int iParam4, int iParam5, int iParam6, int iParam7) { struct<7> Var0; Var0.f_5 = 1; Var0.f_6 = 1; Var0 = sParam1; Var0.f_1 = iParam5; Var0.f_2 = iParam6; Var0.f_3 = iParam2; Var0.f_4 = iParam3; Var0.f_5 = iParam4; Var0.f_6 = iParam7; return func_2348(uParam0, &Var0); } func_1859(*iParam1, "PLEAD", -69597692, 0, 1, 0, 0, 1) I tried to mirror this exactly and had a few type mismatches. I think this is where the problem lies. I would have liked to do it that way: bool PedFearTestHelper(Ped ped, Any* x) { return invoke<BOOL>(0x8E04FEDD28D42462, ped, x); } bool PedFearTest(Ped ped) { struct CSpeechStruct { Any f_1, f_2, f_3, f_4, f_5, f_6; }; CSpeechStruct Var0; Var0.f_5 = 1; Var0.f_6 = 1; Var0 = "PLEAD"; Var0.f_1 = 0; Var0.f_2 = 0; Var0.f_3 = -69597692; Var0.f_4 = 0; Var0.f_5 = 1; Var0.f_6 = 1; return PedFearTestHelper(ped, &Var0); } But the errors are here (because I cannot assign a char* to CSpeechStruct): Var0 = "PLEAD"; and here (because CSpeechStruct* cannot be assigned to Any*): return PedFearTestHelper(ped, &Var0); Due to these errors I have adjusted the code as follows: struct CSpeechStruct { Any f_1, f_2, f_3, f_4, f_5, f_6; char* f_0; }; bool PedFearTestHelper(Ped ped, CSpeechStruct* x) { return invoke<BOOL>(0x8E04FEDD28D42462, ped, x); } bool PedFearTest(Ped ped) { CSpeechStruct Var0; Var0.f_5 = 1; Var0.f_6 = 1; Var0.f_0 = "PLEAD"; Var0.f_1 = 0; Var0.f_2 = 0; Var0.f_3 = -69597692; Var0.f_4 = 0; Var0.f_5 = 1; Var0.f_6 = 1; return PedFearTestHelper(ped, &Var0); } Now my IDE does not throw any errors, but script hook does as soon as it loads the asi. From all this, I assume that the main obstacle is the type mismatches. Since I dont know what the native wants as a second parameter exactly, I dont really know what to pass to it. @LMS You seem very savvy, could you make an educated guess on what C++ structure would be fitting to be passed to the native? I dont know anything in C++ that would match the var type of the mission script. var seems to be some kind of more forgiving Any type. Same with the struct<x>, I wouldnt know how to get an equivalent in C++. Quote
LMS 676 Posted September 13, 2020 Posted September 13, 2020 Good to see you are back and sad to see it is still not working.. When the decompiler puts "Var0 = sParam1;" that just means that the first field (at offset 0) is being assigned. You hence should move that to be the first field in your definition. If ScriptHook still throws errors, try casting your "CSpeechStruct* x" to UINT_PTR before passing it. Maybe that makes it accept it. 1 Quote
HughJanus 244 Posted September 14, 2020 Author Posted September 14, 2020 20 hours ago, LMS said: Good to see you are back and sad to see it is still not working.. When the decompiler puts "Var0 = sParam1;" that just means that the first field (at offset 0) is being assigned. You hence should move that to be the first field in your definition. If ScriptHook still throws errors, try casting your "CSpeechStruct* x" to UINT_PTR before passing it. Maybe that makes it accept it. I tried what you proposed and the IDE now lets me do it. Unfortunately, script hook still throws the error on activation of the script. Here is what I currently have: struct CSpeechStruct { char* f_0; Any f_1, f_2, f_3, f_4, f_5, f_6; }; bool PedFearTestHelper(Ped ped, Any x) { return invoke<BOOL>(0x8E04FEDD28D42462, ped, (UINT_PTR)x); } bool PedFearTest(Ped ped) { CSpeechStruct Var0; Var0.f_0 = "PLEAD"; Var0.f_1 = 0; Var0.f_2 = 0; Var0.f_3 = -69597692; Var0.f_4 = 0; Var0.f_5 = 1; Var0.f_6 = 1; return PedFearTestHelper(ped, (UINT_PTR)&Var0); } I am now looking into including C code into the C++ project. Maybe this way I can use the exact code of the mission script. Or is it easier to use C# code in a C++ project? (havent started researching yet) Quote
LMS 676 Posted September 14, 2020 Posted September 14, 2020 I don't think Any works for the rest of the types, as that will be too big. I would suggest using the types I provided initially, but you already tried that, so not sure. This is actually interesting me as to why it does not work, so I might try to get it to work in C++ myself this weekend. I don't think including C will do anything for you (and C# is not possible). 1 Quote
HughJanus 244 Posted September 15, 2020 Author Posted September 15, 2020 (edited) I will try again with the other types, now that I have the cast into the pointer type (maybe that was the problem all along). Edit: Tried it like this, but to no avail, unfortunately. struct CSpeechStruct { char* f_0; char* f_1; uint f_2; uint f_3; uint f_4; bool f_5; uint f_6; }; I'm thrilled to see if you can get it working! Edited September 18, 2020 by HughJanus Quote
HughJanus 244 Posted October 15, 2020 Author Posted October 15, 2020 @LMS Have you had time yet to check the ambient speech in C++? No worries if not, just checking in. Quote
LMS 676 Posted October 23, 2020 Posted October 23, 2020 On 10/15/2020 at 8:17 PM, HughJanus said: @LMS Have you had time yet to check the ambient speech in C++? No worries if not, just checking in. I haven't had the chance yet, no. I have not forgotten about it, though 🙂 1 Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.