Jump to content

[SOLVED] Swapped speech files not applying to player examinations


Recommended Posts

Hey hey.

I have a character with several forms who swaps between different speech files when he transforms (by equpping a certain item). Originally I used a different function to replace his forms' describe strings with an assortment of generic lines, but today I removed it and started manually changing things so that they could examine certain prefabs. This is a simplified recreation of the function that handles his swaps (I believe I got it from the forums originally and did not write it myself.)

Spoiler

local function SwapSpeech(inst)
    if type(inst) == "table" and inst.prefab == "esctemplate" then    
        if inst:HasTag("hatison") then
        return "esctemplateswap"
        end
    end
    return inst
end

local _GetString = GLOBAL.GetString
function GLOBAL.GetString(inst, stringtype, modifier)
    return _GetString(SwapSpeech(inst), stringtype, modifier)
end

local _GetDescription = GLOBAL.GetDescription
function GLOBAL.GetDescription(inst, item, modifier)
    return _GetDescription(SwapSpeech(inst), item, modifier)
end

local _GetActionFailString = GLOBAL.GetActionFailString
function GLOBAL.GetActionFailString(inst, action, reason)
    return _GetActionFailString(SwapSpeech(inst), action, reason)
end

In doing this I noticed that his strings aren't being swapped properly when he examines another player. Examining other things uses speech file 2, but examining players uses speech file 1.

This kind of sucks because the entire reason I started doing the lines manually is specifically so that my character could still examine other players, but in my limited testing it's the only thing that doesn't work. It always defaults to the string of his default file. :|

Anyone have any idea how to potentially fix it? I've included a slimmed down recreation of my mod using the Extended Character Template. Equipping the "hat" the character spawns with changes the dialogue. Thank you for reading.

swapface speech.zip

Edited by Chesed
Link to comment
Share on other sites

2 hours ago, Chesed said:

Hey hey.

I have a character with several forms who swaps between different speech files when he transforms (by equpping a certain item). Originally I used a different function to replace his forms' describe strings with an assortment of generic lines, but today I removed it and started manually changing things so that they could examine certain prefabs. This is a simplified recreation of the function that handles his swaps (I believe I got it from the forums originally and did not write it myself.)

  Reveal hidden contents

local function SwapSpeech(inst)
    if type(inst) == "table" and inst.prefab == "esctemplate" then    
        if inst:HasTag("hatison") then
        return "esctemplateswap"
        end
    end
    return inst
end

local _GetString = GLOBAL.GetString
function GLOBAL.GetString(inst, stringtype, modifier)
    return _GetString(SwapSpeech(inst), stringtype, modifier)
end

local _GetDescription = GLOBAL.GetDescription
function GLOBAL.GetDescription(inst, item, modifier)
    return _GetDescription(SwapSpeech(inst), item, modifier)
end

local _GetActionFailString = GLOBAL.GetActionFailString
function GLOBAL.GetActionFailString(inst, action, reason)
    return _GetActionFailString(SwapSpeech(inst), action, reason)
end

In doing this I noticed that his strings aren't being swapped properly when he examines another player. Examining other things uses speech file 2, but examining players uses speech file 1.

This kind of sucks because the entire reason I started doing the lines manually is specifically so that my character could still examine other players, but in my limited testing it's the only thing that doesn't work. It always defaults to the string of his default file. :|

Anyone have any idea how to potentially fix it? I've included a slimmed down recreation of my mod using the Extended Character Template. Equipping the "hat" the character spawns with changes the dialogue. Thank you for reading.

swapface speech.zip 2.83 MB · 0 downloads

local function TryCharStrings(inst, charstrings, modifier)
    return charstrings ~= nil and (
            TryDescribe(charstrings.DESCRIBE[string.upper(inst.prefab)], modifier) or
            TryDescribe(charstrings.DESCRIBE.PLAYER, modifier)
        ) or nil
end

local function GetDescription(inst, viewer)
    local modifier = inst.components.inspectable:GetStatus(viewer) or "GENERIC"
    return string.format(
            TryCharStrings(inst, STRINGS.CHARACTERS[string.upper(viewer.prefab)], modifier) or
            TryCharStrings(inst, STRINGS.CHARACTERS.GENERIC, modifier),
            inst:GetDisplayName()
        )
end

Looking at player_common, it's hardcoded to use the players prefab when indexing the characters strings, so you'll want to wrap these functions and make sure it returns the proper result.

There could possibly be other instances like this but it's just players examinations from what I see.

  • Thanks 1
Link to comment
Share on other sites

16 hours ago, Hornete said:

Looking at player_common, it's hardcoded to use the players prefab when indexing the characters strings, so you'll want to wrap these functions and make sure it returns the proper result.

There could possibly be other instances like this but it's just players examinations from what I see.

Typical! Thank you for digging into the cause for me, I would have never been able to find this.

I have never actually wrapped a function for my mod before and have no idea how to do it. I'll poke around other threads on the forums and see if I can figure something out myself in the meantime, but if someone reading this knows how and is willing to help me out, that would be great.

Worst case scenario I give up after a month and throw 10 bucks at a coder friend to do it for me.

Link to comment
Share on other sites

38 minutes ago, Chesed said:

I have never actually wrapped a function for my mod before and have no idea how to do it.

You do, and just did! (Probably just didn't realize the name/terminology, which is OK, For a lot of things I don't recognize them by their name/term but I do know how to do it and the process.)

20 hours ago, Chesed said:

local function SwapSpeech(inst)
    if type(inst) == "table" and inst.prefab == "esctemplate" then    
        if inst:HasTag("hatison") then
        return "esctemplateswap"
        end
    end
    return inst
end

local _GetString = GLOBAL.GetString
function GLOBAL.GetString(inst, stringtype, modifier)
    return _GetString(SwapSpeech(inst), stringtype, modifier)
end

local _GetDescription = GLOBAL.GetDescription
function GLOBAL.GetDescription(inst, item, modifier)
    return _GetDescription(SwapSpeech(inst), item, modifier)
end

local _GetActionFailString = GLOBAL.GetActionFailString
function GLOBAL.GetActionFailString(inst, action, reason)
    return _GetActionFailString(SwapSpeech(inst), action, reason)
end

You're 'wrapping' these functions here by saving the original function, overriding it and running the old function while making your own adjustments.

You'll just want to do that same process for this player characters examination function:

inst.components.inspectable.getspecialdescription

(AddPlayerPostInit will be useful this case of hooking into every character)
Happy modding!

Link to comment
Share on other sites

On 1/25/2024 at 6:14 PM, Hornete said:

You do, and just did! (Probably just didn't realize the name/terminology, which is OK, For a lot of things I don't recognize them by their name/term but I do know how to do it and the process.)

AH, I see. I took those function from another thread on the forums... I actually had no idea exactly how they worked until now.

I am godawful at understanding programming but I talked it through with a friend and between that and your explanation I think understand what I have to do. Unfortunately I spent my entire evening yesterday trying to figure out how exactly to write the functions to do it and got nowhere. For a myriad of different reasons the game just crashes as soon as my character examines something no matter what I try changing, I'm guessing because I'm just altering the wrong variables when trying to wrap the function. I eventually started getting crashes linked to stringsutil and just gave up for now.

The most I can figure out is that I probably have to somehow check for the hat's tag and then do this?

Quote

return string.format(
            TryCharStrings(inst, STRINGS.CHARACTERS["ESCTEMPLATESWAP"], modifier)
            )

But I have no idea where to put it or how to write it.

Thank you for taking the time to try and explain it to me, and I'm really sorry I'm just not getting it. Unfortunately my understanding of programming is just too low and I didn't anticipate Klei hardcoding things to be the issue.

If nobody gets bored enough to try and write it, I will try again when I'm not so wiped. I really didn't think tweaking the strings would take three days off of my life.

Link to comment
Share on other sites

6 hours ago, Chesed said:

The most I can figure out is that I probably have to somehow check for the hat's tag and then do this?

Yea! That's very good.

Since you'll want to do this for every player character, you'll want to use the helpful `AddPlayerPostInit` utility function

local function TryDescribe(descstrings, modifier)
    return descstrings ~= nil and (
            type(descstrings) == "string" and
            descstrings or
            descstrings[modifier] or
            descstrings.GENERIC
        ) or nil
end

local function TryCharStrings(inst, charstrings, modifier)
    return charstrings ~= nil and (
            TryDescribe(charstrings.DESCRIBE[string.upper(inst.prefab)], modifier) or
            TryDescribe(charstrings.DESCRIBE.PLAYER, modifier)
        ) or nil
end

AddPlayerPostInit(function(inst)
	if not TheWorld.ismastersim then
		return
	end
	--Server only past this point
	local _getspecialdescription = inst.components.inspectable.getspecialdescription
	inst.components.inspectable.getspecialdescription = function(inst, viewer, ...)
		if viewer:HasTag("hatison") then --The 'viewer'(person examining) is wearing the hat
			return string.format(
            	TryCharStrings(inst, STRINGS.CHARACTERS["ESCTEMPLATESWAP"], modifier),
				inst:GetDisplayName()
            )
		end
		--
		return _getspecialdescription(inst, viewer, ...)
	end
end)

Something like this should work nicely.

About your stringutil crashes, I'd suspect it was because you were missing the 2nd parameter to pass into string.format judging by your sample code, which is the name of the player (when gets substituted into the '%s' characters you see in the speech file strings)

  • Thanks 1
Link to comment
Share on other sites

On 1/27/2024 at 3:30 AM, Hornete said:

[SNIP]

Something like this should work nicely.

Thank you very much! Sorry for the late reply, I've been sick the past few days.

I had to change "TheWorld" to "GLOBAL.TheWorld" but other than that, it almost works perfectly! Unfortunately there's one issue.

The swapped character can't tell what modifier the examined player has, for some reason?

image.png.f39b88771c1506c0622b12a4e3b9648d.png

Even still, this is still a gigantic improvement over what I had to deal with before, so thank you!

Link to comment
Share on other sites

21 hours ago, Chesed said:

Thank you very much! Sorry for the late reply, I've been sick the past few days.

I had to change "TheWorld" to "GLOBAL.TheWorld" but other than that, it almost works perfectly! Unfortunately there's one issue.

The swapped character can't tell what modifier the examined player has, for some reason?

image.png.f39b88771c1506c0622b12a4e3b9648d.png

Even still, this is still a gigantic improvement over what I had to deal with before, so thank you!

Ah right! We forgot to set the 'modifier' variable properly.

local modifier = inst.components.inspectable:GetStatus(viewer) or "GENERIC"

Sneak this in right at the beginning of the `getspecialdescription` override.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

On 1/30/2024 at 3:03 PM, Hornete said:

Ah right! We forgot to set the 'modifier' variable properly.

local modifier = inst.components.inspectable:GetStatus(viewer) or "GENERIC"

Sneak this in right at the beginning of the `getspecialdescription` override.

Everything works as I want now! Thank you very much for your help and patience!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
  • Create New...