-- 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 yl_speak_up.export_to_ink = {} -- an abbreviation local ink_export = yl_speak_up.export_to_ink -- in order to be able to deal with multiple NPC in ink, we use the NPC id n_id -- plus the dialog id d_id as a name prefix; o_id, a_id and r_id are appended -- as needed yl_speak_up.export_to_ink.print_knot_name = function(lines, knot_name) table.insert(lines, "\n\n=== ") table.insert(lines, tostring(knot_name or "ERROR")) table.insert(lines, " ===") end -- execution of effects ends if an on_failure effect is reached; for ink to be able to -- display effects (as tags) correctly, we need to add them at the right place - some -- tags come after the option/choice, some after the last action (if there is an action), -- some between on_failure actions (if they exist) yl_speak_up.export_to_ink.add_effect_tags = function(text, sorted_e_list, effects, start_at_effect) if(not(text)) then text = "" end if(not(start_at_effect) or start_at_effect > #sorted_e_list) then return text end for i = start_at_effect, #sorted_e_list do local r_id = sorted_e_list[i] if(effects and effects[r_id]) then local r = effects[r_id] if(r and r.r_type and r.r_type == "on_failure") then -- end as soon as we reach the next on_failure dialog return text end if(r and r.r_type and r.r_type ~= "dialog") then if(text ~= "") then text = text.."\n " end -- the dialog effect is something diffrent text = text.."# effect "..tostring(r_id).." "..tostring(yl_speak_up.show_effect(r)) end end end return text end -- choices are a bit complicated as they may contain alternate_text that is to be -- 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) -- don't repeat the text of the choice in the output when running ink table.insert(lines, "\n+ [") table.insert(lines, choice_text) table.insert(lines, "]") -- dialogs, actions and effects can have an alternate_text with which they override the -- text of the target_dialog/divert_to; -- this isn't perfect as alternate_text supports $TEXT$ for inserting the text of the -- target dialog anywhere in the alternate_text - while ink will print out this alternate_text -- first and then that of the target dialog/divert_to if(alternate_text and alternate_text ~= "") then -- a new line and some indentation makes this more readable table.insert(lines, "\n ") table.insert(lines, alternate_text) -- write the divert into a new line as well table.insert(lines, "\n ") end table.insert(lines, " -> ") if(not(start_dialog) or start_dialog == "") then start_dialog = "d_1" end if(not(divert_to) or divert_to == "") then -- go back to the start dialog (the start dialog may have been changed) divert_to = tostring(n_id).."_"..tostring(start_dialog) elseif(divert_to == "d_end" or divert_to == tostring(n_id).."_d_end") then -- go back to choosing between talking to NPC and end divert_to = tostring(n_id).."_main" elseif(string.sub(divert_to, 1, 2) ~= "n_") then -- make sure it is prefixed with the n_id divert_to = tostring(n_id).."_"..tostring(divert_to) end table.insert(lines, divert_to) end -- this prints the dialog as a knot - but without choices (those are added to the lines table later) -- d: dialog yl_speak_up.export_to_ink.print_dialog_knot = function(lines, n_id, d_id, d) local knot_name = tostring(n_id).."_"..tostring(d_id) ink_export.print_knot_name(lines, knot_name) -- many characters at the start of a line have a special meaning; -- hopefully they will not be obstrusive later on; -- TODO: in order to be on the safe side: add a ":" in front of each line? local t = d.d_text or "" if(t == "") then -- entirely empty text for knots does not work t = "No text." end -- t = string.gsub(t, "\n([:>=])", "\n %1") table.insert(lines, "\n") table.insert(lines, t) return knot_name end -- actions can fail *and* be aborted by the player; in order to model that in ink, we add -- a knot for each action -- Parameter: -- a action yl_speak_up.export_to_ink.print_action_knot = function(lines, n_id, d_id, o_id, start_dialog, a, alternate_text_on_success, next_target) local knot_name = tostring(n_id).."_"..tostring(d_id).."_"..tostring(o_id).."_"..tostring(a.a_id) ink_export.print_knot_name(lines, knot_name) table.insert(lines, "\n:action: ") table.insert(lines, a.a_id) table.insert(lines, " ") table.insert(lines, yl_speak_up.show_action(a)) ink_export.print_choice(lines, "Action was successful", n_id, start_dialog, alternate_text_on_success, next_target) ink_export.print_choice(lines, "Action failed", n_id, start_dialog, a.alternate_text, a.a_on_failure) ink_export.print_choice(lines, "Back", n_id, start_dialog, nil, tostring(n_id).."_"..tostring(d_id)) return knot_name end -- there is a special on_failure effect that can lead to a diffrent target dialog and print -- out a diffrent alternate_text if the *previous* effect failed; in order to model that in -- ink, we add a knot for such on_failure effects -- Parameter: -- r effect/result -- r_prev previous effect yl_speak_up.export_to_ink.print_effect_knot = function(lines, n_id, d_id, o_id, start_dialog, r, r_prev, alternate_text_on_success, next_target) local knot_name = tostring(n_id).."_"..tostring(d_id).."_"..tostring(o_id).."_"..tostring(r.r_id) ink_export.print_knot_name(lines, knot_name) table.insert(lines, "\n:effect: ") table.insert(lines, r.r_id) table.insert(lines, " ") -- show text of the *previous effect* - because that is the one which may have failed: table.insert(lines, yl_speak_up.show_effect(r)) table.insert(lines, "\nThe previous effect was: ") table.insert(lines, r_prev.r_id) table.insert(lines, " ") -- show text of the *previous effect* - because that is the one which may have failed: table.insert(lines, yl_speak_up.show_effect(r_prev)) ink_export.print_choice(lines, "Effect was successful", n_id, start_dialog, alternate_text_on_success, next_target) ink_export.print_choice(lines, "Effect failed", n_id, start_dialog, r.alternate_text, r.r_value) return knot_name 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 start_dialog = "d_1" end local main = tostring(n_id).."_main" local tmp = {"-> ", main, "\n=== ", main, " ===", "\nWhat do you wish to do?", "\n+ Talk to ", tostring(dialog.n_npc), " -> ", tostring(n_id).."_"..tostring(start_dialog), "\n+ End -> END"} 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: local tmp2 = {} local d = dialog.n_dialogs[d_id] -- print the dialog knot, but without choices (those we add in the following loop) local this_knot_name = ink_export.print_dialog_knot(tmp, n_id, d_id, d) -- iterate over all options local sorted_o_list = yl_speak_up.get_sorted_options(dialog.n_dialogs[d_id].d_options or {}, "o_sort") for j, o_id in ipairs(sorted_o_list) do local o_data = d.d_options[o_id] local sorted_a_list = yl_speak_up.sort_keys(o_data.actions or {}) local sorted_e_list = yl_speak_up.sort_keys(o_data.o_results or {}) -- we will get alternate_text from the dialog result later on local alternate_text_on_success = "" local target_dialog = nil -- what is the normal target dialog/divert (in ink language) of this dialog? for k, r_id in ipairs(sorted_e_list) do local r = o_data.o_results[r_id] if(r and r.r_type and r.r_type == "dialog") then target_dialog = tostring(n_id).."_"..tostring(r.r_value) alternate_text_on_success = r.alternate_text or "" end end -- iterate backwards through the effects and serach for on_failure; -- the first effect cannot be an on_failure effect because on_failure effects -- decide on failure/success of the *previous* effect for k = #sorted_e_list, 2, -1 do local r_id = sorted_e_list[k] local r = o_data.o_results[r_id] if(r and r.r_type and r.r_type == "on_failure") then local r_prev = o_data.o_results[sorted_e_list[k-1]] -- *after* this effect we still need to execute all the other -- remaining effects (read: add them as tag) alternate_text_on_success = ink_export.add_effect_tags( alternate_text_on_success, sorted_e_list, o_data.o_results, k) -- whatever dialog comes previously - the dialog, an action, or -- another on_failure dialog - needs to lead to this dialog target_dialog = ink_export.print_effect_knot(tmp2, n_id, d_id, o_id, start_dialog, r, r_prev, alternate_text_on_success, target_dialog) -- we have dealt with the alternate text (it will only be shown -- in the last on_failure dialog before we go to the target) alternate_text_on_success = "" end end -- add the remaining effects alternate_text_on_success = ink_export.add_effect_tags( alternate_text_on_success, sorted_e_list, o_data.o_results, 1) -- iterate backwards through the actions (though usually only one is supported) for k = #sorted_a_list, 1, -1 do local a_id = sorted_a_list[k] local a = o_data.actions[a_id] target_dialog = ink_export.print_action_knot(tmp2, n_id, d_id, o_id, start_dialog, a, alternate_text_on_success, target_dialog) -- has been dealt with alternate_text_on_success = "" end -- 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) end -- dealt with the option table.insert(tmp, "\n") -- add way to end talking to the NPC ink_export.print_choice(tmp, "Farewell!", n_id, start_dialog, nil, tostring(n_id).."_main") -- add the knots for actions and effects for this dialog and all its options: for _, line in ipairs(tmp2) do table.insert(tmp, line) end end return table.concat(tmp, "") end