local inspect = require('inspect') -- Utility functions for working with Lua in Fungus local M = {} ------------ -- Debugging ------------ -- Prints a summary of object v in a human readable format. function M.inspect(v) print (inspect.inspect(v)) end ------------ -- Math ------------ -- rounds a number to the nearest decimal places -- e.g. round(1.523, 100) -- 1.52 function M.round(val, decimal) if (decimal) then return math.floor( (val * 10^decimal) + 0.5) / (10^decimal) else return math.floor(val+0.5) end end ------------- -- Coroutines ------------- -- Waits for a number of seconds function M.wait(duration) local t = M.time.timeSinceLevelLoad while (M.time.timeSinceLevelLoad - t < duration) do coroutine.yield() end end -- Waits until the lamda function provided returns true, or the timeout expires. -- Returns true if the function succeeded, or false if the timeout expired function M.waitfor(fn, timeoutduration) local t = M.time.timeSinceLevelLoad while (not fn()) do coroutine.yield() if (M.time.timeSinceLevelLoad - t > timeoutduration) then return false end end return true end -- Starts a C# coroutine method function M.run(enumerator) -- If the parameter isn't an enumerator then CreateTask will fail local status, err = pcall( function() local task = M.luaenvironment.RunUnityCoroutine(enumerator) end) if (not status) then print(debug.traceback("Can't start a coroutine with a c# method that doesn't return IEnumerator", 2)) error(err) end end -- Starts a C# coroutine method and waits until it's finished function M.runwait(enumerator) -- If the parameter isn't an enumerator then CreateTask will fail local status, err = pcall( function() local task = M.luaenvironment.RunUnityCoroutine(enumerator) while (task != nil and task.Running) do coroutine.yield(); end end) if (not status) then print(debug.traceback("Can't start a coroutine with a c# method that doesn't return IEnumerator", 2)) error(err) end end --------------- -- String table --------------- -- Set active language for string table function M.setlanguage(languagecode) M.luautils.ActiveLanguage = languagecode end -- Get a named string from the string table function M.getstring(key) return M.luautils.GetString(key) end -- Substitutes variables and localisation strings into a piece of text -- e.g. v = 10, "Subbed value is [$v]" => "Subbed value is 10" function M.sub(text) return M.luautils.substitute(text) end -------------------------------------------------------------- -- Integration tests -- These functions require the Unity Test Tools to work. -------------------------------------------------------------- -- Checks if a condition is true (requires Unity Test Tools) -- Lua has a built in assert function, so we called this check to avoid conflicting. function M.check(c, reason) if (not c) then -- Output a traceback to help track down source error( debug.traceback("Assert failed", 2) ) end M.test.assert(c, reason) end -- Pass an integration test (requires Unity Test Tools) function M.pass() M.test.pass() end -- Fail an integration test (requires Unity Test Tools) -- reason: Option string explaining why the test failed. function M.fail(reason) error( debug.traceback("Test failed", 2) ) M.test.fail(reason) end --------------------------------------------- -- All functions below require Fungus to work --------------------------------------------- ------------- -- Say Dialog ------------- -- Options for configuring Say Dialog behaviour M.sayoptions = {} M.sayoptions.saydialog = nil M.sayoptions.clearprevious = true M.sayoptions.waitforinput = true M.sayoptions.fadewhendone = true M.sayoptions.stopvoiceover = true -- Set the active saydialog to use with the say and conversation functions function M.setsaydialog(saydialog) M.sayoptions.saydialog = saydialog luautils.SetSayDialog(saydialog) end -- Gets the active say dialog, or creates one if none exists yet function M.getsaydialog() if (M.sayoptions.saydialog == nil) then local sd = M.luautils.spawn("Prefabs/SayDialog").GetComponent("SayDialog") M.setsaydialog(sd) end return M.sayoptions.saydialog end -- Set the active character on the Say Dialog -- character: A Fungus.Character component -- portrait: The name of a sprite in the character's portrait list function M.setcharacter(character, portrait) assert(character, "character must not be nil") local sd = M.getsaydialog() sd.SetCharacter(character) -- Do substitution on character name local subbed = M.sub(character.nameText) M.sayoptions.saydialog.SetCharacterName(subbed, character.nameColor) -- Try to set the portrait sprite if (portrait) then if (portrait == "") then M.sayoptions.saydialog.SetCharacterImage(nill) else for i,v in ipairs(character.portraits) do -- Use a case insensitive comparison if (string.lower(v.name) == string.lower(portrait)) then M.sayoptions.saydialog.SetCharacterImage(v) end end end end end -- Write text to the active Say Dialog -- text: A string to write to the say dialog -- voice: A voiceover audioclip to play function M.say(text, voiceclip) local sd = M.getsaydialog() -- Do variable substitution before displaying text local subbed = M.sub(text) local e = sd.DoSay(subbed, M.sayoptions.clearprevious, M.sayoptions.waitforinput, M.sayoptions.fadewhendone, M.sayoptions.stopvoiceover, voiceclip) M.runwait(e) end -- Say a series of lines, setting speakign character, portrait or stage position on each line. -- All parameters may be ommitted. The first three parameters can appear in any order. -- The story text starts with a ':' and runs to the next newline character. -- conv: A string of conversational lines using the following format. -- [character] [portrait] [position] <: Story text> -- e.g. john happy left: Hi, I'm happy. function M.conversation(text) local subbed = M.sub(text) local e = luautils.DoConversation(subbed) M.runwait(e) end -------------- -- Menu Dialog -------------- -- Options for configuring Menu Dialog behaviour M.menuoptions = {} -- Set the active menudialog to use with the menu function function M.setmenudialog(menudialog) M.menuoptions.menudialog = menudialog end -- Gets the active menu dialog, or creates one if none exists yet function M.getmenudialog() if (M.menuoptions.menudialog == nil) then local md = M.luautils.spawn("Prefabs/MenuDialog").GetComponent("MenuDialog") M.setmenudialog(md) end return M.menuoptions.menudialog end -- Display a menu button -- text: text to display on the button -- callback: function to call when this option is selected -- interactive (optional): if false, displays the option as disabled function M.menu(text, callback, interactive) local md = M.getmenudialog() -- Do variable substitution before displaying text local subbed = M.sub(text) -- Default bool parameters are tricksy if (interactive == nil) then interactive = true end md.AddOption(subbed, interactive, M.luaenvironment, callback) end -- Display a list of menu options and wait for user to choose one. -- When an option starts with the ~ character it will be displayed but not be selectable. -- Returns the index of the selected option. -- Returns 0 if options list is empty. Note: Lua array indices start at 1, not 0). -- options: an array of option strings. e.g. { "Option 1", "Option 2" } function M.choose(options) return M.choosetimer(options, 0, 0) end -- Display a list of menu options and wait for user to choose one, or for a timer to expire. -- When an option starts with the ~ character it will be displayed but not be selectable. -- Returns the index of the selected option, or the defaultoption if the timer expires. -- Returns 0 if options list is empty. Note: Lua array indices start at 1, not 0). -- options: an array of option strings. e.g. { "Option 1", "Option 2" } -- duration: Time player has to pick an option. -- defaultoption: Option index to return if the timer expires. function M.choosetimer(options, duration, defaultoption) if (options == nil or #options == 0) then return 0 end local md = M.getmenudialog() local selection = 0 for i, text in ipairs(options) do local callback = function () selection = i; end -- Do variable substitution before displaying text local subbed = M.sub(text) -- Check if option has been flagged as not interactive local interactive = true if string.sub(subbed, 1, 1) == "~" then interactive = false subbed = string.sub(subbed, 2) end md.AddOption(subbed, interactive, M.luaenvironment, callback) end if (duration > 0) then local callback = function () selection = defaultoption end local e = md.ShowTimer(duration, M.luaenvironment, callback) M.run(e) end -- Wait until one of the callbacks is called by the user selecting an option while (selection == 0) do coroutine.yield() end return selection end -- Display a timer during which the player has to choose an option. -- duration: The length of time to display the timer. -- callback: Function to call if the timer expires before an option is selected. function M.menutimer(duration, callback) local md = M.getmenudialog() local e = md.ShowTimer(duration, M.luaenvironment, callback) M.runwait(e) end -- Clear all currently displayed menu options function M.clearmenu() assert(M.menuoptions.menudialog, "menudialog must not be nil") M.menuoptions.menudialog.Clear() end ------------ -- Flowchart ------------ -- Returns the specified Variable in a Flowchart. -- To access the value of the variable, use its .value property. e.g. -- v = getvar(flowchart, "FloatVar") -- v.value = 10 -- Sets the value of the variable to 10 -- f = v.value -- f now contains 10 -- flowchart: The Fungus Flowchart containing the Block to run. -- varname: The name of the Variable to get. function M.getvar(flowchart, varname) assert(flowchart, "flowchart must not be nil") return flowchart.GetVariable(varname) end -- Runs the specified Block in a Flowchart -- flowchart: The Fungus Flowchart containing the Block to run. -- blockname: The name of the Block to run. -- commandindex: Index of the command to start execution at -- nowait: If false, will yield until the Block finishes execution. If true will continue immediately. function M.runblock(flowchart, blockname, commandindex, nowait) assert(flowchart, "flowchart must not be nil") assert(blockname, "blockname must not be nil") local block = flowchart.FindBlock(blockname) if (not block) then error("Block " .. blockname .. " not found") return end local e = block.Execute(commandindex or 0); if (nowait) then M.run( e ) else M.runwait( e ) end end return M