[CUSTOM] dialog system (working/tutorial pending)

  • #1, z sebastianSaturday, 24. September 2016, 18:07 hodinky 8 years ago
    Hello there,

    the past few days i was working on a custom made dialog system because the normal one wasn't satisfying enough for me. Thats because of some main points:
    1) the dialog parts itself are bound to a single condition: When wanting more than one condition influence the dispalying of one dialog part several conditions have to be connected up and result in a single condition which can be checked
    2) the dialog tree is static. I have no chance to display specific parts from different sub-dialogs of the same tree
    3) the dialog "box" contains all selectable items. No chance to display the texts on different positions

    All in all the normal dialog system is great and easy to use, but has some flaws which i liked to solve.

    I made up a concept for this which replicates the normal dialog system but adds some more features like:
    1) customizable gui. The whole dialog is an interface with displayed object texts for each line. It is possible to arrange the text positions as you like.
    2) interface could be animated. Lets slide the interface in or do some other crazy stuff when showing the dialog or selecting a line
    3) match multiple conditions and/or variables for a single dialog part (just yet i only check one condition, but thats just a matter of time to replace it with a checking function and return true or false for all given conditions.
    4) still multi language support out of the "script"
    5) dialog parts are included via a Script from the Scripts section of Visionaire (no additional files needed). By that all your dialog parts which get displayed can be edited in a much easier way (or in an external program) ->mass edit stuff
    6) mixed level dialogs (not bound to a static dialog tree. Just connect the parts how you like it).
    7) its still possible to scroll the displayed dialog parts. (because i didn't mention it )

    (One contra point (right now) is that i cant display valueInt and valueStrings inside of the selectable displayed dialog parts)

    To visualize what i did i made the following flow chart:
    https://cl.ly/1j1k1M473y0z/dialog_system.png

    Now what happens here?
    if i start the start_dialog function i attach some atributes to it and look up the dialog_options which were given and if they exist inside the dialog_file (a Script inside the Scripts Part of Visionaire)

    For each dialog_option i found i grab its content and save it to a table (see violet box on the left). For dialog part "dialog_part_1" i have this table generated:

    text_de: 'Dialog text 1 und so'
    text_en: 'Dialog text 1 and stuff'
    action: 'text_1_action'
    conditions: 'test_condition'
    pointer: 'dialog_part_1|dialog_ende'
    


    I now check its condition "test_condition" and if its true. If yes i add the dialog part to a table and add either the english or german text to table field [text], the action to [action] and the pointers to [pointers]

    We need the pointers to run a following dialog when the action of the selected dialog part ends.
    So we have this at the end:

    dialog_parts = {
    [1] = {
                    ["text"] = "Dialog text 1 und so",
                    ["action"] = "text_1_action",
                    ["pointer"] = "dialog_part_1|dialog_ende"
            }
    [2] = some more parts like this
    [3] = ...
    }
    


    Only the table i need to display the text and what i need to execute the action is inside the final table. It is possible to
    edit the script to pass an image file string instead of a text by that imitating the sam & max hit the road dialog system.

    After the table of valid dialog parts is generated i show the interface which runs at left click per "execute a script" action part the action which was passed from the table.

    in each action which would probably run from a left click, the last action part starts a new dialog with the passed "pointers". if no pointers were passed the dialog ends.


    So why am i writing this?

    Because I still need some help to polish some things up. I know not everybody is able to script in Lua but even you can give me some input to expand the script and add features from your ideas.
    Also i need help for the following scripting stuff to complete this concept. Maybe with some help from the community

    1) check condition(s): EDIT: DONE!
    What i have is a string of conditions separated by "|". What i need is a function which goes through each of these conditions and check if they are true (or false if a "!" is in front of it). Also it would be cool to check also for valueInt (and ValueString). If all condition/values are true, return true, else return false.
    The given String could look like this:
    "condition1|condition2|!condition3|v_valuename=10|vs_valuename=stanley"
    

    The vs_part maybe is a bit too much, but stil nice to have^^

    2) replace and to its string or int. Because the text from the table gets already displayed via a show object text action part, the tags inside this string don't get replaced again. So i need to replace the tags inside the text before displaying them...

    3) currently the object polygon of each line is static. It would be cool to add points to reorder the object polygon and change it regarding to the displayed text (so the dialogscript need a further "polygon"-field with coordinates).

    4) any suggestions?

    kind regards
    Sebastian

    Kapitán

    2346 Posts


  • #2, z afrlmeSaturday, 24. September 2016, 18:26 hodinky 8 years ago
    Damn, that was a tiring read mate!

    Can you not swap the condition string to a table & separate them by commas, like so? If it was a table then you could split each one into unique sub-table which can contain name of condition or value, type (condition, integer or string) & the desired value (optional).

    All in all this sounds really complex. I not sure the average user will be able to wrap their head around this. I'm having a hard time with it too as there's just so much going on. Nice work all the same. wink

    P.S: you should really try to think in tables from the get go rather than bothering with strings & other things before hand. For me, the simplest solution would be to completely ignore the dialog system altogether & create all my dialogs manually by hand as tables (first inside of Sublime Text, then import them as definition scripts inside of VS). Maybe split the tables into multiple scripts, one per scene / chapter or maybe one per language - not really sure which would be the better option.

    Imperátor

    7285 Posts

  • #3, z sebastianSaturday, 24. September 2016, 19:01 hodinky 8 years ago
    The tables get generated by the strings with the gmatch function. For the later user who wants to use this system the only need to write some "easy" code like
    [dialog_part_1]
    	text_de: 'Dialog text 1 und so'
    	text_en: 'Dialog text 1 and stuff'
    	action: 'text_1_action'
    	conditions: 'test_condition'
    	pointer: 'dialog_part_1|dialog_ende'
    [end]
    
    [dialog_part_2]
    	text_de: 'Dies ist auch ein Text der angezeigt wird'
    	text_en: 'This is also a text which gets displayed'
    	action: 'text_2_action'
    	conditions: ''
    	pointer: 'dialog_ende'
    [end]
    
    [dialog_part_ende]
    	text_de: 'Möp Möp'
    	text_en: 'Meep Meep'
    	action: 'text_3_action'
    	conditions: ''
    	pointer: 'dialog_part_2'
    [end]
    

    instead of table stuff in Lua.
    The aim here is to have the user writing "easy" dialog "scripts" instead of thinking in Lua. Of course they have to include the script and setup their interface. But the complex stuff is handled by the dialog system.

    And the script itself is not that complex at all. All i do is getting the content of a VS Script and search it for the dialog part which was passed by the function call.
    Then go further and check the dialog parts if they are valid and add them to a final table ...

    the dialog system script itself is only 50 lines long (with some function calls to clearing functions)


    Kapitán

    2346 Posts

  • #4, z afrlmeSaturday, 24. September 2016, 19:39 hodinky 8 years ago
    50 lines doesn't sound too bad. I expected you to tell it was something mad like 2k+++ lines or something. grin

    The examples you are writing there look more or less like Lua tables already to me.

    td = {} -- initial table
    
    td[1] =
    {
       English =  "Dialog text 1 und so"
       German = "Dialog text 1 and stuff"
       a = "text_1_action"
       c =  "test_condition"
       p = {"dialog_part_1", "dialog_ende"}
    }
    
    td[2] =
    {
       English =  "Dies ist auch ein Text der angezeigt wird"
       German = "This is also a text which gets displayed"
       a = "text_2_action"
       c =  nil
       p = {"dialog_ende"}
    }
    

    Imperátor

    7285 Posts

  • #5, z sebastianSunday, 25. September 2016, 13:29 hodinky 8 years ago
    That would do also, but i was not sure if these table is permanently loaded with the whole content while trying to get only one part of it.
    For me and my later usage of my approach: easy usage without using too much { and } and stuff. Also i guess it's more user friendly ^_^

    I managed to do the condition check function i told about in the first post.
    Now anyone is able to make this:
    if Conditions["cond1"].ConditionValue and Conditions["cond2"].ConditionValue and not Conditions["cond3"].ConditionValue == false and not Values["value1"].Int == 30 and Values["value1"].String == "Meep" then...
    

    into this
    if check("c_cond1|c_cond2|!c_cond3|!v_value1=30|vs_value1=meep") then
    


    (only thing it cant do is checking with "or" right now)

    checking:
    condition: c_"conditionname"
    value int: v_"valuename"="int"
    value string: vs_"valuename"="string"
    preattach an "!" to check "IF NOT"


    Here's dat code
    function check(conditionstring)
    	print("Passed String: "..conditionstring)
    	if conditionstring == " " then return true end
    	local value = {}
    	for x in string.gmatch(conditionstring, '([^|]+)') do						--for each given condition
    		print("checking for "..x)	
    		if string.starts(x,"!c_") then									--condition is "not true"				
    			x = string.sub(x, 4)
    			if not Conditions[x].ConditionValue then print("is not true: TRUE") else print("is not true: FALSE"); return false end
    		elseif string.starts(x,"c_") then									--condition is "true"
    			x = string.sub(x, 3)
    			if Conditions[x].ConditionValue then print("is true: TRUE") else print("is true: FALSE"); return false end
    		elseif string.starts(x,"!v_") then								--value Int is "not X"
    			x = string.sub(x, 4)
    			value[1] = nil;value[2] = nil
    			for w in string.gmatch(x,'([^=]+)') do table.insert(value, w); print(w) end
    			if Values[value[1]].Int == tonumber(value[2]) then print("is not value: FALSE"); return false else print("is not value: TRUE")  end
    		elseif string.starts(x,"v_") then									--value Int is "X"
    			x = string.sub(x, 3)
    			value[1] = nil;value[2] = nil
    			for w in string.gmatch(x,'([^=]+)') do table.insert(value, w); print(w) end
    			if Values[value[1]].Int == tonumber(value[2]) then print("is value: TRUE") else print("is value: FALSE"); return false end
    		elseif string.starts(x,"!vs_") then								--value String is "not XXX"
    			x = string.sub(x, 5)
    			value[1] = nil;value[2] = nil
    			for w in string.gmatch(x,'([^=]+)') do table.insert(value, w); print(w) end
    			if Values[value[1]].String == value[2] then print("is not word: FALSE"); return false else print("is not word: TRUE") end
    		elseif string.starts(x,"vs_") then								--value String is "XXX"
    			x = string.sub(x, 4)
    			value[1] = nil;value[2] = nil
    			for w in string.gmatch(x,'([^=]+)') do table.insert(value, w); print(w) end
    			if Values[value[1]].String == value[2] then print("is word: TRUE") else print("is word: FALSE"); return false end
    		end
    	end
    	return true
    end
    

    Kapitán

    2346 Posts

  • #6, z afrlmeSunday, 25. September 2016, 13:42 hodinky 8 years ago
    Nice one mate. You've certainly got a knack for wrapping your head around complex stuff. wink

    In regards to tables. If leave them as global rather adding the local in front of them, then you can simply clear / overwrite them as needed.

    In a project I've been doing a bit of freelance for recently, what we did is create a script for adding all the English dialog, then duplicated that script (one per language), then those scripts are set as execution rather than definition type, so if the game language is changed they simply have to load in the relevant script, which will overwrite all the currently stored tables with the new language version of them. It's no where near as complex as what you are working on as it's just for iterating through conversations rather than them having to manually create 100's of action parts to display the relevant text, character avatar, additional emotion based animation (lightbulb, tear, etc).

    P.S: if you are going to be using local for everything you might want to consider setting your tables as ["_temporary_"] tables so the data doesn't get saved into save files. Each time you run that function you are going to be generating a new table, so either set as temporary or use a global table.
    local t = {}
    t["_temporary_"] = ""
    
    t[1] = "hello world"
    t[2] = "goodbye world"
    

    Imperátor

    7285 Posts

  • #7, z sebastianSunday, 25. September 2016, 16:07 hodinky 8 years ago
    aren't these local tables get only handled inside the function they appear in. so after the function ends, the table itself is also gone?

    By the way... i saw that i also need to include < and > for value testing razz

    Kapitán

    2346 Posts

  • #8, z afrlmeSunday, 25. September 2016, 16:38 hodinky 8 years ago
    I'm pretty sure I read that each time you call local it generates a new table which will get stored in save files or something, I can't remember the exact reason other than it's not good to use local tables; espeially in something that has the potential to generate tons of them, for example inside of a mainLoop function or in another function that will be getting used quite often.

    You should really consider where to use local and when to leave it as global. If the function you are including it in will only be called 1 instance at a time rather than potentially multiple instances, then it might be worth considering defining the variable outside of the function...
    local var
    
    function test(txt)
     var = txt
     print(txt) 
    end
    

    Because we declared var as local inside of the same script, simply typing var inside of the function will automatically use the variable we created above the function. wink

    As for the temporary thing I mentioned, what that would do is purge all tables that you assigned as temporary tables when the current session ends, the data should also be excluded from save files. Think about it in terms of a cache, each time you generate a new table the data is being held during the session in case something needs to access it later on, thus it's using up resources, hence why I tend to declare my local tables outside of functions / loops or use global or temporary tables.

    Imperátor

    7285 Posts

  • #9, z sebastianSunday, 25. September 2016, 18:09 hodinky 8 years ago
    I learned that local variables get deallocated when they loose their scope (when the function ends). Don't know if this is with Lua ( please tell me smile ), too but I coded like this because of that in mind...

    Also i learned to make use of local variables whenever possible (and when using Lua declare them when needed to get the scope small). Local variables help to avoid cluttering the global environment with lots of unnecessary stuff.

    Edited the condition check function because of a little issue when stacking conditions.





    Kapitán

    2346 Posts

  • #10, z afrlmeSunday, 25. September 2016, 18:33 hodinky 8 years ago
    I've no idea mate. My memory is very fuzzy on what the temporary tables are for. I just vaguely recall it's something to do with not storing the data in save files. I know nothing about local variables in containers being purged when a function ends. Local variables do read faster though apparently, but it's not really that significant of a time difference over using a global variable or a local variable outside of a container - as long as it's in the same script.

    Imperátor

    7285 Posts

  • #11, z sebastianSunday, 25. September 2016, 23:42 hodinky 8 years ago
    Maybe the code genies can bring us light here...

    I SUMMON @SIMONS and @BIGSTANS

    wulululululululululu~

    Kapitán

    2346 Posts