mirror of
https://gitea.your-land.de/Sokomine/yl_speak_up.git
synced 2025-06-18 21:28:03 +02:00
export_to_ink: works apart from preconditions and variables
This commit is contained in:
parent
2d677127dd
commit
b076e6a2f5
@ -1,180 +1,271 @@
|
|||||||
-- helper functions for export_to_ink_language:
|
-- helper functions for export_to_ink_language:
|
||||||
|
|
||||||
-- TODO: include alternate_text for effects
|
|
||||||
-- TODO: include effect on_failure
|
|
||||||
-- TODO: include effect setting variables to values (problem: diffrent characters allowed)
|
-- TODO: include effect setting variables to values (problem: diffrent characters allowed)
|
||||||
-- TODO: include preconditions/conditions regarding setting variables
|
-- TODO: include preconditions/conditions regarding setting variables
|
||||||
|
|
||||||
-- helper function:
|
|
||||||
-- do not quit the ink game if the player selected an end option - allow to continue talking
|
-- 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_rewrite_target = function(target, n_id)
|
yl_speak_up.export_to_ink = {}
|
||||||
if(not(target)) then
|
|
||||||
return "-> END"
|
-- an abbreviation
|
||||||
elseif(target == "d_end" or target == tostring(n_id).."_d_end") then
|
local ink_export = yl_speak_up.export_to_ink
|
||||||
return tostring(n_id).."_main"
|
|
||||||
end
|
|
||||||
return target
|
|
||||||
|
-- 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
|
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
|
-- d: dialog
|
||||||
yl_speak_up.export_to_ink_dialog_knot = function(lines, d, n_d_id)
|
yl_speak_up.export_to_ink.print_dialog_knot = function(lines, n_id, d_id, d)
|
||||||
table.insert(lines, "\n\n=== ")
|
local knot_name = tostring(n_id).."_"..tostring(d_id)
|
||||||
table.insert(lines, n_d_id)
|
ink_export.print_knot_name(lines, knot_name)
|
||||||
table.insert(lines, " ===")
|
|
||||||
-- many characters at the start of a line have a special meaning;
|
-- many characters at the start of a line have a special meaning;
|
||||||
-- hopefully they will not be obstrusive later on;
|
-- hopefully they will not be obstrusive later on;
|
||||||
-- TODO: in order to be on the safe side: add a ":" in front of each line?
|
-- TODO: in order to be on the safe side: add a ":" in front of each line?
|
||||||
local t = d.d_text or ""
|
local t = d.d_text or ""
|
||||||
if(t == "") then
|
if(t == "") then
|
||||||
-- entirely empty text for knots does not work
|
-- entirely empty text for knots does not work
|
||||||
t = "[no text]"
|
t = "No text."
|
||||||
end
|
end
|
||||||
-- t = string.gsub(t, "\n([:>=])", "\n %1")
|
-- t = string.gsub(t, "\n([:>=])", "\n %1")
|
||||||
table.insert(lines, "\n")
|
table.insert(lines, "\n")
|
||||||
table.insert(lines, t)
|
table.insert(lines, t)
|
||||||
table.insert(lines, "\n")
|
return knot_name
|
||||||
end
|
end
|
||||||
|
|
||||||
-- a: action
|
-- actions can fail *and* be aborted by the player; in order to model that in ink, we add
|
||||||
yl_speak_up.export_to_ink_action_knot = function(lines, a, n_d_id, o_id, next_target, n_id)
|
-- a knot for each action
|
||||||
table.insert(lines, "\n\n=== ")
|
-- Parameter:
|
||||||
table.insert(lines, n_d_id.."_"..tostring(o_id).."_"..tostring(a.a_id))
|
-- a action
|
||||||
table.insert(lines, " ===")
|
yl_speak_up.export_to_ink.print_action_knot = function(lines, n_id, d_id, o_id, start_dialog,
|
||||||
table.insert(lines, "\n:action:")
|
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))
|
table.insert(lines, yl_speak_up.show_action(a))
|
||||||
table.insert(lines, "\n+ [Action was successful] -> ")
|
|
||||||
table.insert(lines, yl_speak_up.export_to_ink_rewrite_target(next_target, n_id))
|
ink_export.print_choice(lines, "Action was successful", n_id, start_dialog,
|
||||||
table.insert(lines, "\n+ [Action failed] ")
|
alternate_text_on_success, next_target)
|
||||||
table.insert(lines, tostring(a.alternate_text or ""))
|
|
||||||
table.insert(lines, " -> ")
|
ink_export.print_choice(lines, "Action failed", n_id, start_dialog,
|
||||||
if(a.a_on_failure) then
|
a.alternate_text, a.a_on_failure)
|
||||||
next_target = tostring(n_id).."_"..tostring(a.a_on_failure)
|
|
||||||
else
|
ink_export.print_choice(lines, "Back", n_id, start_dialog,
|
||||||
next_target = next_target or "ERROR"
|
nil, tostring(n_id).."_"..tostring(d_id))
|
||||||
end
|
return knot_name
|
||||||
table.insert(lines, yl_speak_up.export_to_ink_rewrite_target(tostring(next_target), n_id))
|
|
||||||
table.insert(lines, "\n+ [Back] -> ")
|
|
||||||
table.insert(lines, n_d_id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- r: effect/result
|
|
||||||
-- r_prev: previous effect
|
-- there is a special on_failure effect that can lead to a diffrent target dialog and print
|
||||||
yl_speak_up.export_to_ink_effect_knot = function(lines, r, n_d_id, o_id, next_target, r_prev, n_id)
|
-- out a diffrent alternate_text if the *previous* effect failed; in order to model that in
|
||||||
table.insert(lines, "\n\n=== ")
|
-- ink, we add a knot for such on_failure effects
|
||||||
table.insert(lines, n_d_id.."_"..tostring(o_id).."_"..tostring(r.r_id))
|
-- Parameter:
|
||||||
table.insert(lines, " ===")
|
-- r effect/result
|
||||||
table.insert(lines, "\n:effect:")
|
-- 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:
|
-- 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))
|
table.insert(lines, yl_speak_up.show_effect(r_prev))
|
||||||
table.insert(lines, "\n+ [Effect was successful] -> ")
|
|
||||||
table.insert(lines, yl_speak_up.export_to_ink_rewrite_target(next_target, n_id))
|
ink_export.print_choice(lines, "Effect was successful", n_id, start_dialog,
|
||||||
table.insert(lines, "\n+ [Effect failed] -> ")
|
alternate_text_on_success, next_target)
|
||||||
table.insert(lines, yl_speak_up.export_to_ink_rewrite_target(tostring(r.r_value or "ERROR"), n_id))
|
|
||||||
|
ink_export.print_choice(lines, "Effect failed", n_id, start_dialog,
|
||||||
|
r.alternate_text, r.r_value)
|
||||||
|
|
||||||
|
return knot_name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
yl_speak_up.export_to_ink_language = function(dialog, n_id)
|
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 main = tostring(n_id).."_main"
|
||||||
local tmp = {"-> ", main,
|
local tmp = {"-> ", main,
|
||||||
"\n=== ", main, " ===\n",
|
"\n=== ", main, " ===",
|
||||||
"\nWhat do you wish to do?",
|
"\nWhat do you wish to do?",
|
||||||
"\n+ Talk to ", tostring(dialog.n_npc), " -> ", tostring(n_id).."_d_1",
|
"\n+ Talk to ", tostring(dialog.n_npc), " -> ", tostring(n_id).."_"..tostring(start_dialog),
|
||||||
"\n+ End -> END"}
|
"\n+ End -> END"}
|
||||||
local sorted_d_list = yl_speak_up.sort_keys(dialog.n_dialogs or {}, true)
|
local sorted_d_list = yl_speak_up.sort_keys(dialog.n_dialogs or {}, true)
|
||||||
for i, d_id in ipairs(sorted_d_list) do
|
for i, d_id in ipairs(sorted_d_list) do
|
||||||
-- store the knots for actions and effects here:
|
-- store the knots for actions and effects here:
|
||||||
local tmp2 = {}
|
local tmp2 = {}
|
||||||
local d = dialog.n_dialogs[d_id]
|
local d = dialog.n_dialogs[d_id]
|
||||||
local n_d_id = tostring(n_id).."_"..tostring(d_id)
|
-- print the dialog knot, but without choices (those we add in the following loop)
|
||||||
yl_speak_up.export_to_ink_dialog_knot(tmp, d, n_d_id)
|
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")
|
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
|
for j, o_id in ipairs(sorted_o_list) do
|
||||||
local o_data = d.d_options[o_id]
|
local o_data = d.d_options[o_id]
|
||||||
local target_action = nil
|
|
||||||
local target_effect = nil
|
|
||||||
local target_dialog = nil
|
|
||||||
local sorted_a_list = yl_speak_up.sort_keys(o_data.actions or {})
|
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 {})
|
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 = ""
|
|
||||||
-- is there an action? That takes precedence.
|
|
||||||
if(#sorted_a_list > 0) then
|
|
||||||
-- this action dialog knot needs to be created:
|
|
||||||
target_action = n_d_id.."_"..tostring(o_id).."_"..tostring(sorted_a_list[1])
|
|
||||||
alternate_text = ""
|
|
||||||
end
|
|
||||||
-- is there an effect/result of type on_failure?
|
|
||||||
local effect_list = {}
|
|
||||||
for k, r_id in ipairs(sorted_e_list) do
|
|
||||||
local r = o_data.o_results[r_id]
|
|
||||||
-- checking for previous effect failed?
|
|
||||||
if(not(target_effect)
|
|
||||||
and k > 1 and r and r.r_type and r.r_type == "on_failure") then
|
|
||||||
-- this effect dialog knot needs to be created:
|
|
||||||
target_effect = n_d_id.."_"..tostring(o_id).."_"..tostring(r_id)
|
|
||||||
elseif(target_effect
|
|
||||||
and k > 1 and r and r.r_type and r.r_type == "on_failure") then
|
|
||||||
-- collect all effects that need their own knots
|
|
||||||
table.insert(effect_list, target_effect)
|
|
||||||
-- normal target dialog?
|
|
||||||
elseif(not(target_dialog)
|
|
||||||
and r and r.r_type and r.r_type == "dialog") then
|
|
||||||
target_dialog = tostring(n_id).."_"..tostring(r.r_value)
|
|
||||||
alternate_text = r.alternate_text or ""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.insert(effect_list, target_dialog)
|
|
||||||
local target = (target_action or target_effect or target_dialog or "d_1")
|
|
||||||
table.insert(tmp, "+ [")
|
|
||||||
table.insert(tmp, o_data.o_text_when_prerequisites_met)
|
|
||||||
table.insert(tmp, "] ")
|
|
||||||
table.insert(tmp, alternate_text)
|
|
||||||
|
|
||||||
-- add the actions
|
-- we will get alternate_text from the dialog result later on
|
||||||
local next_target = "ERROR"
|
local alternate_text_on_success = ""
|
||||||
for k, a_id in ipairs(sorted_a_list) do
|
local target_dialog = nil
|
||||||
local a = o_data.actions[a_id]
|
-- what is the normal target dialog/divert (in ink language) of this dialog?
|
||||||
if(k < #sorted_a_list) then
|
|
||||||
next_target = n_d_id.."_"..tostring(o_id).."_"..tostring(sorted_a_list[k+1])
|
|
||||||
elseif(target_effect) then
|
|
||||||
next_target = target_effect
|
|
||||||
elseif(target_dialog) then
|
|
||||||
next_target = target_dialog
|
|
||||||
else
|
|
||||||
next_target = "d_1"
|
|
||||||
end
|
|
||||||
yl_speak_up.export_to_ink_action_knot(tmp2, a, n_d_id, o_id, next_target,n_id)
|
|
||||||
end
|
|
||||||
-- add the effects
|
|
||||||
for k, r_id in ipairs(sorted_e_list) do
|
for k, r_id in ipairs(sorted_e_list) do
|
||||||
local r = o_data.o_results[r_id]
|
local r = o_data.o_results[r_id]
|
||||||
-- checking for previous effect failed?
|
if(r and r.r_type and r.r_type == "dialog") then
|
||||||
if(not(target_effect)
|
target_dialog = tostring(n_id).."_"..tostring(r.r_value)
|
||||||
and k > 1 and r and r.r_type and r.r_type == "on_failure") then
|
alternate_text_on_success = r.alternate_text or ""
|
||||||
-- this effect dialog knot needs to be created:
|
|
||||||
if(effect_list > 0) then
|
|
||||||
next_target = effect_list[1]
|
|
||||||
table.remove(effect_list, 1)
|
|
||||||
else
|
|
||||||
next_target = "ERROR"
|
|
||||||
end
|
|
||||||
yl_speak_up.export_to_ink_effect_knot(tmp2, r, n_d_id, o_id, next_target, o_data.o_results[sorted_e_list[k-1]], n_id)
|
|
||||||
elseif(not(r) or not(r.r_type) or r.r_type ~= "dialog") then
|
|
||||||
-- add the effect as a tag
|
|
||||||
-- TODO: sometimes this needs to be added at diffrent places (action, previous effect)
|
|
||||||
table.insert(tmp, "\n # effect ")
|
|
||||||
table.insert(tmp, yl_speak_up.show_effect(r))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
table.insert(tmp, "\n -> ")
|
|
||||||
table.insert(tmp, yl_speak_up.export_to_ink_rewrite_target(target, n_id))
|
-- iterate backwards through the effects and serach for on_failure;
|
||||||
table.insert(tmp, "\n")
|
-- the first effect cannot be an on_failure effect because on_failure effects
|
||||||
end
|
-- 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")
|
table.insert(tmp, "\n")
|
||||||
-- add way to end talking to the NPC
|
-- add way to end talking to the NPC
|
||||||
table.insert(tmp, "\n+ Farewell! -> "..tostring(n_id).."_main")
|
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:
|
-- add the knots for actions and effects for this dialog and all its options:
|
||||||
for _, line in ipairs(tmp2) do
|
for _, line in ipairs(tmp2) do
|
||||||
table.insert(tmp, line)
|
table.insert(tmp, line)
|
||||||
|
Loading…
Reference in New Issue
Block a user