diff --git a/fs_manage_quests.lua b/fs_manage_quests.lua new file mode 100644 index 0000000..d1b9fa1 --- /dev/null +++ b/fs_manage_quests.lua @@ -0,0 +1,79 @@ +-- helper functions for yl_speak_up.input_fs_manage_quests(..) +-- returns the index of the new quest +yl_speak_up.input_fs_manage_quests_add_new_entry = function(pname, entry_name) + local res = yl_speak_up.add_quest(pname, entry_name, + "Name of your quest", + "Enter a longer description here for describing the quest ".. + "to players who search for one.", + "Enter a short description here describing what the quest is about.", + "Room for comments/notes") + -- TODO: might make sense to show the error message somewhere + if(res ~= "OK") then + return -1 + end + local quest_list = yl_speak_up.get_sorted_quest_list(pname) + return table.indexof(quest_list, entry_name) +end + +-- helper functions for yl_speak_up.input_fs_manage_quests(..) +-- returns a text describing if deleting the quest worked +yl_speak_up.input_fs_manage_quests_del_old_entry = function(pname, entry_name) + return "NOT IMPLEMENTED YET" + -- delete (empty) variable +-- return yl_speak_up.del_quest_variable(pname, entry_name, nil) +end + +-- helper functions for yl_speak_up.input_fs_manage_quests(..) +-- implements all the functions that are specific to managing quests and not part of +-- general item management +yl_speak_up.input_fs_manage_quests_check_fields = function(player, formname, fields, quest_name, list_of_entries) + local pname = player:get_player_name() + if(not(quest_name)) then + quest_name = "" + 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_quests = function(player, formname, fields) + local pname = player:get_player_name() + local quest_list = yl_speak_up.get_sorted_quest_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", 2, 80, + yl_speak_up.input_fs_manage_quests_add_new_entry, + quest_list, + yl_speak_up.input_fs_manage_quests_del_old_entry, + yl_speak_up.input_fs_manage_quests_check_fields) + return true +end + + +yl_speak_up.get_fs_manage_quests = function(player, param) + local pname = player:get_player_name() + local quest_list = yl_speak_up.get_sorted_quest_list(pname) + local formspec = {} + table.insert(formspec, "size[18,12]".. + "label[0.2,1.2;A quest is a linear sequence of quest steps. Quests can ".. + "depend on and influence other quests.\n".. + "Progress for each player is stored in a variable. The name of ".. + "that variable cannot be changed after creation.]") + local selected = yl_speak_up.get_fs_manage_general(player, param, + formspec, quest_list, + "Create quest", + "Create a new varialbe with the name\n".. + "you entered in the field to the left.", + "quest", + "Enter the name of the new quest you want to create.\n".. + "You can't change this name afterwards. But you *can*\n".. + "add and change a human readable description later on.", + "If you click here, the selected quest 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:" + end + return table.concat(formspec, "") +end diff --git a/fs_quest_gui.lua b/fs_quest_gui.lua index 6b9dd3d..da988ef 100644 --- a/fs_quest_gui.lua +++ b/fs_quest_gui.lua @@ -14,6 +14,10 @@ yl_speak_up.input_quest_gui = function(player, formname, fields) yl_speak_up.speak_to[pname][ "working_at" ] = "quest_gui" yl_speak_up.show_fs(player, "manage_variables") return ret + elseif(fields.manage_quests) then + yl_speak_up.speak_to[pname][ "working_at" ] = "quest_gui" + yl_speak_up.show_fs(player, "manage_quests") + return ret end -- the calling NPC shall no longer do anything return ret @@ -22,8 +26,8 @@ end yl_speak_up.get_fs_quest_gui = function(player, param) local pname = player:get_player_name() - - return "size[10,2]".. + return "size[24,20]".. "label[0,0.5;Hi. This is a quest admin gui.]".. - "button[0.2,1.0;4.0,0.6;manage_variables;Manage variables]" + "button[0.2,1.0;4.0,0.6;manage_variables;Manage variables]".. + "button[6.2,1.0;4.0,0.6;manage_quests;Manage quests]" end diff --git a/init.lua b/init.lua index 4d83687..8c3a162 100644 --- a/init.lua +++ b/init.lua @@ -118,6 +118,8 @@ yl_speak_up.reload = function(modpath, log_entry) dofile(modpath .. "fs_manage_variables.lua") -- handle variables for quests for player-owned NPC dofile(modpath .. "quest_api.lua") + -- GUI for adding/editing quests + dofile(modpath .. "fs_manage_quests.lua") -- setting skin, wielded item etc. dofile(modpath .. "fs_fashion.lua") -- properties for NPC without specific dialogs that want to make use of diff --git a/quest_api.lua b/quest_api.lua index 856d151..298d936 100644 --- a/quest_api.lua +++ b/quest_api.lua @@ -524,3 +524,191 @@ end yl_speak_up.get_time_in_seconds = function() return math.floor(minetest.get_us_time()/1000000) end + + +----------------------------------------------------------------------------- +-- Quests as such (up until here we mostly dealt with variables) +----------------------------------------------------------------------------- + +-- uses yl_speak_up.quest_path +-- uses yl_speak_up.number_of_quests = yl_speak_up.modstorage:get_int("max_quest_id") or 0 + +-- table containing the quest data with q_id as index +yl_speak_up.quests = {} + + +-- store quest q_id to disc +yl_speak_up.save_quest = function(q_id) + local json = minetest.write_json(yl_speak_up.quests[q_id]) + -- actually store it on disk + local file_name = yl_speak_up.worldpath..yl_speak_up.quest_path..DIR_DELIM..q_id..".json" + minetest.safe_file_write(file_name, json) +end + + +-- read quest q_id from disc +yl_speak_up.load_quest = function(q_id) + -- load the data from the file + local file_name = yl_speak_up.worldpath..yl_speak_up.quest_path..DIR_DELIM..q_id..".json" + local file, err = io.open(file_name, "r") + if err then + return + end + io.input(file) + local text = io.read() + -- all values saved in the tables as such are strings + local data = minetest.parse_json(text, "$NIL_VALUE$") + io.close(file) + + 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 + return +end + + +-- get data of quest q_id +yl_speak_up.get_quest = function(q_id) + if(not(yl_speak_up.quests[q_id])) then + load_quest(q_id) + end + return yl_speak_up.quests[q_id] +end + + +-- add/create a new quest +-- a quest is based on a variable; the variable is needed to store quest progress; +-- as this variable is of type integer, quests can only be linear; +-- in order to offer alternatives, players can add as many quests as they want and +-- make them depend on each other +yl_speak_up.add_quest = function(owner_name, variable_name, quest_name, descr_long, descr_short, comment) + -- add a special variable (if needed) for saving quest meta data + if(not(yl_speak_up.player_vars[ "$QUEST_META_DATA$" ])) then + yl_speak_up.player_vars[ "$QUEST_META_DATA$" ] = {} + yl_speak_up.save_quest_variables(true) + end + + if(not(variable_name) or variable_name == "") then + return "Missing name of variable." + end + -- determine the full name of the variable used to store quest progress + local var_name = yl_speak_up.add_pname_to_var(variable_name, owner_name) + -- if it is a new variable: make sure it gets created + if(not(yl_speak_up.player_vars[var_name])) then + -- create the new varialbe + yl_speak_up.add_quest_variable(owner_name, variable_name) + else + -- if it exists already: make sure it is of a type that can be used + local var_type = yl_speak_up.get_variable_metadata(var_name, "var_type") + if("var_type" == "time_based") then + return "Variable already used as a timer." + elseif("var_type" == "quest") then + return "Variable already used by another quest." + end + end + -- set the variable for the quest creator to 0 - so that it's possible to check for + -- var_name is set (to a value) in a precondition and thus only allow the quest creator + -- to test the quest in the beginning + yl_speak_up.set_quest_variable_value(owner_name, var_name, 0) + -- set the variable type to quest + yl_speak_up.set_variable_metadata(var_name, owner_name, "var_type", nil, "quest") + -- get a uniq ID for storing this quest (mostly needed for creating a safe file name) + local quest_nr = yl_speak_up.number_of_quests + 1 + yl_speak_up.number_of_quests = quest_nr + yl_speak_up.modstorage:set_int("max_quest_nr", yl_speak_up.number_of_quests) + -- store this number in the variable $META$ data + yl_speak_up.set_variable_metadata(var_name, owner_name, "quest_data", "quest_nr", quest_nr) + -- 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) + yl_speak_up.set_variable_metadata(var_name, owner_name, "quest_data", "steps", {"start","finish"}) + + -- create the quest data structure + local quest = {} + quest.nr = quest_nr + quest.id = "q_"..quest_nr -- quest ID + quest.name = quest_name -- human-readable name of the quest + quest.description = (descr_long or "") + -- long description of what the quest is about + quest.short_desc = (descr_short or "") + -- a short description of this quest which may later be used to + -- advertise for the quest in a quest log + quest.comment = (comment or "") + -- comment to other programmers who might want to maintain the + -- quest later on + quest.owner = owner_name -- creator of the quest + quest.var_name = var_name -- name of the variable where progress is stored for each player +-- quest.steps = { -- list of names (strings) of the quest steps +-- "start", -- the quest needs to start somehow +-- "finish"} -- and it needs to finish somehow +-- the following things can be determined automaticly, BUT: in order to PLAN a future +-- quest, it is easier to gather information here first + quest.step_data = {} -- table containing information about a quest step (=key) + -- this may also be information about WHERE a quest step shall + -- take place + quest.subquests = {} -- list of other quest_ids that contribute to this quest + -- -> determined from quests.npcs and quests.locations + quest.is_subquest_of = {} -- list of quest_ids this quest contributes to + -- -> determined from quests.npcs and quests.locations + quest.npcs = {} -- list of NPC that contribute to this quest + -- -> derived from quest.var_name + quest.locations = {} -- list of locations that contribute to this quest + -- -> derived from quest.var_name + quest.items = {} -- data of quest items created and accepted + -- -> derived from the quest steps + quest.rewards = {} -- list of rewards (item stacks) for this ques + quest.testers = {} -- list of player names that can test the quest + -- -> during the created/testing phase: any player for which + -- quest.var_name is set to a value + quest.solved_by = {} -- list of names of players that solved the quest at least once + quest.state = "created" -- state of the quest: + -- created: only the creator can do it + -- testing: players listed in quest.testers can do the quest + -- open: *all* players with interact can try to solve the quest + -- *AND* changes to the quest are now impossible (apart from + -- changing texts in the NPC) + -- official: official server quest; NPC can create items out of thin air + -- store the new quest in the quest table + yl_speak_up.quests[quest.id] = quest + -- and store it on disc + yl_speak_up.save_quest(quest.id) + return "OK" +end + + +-- returns a list of all quest IDs to which the player has write access +yl_speak_up.get_quest_owner_list = function(pname) + local var_list = yl_speak_up.get_quest_variables_with_write_access(pname) + local quest_id_list = {} + for i, var_name in ipairs(var_list) do + local t = yl_speak_up.get_variable_metadata(var_name, "var_type") + if(t and t == "quest") then + local data = yl_speak_up.get_variable_metadata(var_name, "quest_data", true) + if(data and data["quest_nr"]) then + table.insert(quest_id_list, "q_"..tostring(data["quest_nr"])) + end + end + end + return quest_id_list +end + + +yl_speak_up.get_sorted_quest_list = function(pname) + local var_list = yl_speak_up.get_quest_variables_with_write_access(pname) + local quest_list = {} + for i, var_name in ipairs(var_list) do + local t = yl_speak_up.get_variable_metadata(var_name, "var_type") + if(t and t == "quest") then + table.insert(quest_list, var_name) + end + end + yl_speak_up.strip_pname_from_varlist(quest_list, pname) + table.sort(quest_list) + return quest_list +end diff --git a/show_fs.lua b/show_fs.lua index d9ff277..f964f5a 100644 --- a/show_fs.lua +++ b/show_fs.lua @@ -92,10 +92,14 @@ yl_speak_up.input_handler = function(player, formname, fields) elseif formname == "yl_speak_up:edit_effects" then yl_speak_up.input_fs_edit_effects(player, formname, fields) return true - -- handled in quest_api.lua + -- handled in fs_manage_variables.lua elseif formname == "yl_speak_up:manage_variables" then yl_speak_up.input_fs_manage_variables(player, formname, fields) return true + -- handled in fs_manage_quests.lua + elseif formname == "yl_speak_up:manage_quests" then + yl_speak_up.input_fs_manage_quests(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) @@ -386,6 +390,10 @@ yl_speak_up.show_fs = function(player, fs_name, param) yl_speak_up.show_fs_ver(pname, "yl_speak_up:manage_variables", yl_speak_up.get_fs_manage_variables(player, param)) + elseif(fs_name == "manage_quests") then + yl_speak_up.show_fs_ver(pname, "yl_speak_up:manage_quests", + yl_speak_up.get_fs_manage_quests(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))