Jump to content

Making NPCs speak..? [SOLVED]


HughJanus
 Share

Recommended Posts

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 by HughJanus
Link to comment
Share on other sites

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".

 

  • Like 1
Link to comment
Share on other sites

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 by HughJanus
Link to comment
Share on other sites

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.

  • Like 1
Link to comment
Share on other sites

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 by HughJanus
Link to comment
Share on other sites

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*)&params);

 

Link to comment
Share on other sites

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, &params);

 

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, &params);

 

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, &params);

 

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, &params);

 

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 by HughJanus
Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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?

Link to comment
Share on other sites

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.

  • Like 1
Link to comment
Share on other sites

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)?

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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

 

Link to comment
Share on other sites

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).

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

 

Link to comment
Share on other sites

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.

  • Like 1
Link to comment
Share on other sites

  • 4 weeks later...

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++.

Link to comment
Share on other sites

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.

  • Like 1
Link to comment
Share on other sites

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)

Link to comment
Share on other sites

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).

  • Like 1
Link to comment
Share on other sites

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 by HughJanus
Link to comment
Share on other sites

  • 1 month later...
  • 2 weeks later...
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 🙂

  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...