Jump to content

Problem With Extended Speech Files


Recommended Posts

So I went and posted a thread called Extending custom speech files where I explained my game was crashing due to an unknown cause. Then @alainmcd came and told me it was because I did not have a comma closing off sections. After allying commas to their correct places, I went ahead and tested my mod. It worked. I got onto the game and it worked fine right up until I tried to whack a pig in the face. The game crashed and told me this:

COROUTINE 110395 SCRIPT CRASH:
[string "scripts/scheduler.lua"]:337: attempt to perform arithmetic on local 'time' (a nil value)
LUA ERROR stack traceback:
        scripts/scheduler.lua(337,1) in function 'Sleep'
        scripts/components/talker.lua(148,1) in function 'sayfn'
        scripts/components/talker.lua(202,1)    

I went into scheduler.lua and talker.lua to identify the problem. It appears that the scheduler thinks that the second line of speech is the time it will take to say it.

function Sleep(time)
    local desttick = math.ceil((GetTime() + time)/GetTickTime())
    if GetTick() < desttick then
        coroutine.yield(SLEEP, desttick)
    else
        coroutine.yield()
    end
end

Here is an example of one of my extended lines of speech:

    ANNOUNCE_CHARLIE = 
    {
        "I see you!",
        "Get away from me!",
        "Someone bring me light!",
    },

I am unsure how to fix this problem but I get the feeling I would have to write something to help the game understand that it gets to choose a line to say, rather than say "I see you!" for "Get away from me!" seconds. I notice that the game has random lines of speech for things like ANNOUNCE_ENCUMBERED or ANNOUNCE_ANTLION_TRIBUTE. This is one of the game's default 'multiple lined' announcements.

    ANNOUNCE_ANTLION_TRIBUTE =
    {
        "I hope you're happy.",
        "A tribute. Don't expect anything more.",
        "Hehe... I spat on it.",
    },

As you can see, there is no difference between the structure of ANNOUNCE_CHARLIE and ANNOUNCE_ANTLION_TRIBUTE so I really have no idea what to add or whatever. Again, if anyone can help, I would EXTREMELY appreciate it!

-Rowan Kula

Link to comment
Share on other sites

This one is a nasty one. The game doesn't handle properly some "topics" when they have "modifiers", so it returns a table when it should be a string.

Copy the text in the spoiler to a file named stringutilfixed.lua and place it in a folder named scripts within your mod folder.

Spoiler

local function getmodifiedstring(topic_tab, modifier)
	if type(modifier) == "table" then
		local ret = topic_tab
		for i,v in ipairs(modifier) do
			if ret == nil then
				return nil
			end
			ret = ret[v]
		end
		return ret
	else
		if modifier ~= nil and type(topic_tab[modifier]) == "table" then
			topic_tab = topic_tab[modifier]
		elseif type(topic_tab.GENERIC) == "table" then
			topic_tab = topic_tab.GENERIC
		end
		return (modifier ~= nil and topic_tab[modifier])
			or topic_tab.GENERIC
			or (#topic_tab > 0 and topic_tab[math.random(#topic_tab)] or nil)
	end
end

local function getcharacterstring(tab, item, modifier)
    if tab == nil then
        return
    end

    local topic_tab = tab[item]
    if topic_tab == nil then
        return
    elseif type(topic_tab) == "string" then
        return topic_tab
    elseif type(topic_tab) ~= "table" then
        return
    end

	if type(modifier) == "table" then
		for i,v in ipairs(modifier) do
			v = string.upper(v)
		end
	else
		modifier = modifier ~= nil and string.upper(modifier) or nil
	end

	return getmodifiedstring(topic_tab, modifier)
end

function GetGenderStrings(charactername)
    for gender,characters in pairs(CHARACTER_GENDERS) do
        if table.contains(characters, charactername) then
            return gender
        end
    end
    return "DEFAULT"
end

---------------------------------------------------------
--"Oooh" string stuff
local Oooh_endings = { "h", "oh", "ohh" }
local Oooh_punc = { ".", "?", "!" }

local function ooohstart(isstart)
    local str = isstart and "O" or "o"
    local l = math.random(2, 4)
    for i = 2, l do
        str = str..(math.random() > 0.3 and "o" or "O")
    end
    return str
end

local function ooohspace()
    local c = math.random()
    local str =
        (c <= .1 and "! ") or
        (c <= .2 and ". ") or
        (c <= .3 and "? ") or
        (c <= .4 and ", ") or
        " "
    return str, c <= .3
end

local function ooohend()
    return Oooh_endings[math.random(#Oooh_endings)]
end

local function ooohpunc()
    return Oooh_punc[math.random(#Oooh_punc)]
end

local function CraftOooh() -- Ghost speech!
    local isstart = true
    local length = math.random(6)
    local str = ""
    for i = 1, length do
        str = str..ooohstart(isstart)..ooohend()
        if i ~= length then
            local space
            space, isstart = ooohspace()
            str = str..space
        end
    end
    return str..ooohpunc()
end

--V2C: Left this here as a global util function so mods or other characters can use it easily.
function Umlautify(string)
    if not Profile:IsWathgrithrFontEnabled() then
        return string
    end

    local ret = ""
    local last = false
    for i = 1, #string do
        local c = string:sub(i,i)
        if not last and (c == "o" or c == "O") then
            ret = ret .. ((c == "o" and "ö") or (c == "O" and "Ö") or c)
            last = true
        else
            ret = ret .. c
            last = false
        end
    end
    return ret
end

---------------------------------------------------------

local wilton_sayings =
{
    "Ehhhhhhhhhhhhhh.",
    "Eeeeeeeeeeeer.",
    "Rattle.",
    "click click click click",
    "Hissss!",
    "Aaaaaaaaa.",
    "mooooooooooooaaaaan.",
    "...",
}

function GetSpecialCharacterString(character)
	if character == nil then
        return nil
    end

    character = string.lower(character)

    return (character == "mime" and "")
        or (character == "ghost" and CraftOooh())
        or (character == "wilton" and wilton_sayings[math.random(#wilton_sayings)])
        or nil
end

--V2C: Deprecated, set talker.mod_str_fn in character prefab definitions instead
--     Kept for backward compatibility with mods
function GetSpecialCharacterPostProcess(character, string)
    return string
end

-- When calling GetString, must pass actual instance of entity if it might be used when ghost
-- Otherwise, handing inst.prefab directly to the function call is okay
function GetString(inst, stringtype, modifier)
    local character =
        type(inst) == "string"
        and inst
        or (inst ~= nil and inst.prefab or nil)

    character = character ~= nil and string.upper(character) or nil
    stringtype = stringtype ~= nil and string.upper(stringtype) or nil
	if type(modifier) == "table" then
		for i,v in ipairs(modifier) do
			v = string.upper(v)
		end
	else
		modifier = modifier ~= nil and string.upper(modifier) or nil
	end

    local specialcharacter =
        type(inst) == "table"
        and ((inst:HasTag("mime") and "mime") or
        (inst:HasTag("playerghost") and "ghost"))
        or character

    return GetSpecialCharacterString(specialcharacter)
        or getcharacterstring(STRINGS.CHARACTERS[character], stringtype, modifier)
        or getcharacterstring(STRINGS.CHARACTERS.GENERIC, stringtype, modifier)
        or ("UNKNOWN STRING: "..(character or "").." "..(stringtype or "").." "..(modifier or ""))
end

function GetActionString(action, modifier)
    return getcharacterstring(STRINGS.ACTIONS, action, modifier) or "ACTION"
end

-- When calling GetDescription, must pass actual instance of entity if it might be used when ghost
-- Otherwise, handing inst.prefab directly to the function call is okay
function GetDescription(inst, item, modifier)
    local character =
        type(inst) == "string"
        and inst
        or (inst ~= nil and inst.prefab or nil)

    character = character ~= nil and string.upper(character) or nil
    local itemname = item.nameoverride or item.components.inspectable.nameoverride or item.prefab or nil
    itemname = itemname ~= nil and string.upper(itemname) or nil
	if type(modifier) == "table" then
		for i,v in ipairs(modifier) do
			v = string.upper(v)
		end
	else
		modifier = modifier ~= nil and string.upper(modifier) or nil
	end

    local specialcharacter =
        type(inst) == "table"
        and ((inst:HasTag("mime") and "mime") or
            (inst:HasTag("playerghost") and "ghost"))
        or character

    local ret = GetSpecialCharacterString(specialcharacter)
    if ret ~= nil then
        return ret
    end

    if character ~= nil and STRINGS.CHARACTERS[character] ~= nil then
        ret = getcharacterstring(STRINGS.CHARACTERS[character].DESCRIBE, itemname, modifier)
        if ret ~= nil then
            if item ~= nil and item.components.repairable ~= nil and not item.components.repairable.noannounce and item.components.repairable:NeedsRepairs() then
                return ret..(getcharacterstring(STRINGS.CHARACTERS[character], "ANNOUNCE_CANFIX", modifier) or "")
            end
            return ret
        end
    end

    ret = getcharacterstring(STRINGS.CHARACTERS.GENERIC.DESCRIBE, itemname, modifier)

    if item ~= nil and item.components.repairable ~= nil and not item.components.repairable.noannounce and item.components.repairable:NeedsRepairs() then
        if ret ~= nil then
            return ret..(getcharacterstring(STRINGS.CHARACTERS.GENERIC, "ANNOUNCE_CANFIX", modifier) or "")
        end
        ret = getcharacterstring(STRINGS.CHARACTERS.GENERIC, "ANNOUNCE_CANFIX", modifier)
        if ret ~= nil then
            return ret
        end
    end

    return ret or STRINGS.CHARACTERS.GENERIC.DESCRIBE_GENERIC
end

-- When calling GetActionFailString, must pass actual instance of entity if it might be used when ghost
-- Otherwise, handing inst.prefab directly to the function call is okay
function GetActionFailString(inst, action, reason)
    local character =
        type(inst) == "string"
        and inst
        or (inst ~= nil and inst.prefab or nil)

    local specialcharacter =
        type(inst) == "table"
        and ((inst:HasTag("playerghost") and "ghost") or
            (inst:HasTag("mime") and "mime"))
        or character

    local ret = GetSpecialCharacterString(specialcharacter)
    if ret ~= nil then
        return ret
    end

    character = string.upper(character)

    return (STRINGS.CHARACTERS[character] ~= nil and getcharacterstring(STRINGS.CHARACTERS[character].ACTIONFAIL, action, reason))
        or getcharacterstring(STRINGS.CHARACTERS.GENERIC.ACTIONFAIL, action, reason)
        or STRINGS.CHARACTERS.GENERIC.ACTIONFAIL_GENERIC
end

function FirstToUpper(str)
    return str:gsub("^%l", string.upper)
end

function TrimString( s )
   return string.match( s, "^()%s*$" ) and "" or string.match( s, "^%s*(.*%S)" )
end

-- usage:
-- subfmt("this is my {adjective} string, read it {number} times!", {adjective="cool", number="five"})
-- => "this is my cool string, read it five times"
function subfmt(s, tab)
  return (s:gsub('(%b{})', function(w) return tab[w:sub(2, -2)] or w end))
end

 

(I just copied the whole stringutil.lua file and fixed the first function, getmodifiedstring.)

Add the following line to your modmain.lua:

GLOBAL.require"stringutilfixed"

That should take care of it.

 

...But wait, there's more! I found a bug with some strings, specifically when attacking pigs.

Spoiler

Bug is fixed in DST, and the code I posted was very wrong. Here's a fix for DS, if anyone's interested.

 

Edited by alainmcd
Removed bad code.
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...