added first basic creation/delition of quest steps

This commit is contained in:
Sokomine 2023-09-15 00:40:34 +02:00
parent 263ec1ac86
commit 4ce6101bca
4 changed files with 350 additions and 13 deletions

120
fs_manage_quest_steps.lua Normal file
View File

@ -0,0 +1,120 @@
-- Imposing an order on the quest steps is...tricky as best as what will
-- be more important to the players will be the order in which the
-- quest steps have to be solved/done - and not an alphabetical order.
-- But we need an order here for a dropdown menu to select each
-- quest step even if it hasn't been assigned any place in the chain
-- of quest steps yet. So - alphabetical order.
yl_speak_up.get_sorted_quest_step_list = function(pname, q_id)
local quest_step_list = {}
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return {}
end
local q_id = yl_speak_up.speak_to[pname].q_id
if(q_id and yl_speak_up.quests[q_id] and yl_speak_up.quests[q_id].step_data) then
for step_id, v in pairs(yl_speak_up.quests[q_id].step_data) do
table.insert(quest_step_list, step_id)
end
end
table.sort(quest_step_list)
return quest_step_list
end
-- helper functions for yl_speak_up.input_fs_manage_quest_steps(..)
-- returns the index of the new quest step
yl_speak_up.input_fs_manage_quest_steps_add_new_entry = function(pname, entry_name)
local q_id = yl_speak_up.speak_to[pname].q_id
local res = yl_speak_up.quest_step_add_quest_step(pname, q_id, entry_name)
-- might make sense to show the error message somewhere
if(res ~= "OK") then
return res
end
-- the new entry will be somewhere in it
local quest_step_list = yl_speak_up.get_sorted_quest_step_list(pname)
return table.indexof(quest_step_list, entry_name)
end
-- helper functions for yl_speak_up.input_fs_manage_quest_steps(..)
-- returns a text describing if deleting the quest worked
yl_speak_up.input_fs_manage_quest_steps_del_old_entry = function(pname, entry_name)
local q_id = yl_speak_up.speak_to[pname].q_id
return yl_speak_up.quest_step_del_quest_step(pname, q_id, entry_name)
end
-- helper functions for yl_speak_up.input_fs_manage_quest_steps(..)
-- implements all the functions that are specific to managing quest steps and not part of
-- general item management
yl_speak_up.input_fs_manage_quest_steps_check_fields = function(player, formname, fields, quest_step_name, list_of_entries)
local pname = player:get_player_name()
if(not(quest_step_name)) then
quest_step_name = ""
end
--[[ TODO: implement some back button functionality?
if(fields and fields.show_variable) then
yl_speak_up.show_fs(player, "manage_variables", {var_name = quest_name})
return
end
--]]
-- this function didn't have anything to do
return "NOTHING FOUND"
end
-- makes use of yl_speak_up.input_fs_manage_general and is thus pretty short
yl_speak_up.input_fs_manage_quest_steps = function(player, formname, fields)
minetest.chat_send_player("singleplayer", "INPUT: "..minetest.serialize(fields))
local pname = player:get_player_name()
local quest_step_list = yl_speak_up.get_sorted_quest_step_list(pname)
local res = yl_speak_up.input_fs_manage_general(player, formname, fields,
-- what_is_the_list_about, min_length, max_length, function_add_new_entry,
"quest step", 2, 70,
yl_speak_up.input_fs_manage_quest_steps_add_new_entry,
quest_step_list,
yl_speak_up.input_fs_manage_quest_steps_del_old_entry,
yl_speak_up.input_fs_manage_quest_steps_check_fields)
return true
end
yl_speak_up.get_fs_manage_quest_steps = function(player, param)
local pname = player:get_player_name()
local formspec = {}
local q_id = yl_speak_up.speak_to[pname].q_id
local quest = yl_speak_up.quests[q_id]
local step_data = quest.step_data
local quest_step_list = yl_speak_up.get_sorted_quest_step_list(pname)
if(param and param ~= "") then
local index = table.indexof(quest_step_list, param)
yl_speak_up.speak_to[pname].tmp_index_general = index + 1
end
table.insert(formspec, "size[18,12]"..
"label[0.2,1.2;A quest step is a single thing a player may do in a quest - "..
"like talking to an NPC.\n"..
"Usually not all quest steps can be done/solved at all times.]")
local selected = yl_speak_up.get_fs_manage_general(player, param,
formspec, quest_step_list,
"Create quest step",
"Create a new quest step for this quest.",
"quest step",
"Enter the name of the new quest step you want to create.\n"..
"This is an internal text shown only to yourself.\n"..
"Players cannot see the names of quest steps.",
"If you click here, the selected quest step will be deleted.\n"..
"This will only be possible if it's not used anywhere.")
--[[
if(selected and selected ~= "") then
local k = selected
-- index 1 is "Add variable:"
table.insert(formspec, "button[12,2.15;4.5,0.6;show_variable;Show and edit this variable]")
end
--]]
return table.concat(formspec, "")
end

View File

@ -218,6 +218,8 @@ yl_speak_up.reload = function(modpath, log_entry)
dofile(modpath .. "quest_api.lua")
-- GUI for adding/editing quests
dofile(modpath .. "fs_manage_quests.lua")
-- GUI for adding/editing quest steps for the quests
dofile(modpath .. "fs_manage_quest_steps.lua")
-- setting skin, wielded item etc.
dofile(modpath .. "fs_fashion.lua")
-- properties for NPC without specific dialogs that want to make use of

View File

@ -25,6 +25,20 @@ yl_speak_up.save_quest_variables = function(force_save)
end
yl_speak_up.handle_json_nil_values = function(data)
if(data and type(data) == "table") then
for k,v in pairs(data) do
if( type(v) == "string" and v == "$NIL_VALUE$") then
data[ k ] = {}
elseif(type(v) == "table") then
data[ k ] = yl_speak_up.handle_json_nil_values(v)
end
end
end
return data
end
-- load the data from disc
yl_speak_up.load_quest_variables = function()
-- load the data from the file
@ -41,12 +55,7 @@ yl_speak_up.load_quest_variables = function()
if(type(data) ~= "table") then
return
end
for k,v in pairs(data) do
if(v == "$NIL_VALUE$") then
data[ k ] = {}
end
end
yl_speak_up.player_vars = data
yl_speak_up.player_vars = yl_speak_up.handle_json_nil_values(data)
if(not(yl_speak_up.player_vars.meta)) then
yl_speak_up.player_vars["meta"] = {}
end
@ -195,7 +204,8 @@ yl_speak_up.get_quest_variables = function(pname, has_write_access)
end
-- if the player has the right privs: allow to access all other variables as well
if( minetest.check_player_privs(pname, {npc_master=true})
or minetest.check_player_privs(pname, {npc_talk_master=true})) then
or minetest.check_player_privs(pname, {npc_talk_master=true})
or minetest.check_player_privs(pname, {npc_talk_admin=true})) then
for k, v in pairs(yl_speak_up.player_vars) do
local parts = string.split(k, " ")
-- variables owned by *other* players
@ -546,6 +556,7 @@ yl_speak_up.save_quest = function(q_id)
end
-- read quest q_id from disc
yl_speak_up.load_quest = function(q_id)
-- load the data from the file
@ -563,12 +574,7 @@ yl_speak_up.load_quest = function(q_id)
if(type(data) ~= "table") then
return
end
for k,v in pairs(data) do
if(v == "$NIL_VALUE$") then
data[ k ] = {}
end
end
yl_speak_up.quests[q_id] = data
yl_speak_up.quests[q_id] = yl_speak_up.handle_json_nil_values(data)
return
end
@ -627,6 +633,7 @@ yl_speak_up.add_quest = function(owner_name, variable_name, quest_name, descr_lo
-- the list of quest steps is stored in the variables' metadata for quicker access
-- (this way we won't have to load the quest file if we want to check a precondition
-- or update the variable value to the next quest step)
-- TODO: store those in the quest file
yl_speak_up.set_variable_metadata(var_name, owner_name, "quest_data", "steps", {"start","finish"})
-- create the quest data structure
@ -658,8 +665,10 @@ yl_speak_up.add_quest = function(owner_name, variable_name, quest_name, descr_lo
-- -> determined from quests.npcs and quests.locations
quest.npcs = {} -- list of NPC that contribute to this quest
-- -> derived from quest.var_name
-- --> or derived from quest.step_data.where
quest.locations = {} -- list of locations that contribute to this quest
-- -> derived from quest.var_name
-- --> or derived from quest.step_data.where
quest.items = {} -- data of quest items created and accepted
-- -> derived from the quest steps
quest.rewards = {} -- list of rewards (item stacks) for this ques
@ -746,6 +755,186 @@ yl_speak_up.get_sorted_quest_list = function(pname)
end
-- quests have a name and a variable which stores their data
-- this returns the q_id (index in the quest table) based on the variable name
yl_speak_up.get_quest_id_by_var_name = function(var_name, owner_name)
local var_name = yl_speak_up.add_pname_to_var(var_name, owner_name)
-- find out which quest we're talking about
for q_id, quest in pairs(yl_speak_up.quests) do
if(quest.var_name == var_name) then
return q_id
end
end
return nil
end
-- quests can also be identified by their name
-- this returns the q_id (index in the quest table) based on the quest name
yl_speak_up.get_quest_id_by_quest_name = function(quest_name)
-- find out which quest we're talking about
for q_id, quest in pairs(yl_speak_up.quests) do
if(quest.name == quest_name) then
return q_id
end
end
return nil
end
-- finds out if player pname is allowed to view (read_only is true)
-- or edit (read_only is false) the quest q_id
yl_speak_up.quest_allow_access = function(q_id, pname, read_only)
-- no quest with that variable as base found
if(not(q_id) or not(yl_speak_up.quests[q_id])) then
return "Quest not found (id: "..tostring(q_id)..")."
end
local quest = yl_speak_up.quests[q_id]
-- check if the player has access rights to that quest
if(quest.owner ~= pname
and not(minetest.check_player_privs(pname, {npc_master=true}))
and not(minetest.check_player_privs(pname, {npc_talk_master=true}))
and not(minetest.check_player_privs(pname, {npc_talk_admin=true}))) then
-- the player may have suitable privs (check access to the variable)
local access_what = "write_access"
if(read_only) then
access_what = "read_access"
end
local allowed = yl_speak_up.get_access_list_for_var(quest.var_name, "", access_what)
if(table.indexof(allowed, pname) == -1) then
return "Sorry. You have no write access to quest \""..tostring(quest.name).."\" "..
"["..tostring(k).."]."
end
end
-- quests that are already open to the public cannot be changed anymore
-- as that would cause chaos; only in "created" and "testing" stage it's
-- possible to change quest steps
if(not(read_only) and (quest.state == "open" or quest.state == "official")) then
return "The quest is in state \""..tostring(quest.state).."\". Quests in such "..
"a state cannot be changed/extended as that would confuse players. "..
"Reset quest state first if changes are unavoidable."
end
return "OK"
end
-- add a quest step to a quest
yl_speak_up.quest_step_add_quest_step = function(pname, q_id, quest_step_name)
local error_msg = yl_speak_up.quest_allow_access(q_id, pname, false)
if(error_msg ~= "OK") then
return error_msg
end
if(not(quest_step_name) or quest_step_name == ""
or string.len(quest_step_name) < 2 or string.len(quest_step_name) > 70) then
return "No name for this quest step given or too long (>70) or too short (<2 characters)."
end
if(not(yl_speak_up.quests[q_id].step_data)) then
yl_speak_up.quests[q_id].step_data = {}
end
-- create an entry for the quest step if needed
if(not(yl_speak_up.quests[q_id].step_data[quest_step_name])) then
yl_speak_up.quests[q_id].step_data[quest_step_name] = {
-- where (NPCs, locations) can this quest step be set?
where = {}}
yl_speak_up.save_quest(q_id)
end
-- return OK even if the quest step existed already
return "OK"
end
-- delete a quest step - but only if it's not used
yl_speak_up.quest_step_del_quest_step = function(pname, q_id, quest_step_name)
local error_msg = yl_speak_up.quest_allow_access(q_id, pname, false)
if(error_msg ~= "OK") then
return error_msg
end
if(not(quest_step_name)
or not(yl_speak_up.quests[q_id].step_data)
or not(yl_speak_up.quests[q_id].step_data[quest_step_name])) then
return "OK"
end
-- the quest step exists; can we delete it?
local quest_step = yl_speak_up.quests[q_id].step_data[quest_step_name]
local anz_where = 0
for k, _ in pairs(quest_step.where or {}) do
anz_where = anz_where + 1
end
if(anz_where > 0) then
return "This quest step is used/set by "..tostring(anz_where)..
" NPCs and/or locations.\nRemove them from this quest step first!"
end
-- is this the previous quest step of another step?
for sn, step_data in pairs(yl_speak_up.quests[q_id].step_data) do
if(step_data and step_data.previous_step and step_data.previous_step == quest_step_name) then
return "Quest step \""..tostring(sn).."\" names this quest step that you want "..
"to delete as its previous step. Please remove that requirement first "..
"for quest step \""..tostring(sn).."\" before deleting this step here."
end
if(step_data and step_data.further_required_steps
and table.indexof(step_data.further_required_steps, quest_step_name) ~= -1) then
return "Quest step \""..tostring(sn).."\" gives this quest step that you want "..
"to delete as one of the further steps required to reach it. Please "..
"remove that requirement first "..
"for quest step \""..tostring(sn).."\" before deleting this step here."
end
-- offered_until_quest_step_reached would be no problem/hinderance and doesn't need checking
end
yl_speak_up.quests[q_id].step_data[quest_step_name] = nil
yl_speak_up.save_quest(q_id)
return "OK"
end
-- add an NPC or location to a quest step (quest_step.where = list of such locations)
-- Note: This is for NPC and locations that SET this very quest step. They ought to be listed here.
-- new_location has to be a table, and new_loc_id an ID to avoid duplicates
-- for NPC, new_loc_id ought to look like this: "NPC <n_id> <d_id> <o_id>"
yl_speak_up.quest_step_add_where = function(pname, q_id, quest_step_name, new_location, new_loc_id) local error_msg = yl_speak_up.quest_allow_access(q_id, pname, false)
if(error_msg ~= "OK") then
return error_msg
end
local quest_step = yl_speak_up.quests[q_id].step_data[quest_step_name]
if(not(quest_step)) then
return "Quest step \""..tostring(quest_step_name).."\" does not exist."
end
if(not(step_data.where)) then
step_data.where = {}
end
if(step_data.where[new_loc_id]) then
return "OK"
end
yl_speak_up.quests[q_id].step_data[quest_step_name].where[new_loc_id] = new_location
-- return OK even if the quest step existed already
return "OK"
end
-- delete a quest step location with the id location_id
yl_speak_up.quest_step_del_where = function(pname, q_id, quest_step_name, location_id)
local error_msg = yl_speak_up.quest_allow_access(q_id, pname, false)
if(error_msg ~= "OK") then
return error_msg
end
local quest_step = yl_speak_up.quests[q_id].step_data[quest_step_name]
if(not(quest_step)) then
return "Quest step \""..tostring(quest_step_name).."\" does not exist."
end
if(not(step_data.where)) then
step_data.where = {}
end
-- delete the quest step location
yl_speak_up.quests[q_id].step_data[quest_step_name].where[location_id] = nil
return "OK"
end
-- TODO: quest_step: previous_step
-- TODO: quest_step: further_required_steps
-- TODO: quest_step: offered_until_quest_step_reached
-- called for example by yl_speak_up.eval_all_preconditions to see if the player
-- can reach quest_step in quest quest_id
yl_speak_up.quest_step_possible = function(player, quest_step, quest_id, n_id, d_id, o_id)
@ -766,3 +955,21 @@ yl_speak_up.quest_step_reached = function(player, quest_step, quest_id, n_id, d_
-- TODO: actually store the quest progress
-- minetest.chat_send_player("singleplayer", "SETTING quest step "..tostring(quest_step).." for quest "..tostring(quest_id))
end
-- load all known quests
yl_speak_up.load_all_quests = function()
for var_name, var_data in pairs(yl_speak_up.player_vars) do
local var_type = yl_speak_up.get_variable_metadata(var_name, "var_type")
if(var_type == "quest") then
local data = yl_speak_up.get_variable_metadata(var_name, "quest_data", true)
if(data and data["quest_nr"]) then
yl_speak_up.load_quest("q_"..tostring(data["quest_nr"]))
end
end
end
end
-- do so on startup and reload
yl_speak_up.load_all_quests()

View File

@ -107,6 +107,10 @@ yl_speak_up.input_handler = function(player, formname, fields)
elseif formname == "yl_speak_up:manage_quests" then
yl_speak_up.input_fs_manage_quests(player, formname, fields)
return true
-- handled in fs_manage_quest_steps.lua
elseif formname == "yl_speak_up:manage_quest_steps" then
yl_speak_up.input_fs_manage_quest_steps(player, formname, fields)
return true
-- handled in fs_alternate_text.lua
elseif formname == "yl_speak_up:show_what_points_to_this_dialog" then
yl_speak_up.input_fs_show_what_points_to_this_dialog(player, formname, fields)
@ -410,6 +414,10 @@ yl_speak_up.show_fs = function(player, fs_name, param)
yl_speak_up.show_fs_ver(pname, "yl_speak_up:manage_quests",
yl_speak_up.get_fs_manage_quests(player, param))
elseif(fs_name == "manage_quest_steps") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:manage_quest_steps",
yl_speak_up.get_fs_manage_quest_steps(player, param))
elseif(fs_name == "show_what_points_to_this_dialog") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:show_what_points_to_this_dialog",
yl_speak_up.show_what_points_to_this_dialog(player, param))