From 13de73ecde4e9560b00efff463af4c2ce5ef2de8 Mon Sep 17 00:00:00 2001 From: Sokomine Date: Sat, 2 Mar 2024 19:54:21 +0100 Subject: [PATCH] export to ink: support for variables (to a degree) and precondtions (to a degree) --- export_to_ink.lua | 153 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 149 insertions(+), 4 deletions(-) diff --git a/export_to_ink.lua b/export_to_ink.lua index 0c0debc..282c1c5 100644 --- a/export_to_ink.lua +++ b/export_to_ink.lua @@ -1,7 +1,6 @@ -- helper functions for export_to_ink_language: -- TODO: include effect setting variables to values (problem: diffrent characters allowed) --- TODO: include preconditions/conditions regarding setting variables -- this table will hold the functions for exporting to ink so that we don't fill that namespace too much @@ -58,18 +57,35 @@ end -- displayed instead (in yl_speak_up) and before (in ink) shwoing the target dialog text; -- also, the divert_to target dialog may need to be rewritten yl_speak_up.export_to_ink.print_choice = function(lines, choice_text, n_id, start_dialog, - alternate_text, divert_to, only_once, label) - -- don't repeat the text of the choice in the output when running ink + alternate_text, divert_to, only_once, label, + precondition_list, effect_list) + -- usually, options/answers/choices can be selected multiple times; + -- we support the default ink way of "*" as well (but only until the player stops talking, + -- not persistently stored) if(not(only_once)) then table.insert(lines, "\n+ ") else table.insert(lines, "\n* ") end + -- helps to regcognize what has been changed how when importing again if(label and label ~= "") then table.insert(lines, "(") table.insert(lines, tostring(label)) table.insert(lines, ") ") end + -- are there any preconditions which can be handled by ink? most can not as they can + -- only be determined ingame (i.e. state of a block); even the value of variables may + -- have been changed externally + if(precondition_list and #precondition_list > 0) then + for _, p_text in ipairs(precondition_list) do + if(p_text ~= "") then + table.insert(lines, "{ ") + table.insert(lines, p_text) + table.insert(lines, " } ") + end + end + end + -- don't repeat the text of the choice in the output when running ink table.insert(lines, "[") table.insert(lines, choice_text) table.insert(lines, "]") @@ -85,6 +101,16 @@ yl_speak_up.export_to_ink.print_choice = function(lines, choice_text, n_id, star -- write the divert into a new line as well table.insert(lines, "\n ") end + -- setting a variable to a value is something we can model in ink as well + if(effect_list and #effect_list > 0) then + for _, e_text in ipairs(effect_list) do + table.insert(lines, "\n ~ ") + table.insert(lines, e_text) + end + -- the divert needs to be put into a new line + table.insert(lines, "\n") + end + -- actually go to the dialog this option leads to table.insert(lines, " -> ") if(not(start_dialog) or start_dialog == "") then start_dialog = "d_1" @@ -182,6 +208,117 @@ yl_speak_up.export_to_ink.print_effect_knot = function(lines, n_id, d_id, o_id, end +-- which variables are used by this NPC? +yl_speak_up.export_to_ink.print_variables_used = function(lines, dialog, n_id, pname) + if(not(dialog) or not(dialog.n_dialogs)) then + return + end + local vars_used = {} + for d_id, d_data in pairs(dialog.n_dialogs or {}) do + for o_id, o_data in pairs(d_data.d_options or {}) do + -- variables may be used in preconditions + for p_id, p in pairs(o_data.o_prerequisites or {}) do + -- we are checking the state of a variable + if(p and p.p_type and p.p_type == "state") then + -- store as key in order to avoid duplicates + vars_used[ p.p_variable ] = true + -- properties are comparable to variables + elseif(p and p.p_type and p.p_type == "property") then + vars_used[ "property "..p.p_value ] = true + end + end + for r_id, r in pairs(o_data.o_results or {}) do + if(r and r.r_type and r.r_type == "state") then + vars_used[ r.r_variable ] = true + elseif(r and r.r_type and r.r_type == "property") then + vars_used[ "property "..r.r_value ] = true + end + end + end + end + table.insert(lines, "\n") + -- we stored as key/value in order to avoid duplicates + for var_name, _ in pairs(vars_used) do + -- replace blanks with an underscore in an attempt to turn it into a legal var name + -- (this is not really sufficient as var names in yl_speak_up are just strings, + -- while the ink language expects sane var names like other lanugages) + -- TODO: this is not necessarily a legitimate var name! + local parts = string.split(var_name, " ") + table.remove(parts, 1) + local v_name = table.concat(parts, "_") + -- stor it for later use + vars_used[var_name] = v_name + -- add the variable as a variable to INK + table.insert(lines, "\nVAR ") + table.insert(lines, v_name) + table.insert(lines, " = NONE") -- start with undefined/nil (we don't know the stored value) + end + table.insert(lines, "\n") + return vars_used +end + + +-- which preconditions and effects can be modelled in ink? +-- +-- in singleplayer adventures, properties can be relevant as well; +-- in multiplayer, other players may affect the state of the property +-- +-- *some* functions may be relevant here: +-- (but not for variables) +-- * compare a variable with a variable +-- * counted dialog option visits +-- * counted option visits +-- +-- types "true" and "false" can be relevant later on + +-- small helper function +local var_with_operator = function(liste, var_name, op, var_cmp_value, vars_used) + -- visits are not stored as variables in ink + if(not(vars_used[var_name])) then + vars_used[var_name] = var_name + end + if(op == "~=") then + op = "!=" + end + if(op=="==" or op=="!=" or op==">=" or op==">" or op=="<=" or op==">") then + table.insert(liste, tostring(vars_used[var_name]).." ".. op.." "..tostring(var_cmp_value)) + elseif(op=="not") then + table.insert(liste, "not "..tostring(vars_used[var_name])) + elseif(op=="is_set") then + table.insert(liste, tostring(vars_used[var_name])) + elseif(op=="is_unset") then + table.insert(liste, tostring(vars_used[var_name]).." == NONE") + end + -- the following values for op cannot really be checked here and are not printed: + -- "more_than_x_seconds_ago","less_than_x_seconds_ago", + -- "quest_step_done", "quest_step_not_done" +end + +yl_speak_up.export_to_ink.translate_precondition_list = function(dialog, preconditions, vars_used, n_id) + -- collect preconditions that may work in ink + local liste = {} + -- variables may be used in preconditions + for p_id, p in pairs(preconditions or {}) do + if(p and p.p_type and p.p_type == "state") then + -- state changes of variables may mostly work in ink as well + var_with_operator(liste, p.p_variable, p.p_operator, p.p_var_cmp_value, vars_used) + elseif(p and p.p_type and p.p_type == "property") then + -- same with properties + var_with_operator(liste, p.p_value, p.p_operator, p.p_var_cmp_value, vars_used) + elseif(p and p.p_type and p.p_type == "evaluate" and p.p_value == "counted_visits_to_option") then + -- simulate the visit counter that ink has in yl_speak_up + local tmp_var_name = n_id.."_"..p.p_param1.."_"..p.p_param2 + var_with_operator(liste, tmp_var_name, p.p_operator, p.p_var_cmp_value, vars_used) + elseif(p and p.p_type and p.p_type == "true") then + table.insert(liste, p.p_type) + elseif(p and p.p_type and p.p_type == "false") then + table.insert(liste, p.p_type) + end + end + return liste +end + + yl_speak_up.export_to_ink_language = function(dialog, n_id) local start_dialog = yl_speak_up.get_start_dialog_id(dialog) if(not(start_dialog)) then @@ -194,6 +331,9 @@ yl_speak_up.export_to_ink_language = function(dialog, n_id) "\nWhat do you wish to do?", "\n+ Talk to ", tostring(dialog.n_npc), " -> ", tostring(n_id).."_"..tostring(start_dialog), "\n+ End -> END"} + + local vars_used = ink_export.print_variables_used(tmp, dialog, n_id, pname) + local sorted_d_list = yl_speak_up.sort_keys(dialog.n_dialogs or {}, true) for i, d_id in ipairs(sorted_d_list) do -- store the knots for actions and effects here: @@ -265,13 +405,18 @@ yl_speak_up.export_to_ink_language = function(dialog, n_id) alternate_text_on_success = "" end + -- which preconditions can be translated to ink? + local p_list = ink_export.translate_precondition_list(dialog, o_data.o_prerequisites, + vars_used, n_id) + local e_list = {} --TODO + -- what remains is to print the option/choice itself ink_export.print_choice(tmp, -- TODO: deal with when_prerequisites_not_met o_data.o_text_when_prerequisites_met, n_id, start_dialog, alternate_text_on_success, target_dialog, o_data.o_visit_only_once, - o_id) + o_id, p_list, e_list) end -- dealt with the option table.insert(tmp, "\n") -- add way to end talking to the NPC