diff --git a/mods/mff/README.md b/mods/mff/README.md new file mode 100644 index 00000000..c4f1a1b1 --- /dev/null +++ b/mods/mff/README.md @@ -0,0 +1,5 @@ +* MFF-specific mods folder + +** Code convention +All MFF mods must be in the `mff` global table, and thus shall depend on the mff_core mod first defining it. +The reason for this is to allow introspection using mff_introspect to track bugs at runtime, and more generally to define a code namespace. diff --git a/mods/mff/mff_core/init.lua b/mods/mff/mff_core/init.lua new file mode 100644 index 00000000..2c840174 --- /dev/null +++ b/mods/mff/mff_core/init.lua @@ -0,0 +1,2 @@ +mff = {} +mff.core = {} \ No newline at end of file diff --git a/mods/mff/mff_introspect/UPCOMING b/mods/mff/mff_introspect/UPCOMING new file mode 100644 index 00000000..e69de29b diff --git a/mods/mff/mff_quests/depends.txt b/mods/mff/mff_quests/depends.txt new file mode 100644 index 00000000..e8cef597 --- /dev/null +++ b/mods/mff/mff_quests/depends.txt @@ -0,0 +1,3 @@ +mff_core +default +quests \ No newline at end of file diff --git a/mods/mff/mff_quests/init.lua b/mods/mff/mff_quests/init.lua new file mode 100644 index 00000000..1208a8ee --- /dev/null +++ b/mods/mff/mff_quests/init.lua @@ -0,0 +1,119 @@ +--- HIGLY UNFINISHED!!!! +--- HIGLY UNFINISHED!!!! +--- HIGLY UNFINISHED!!!! +--- HIGLY UNFINISHED!!!! +--- HIGLY UNFINISHED!!!! +--- HIGLY UNFINISHED!!!! +--- HIGLY UNFINISHED!!!! +--- HIGLY UNFINISHED!!!! +--- HIGLY UNFINISHED!!!! +--- HIGLY UNFINISHED!!!! +--- HIGLY UNFINISHED!!!! +--- HIGLY UNFINISHED!!!! +-- GOT THAT ENOUGH? +-- - gravgun +mff.quests = {} +mff.QPREFIX = "mff_quests:" + +mff.quests.quests = { + testdiggydiggyhole = { + title = "Dig 10 nodes", + max = 10, + desc = "As long as you can not dig, you are not a real miner.", + periodicity = 10, + objective = { + dig = {"default:stone"} + } + } +} + +mff.quests.quest_status = {} + +function table.contains(table, element) + for _, value in pairs(table) do + if value == element then + return true + end + end + return false +end + +function mff.quests.start_quest(playername, qname, meta) + mff.quests.quest_status[playername][qname] = 0 + quests.start_quest(playername, mff.QPREFIX .. qname, meta) +end + +function mff.quests.restart_periodic_quest(playername, qname) + mff.quests.start_quest(playername, qname) +end + +function mff.quests.start_periodicity_timer(playername, qname) + local tstamp = -mff.quests.quest_status[playername][qname] + minetest.after(tstamp-os.time(), mff.quests.restart_periodic_quest, playername, qname) +end + +function mff.quests.start_all_periodicity_timers(playername) + local qstatus = mff.quests.quest_status[playername] + for qname, _ in pairs(qstatus) do + mff.quests.start_periodicity_timer(playername, qname) + end +end + +function mff.quests.set_quest_ended(playername, questname, metadata) + local qstatus = mff.quests.quest_status[playername] + local qname = questname:sub(mff.QPREFIX:len()+1) + local qinfo = mff.quests.quests[qname] + if qinfo.periodicity ~= nil and qinfo.periodicity >= 1 then + qstatus[qname] = -(os.time() + qinfo.periodicity) + mff.quests.start_periodicity_timer(playername, qname) + else + qstatus[qname] = nil + end +end + +-- Register the quests defined above +for qname, quest in pairs(mff.quests.quests) do + quests.register_quest(mff.QPREFIX .. qname, { + title = quest.title, + description = quest.desc, + max = quest.max, + autoaccept = true, + callback = mff.quests.set_quest_ended + }) +end + +-- For quests where you have to dig something, the updates happen here +minetest.register_on_dignode(function(pos, oldnode, digger) + local qstatus = mff.quests.quest_status[digger:get_player_name()] + for qname, quest in pairs(mff.quests.quests) do + if qstatus[qname] ~= nil and qstatus[qname] >= 0 then + if quest.objective.dig then + if table.contains(quest.objective.dig, oldnode.name) then + quests.update_quest(digger:get_player_name(), mff.QPREFIX .. qname, 1) + end + end + end + end +end) + +-- TODO load data +--[[ +for playername in players do + mff.quests.start_all_periodicity_timers(playername) +end +]] + +minetest.register_on_joinplayer(function (player) + -- TODO do nothing + mff.quests.quest_status[player:get_player_name()] = {} + mff.quests.start_quest(player:get_player_name(), "testdiggydiggyhole") +end) + +minetest.register_on_leaveplayer(function (player) + -- TODO do nothing + mff.quests.quest_status[player:get_player_name()] = nil +end) + +minetest.register_on_shutdown(function() + -- TODO save data +end) \ No newline at end of file diff --git a/mods/mff/modpack.txt b/mods/mff/modpack.txt new file mode 100644 index 00000000..e69de29b diff --git a/mods/quests/README b/mods/quests/README new file mode 100644 index 00000000..d9ba3c7d --- /dev/null +++ b/mods/quests/README @@ -0,0 +1,82 @@ +quests 1.1 + +quests is a simple quest framework for minetest that lets you define your own quests and handels the representation. + +Dependencies: +intllib (optional) +unified_inventory or inventory_plus (optional) +central_message (optional) + +License: WTFPL +Sounds: CC-BY +Textures: CC-BY + +------------------------------------------------------------------------------- + +You can see a full list of your active quests with the chatcommand /quests + +API: +quests.register_quest(questname,quest) +-- registers a quest for later use +-- +-- questname is the name of the quest to identify it later +-- it should follow the naming conventions: "modname:questname" +-- quest is a table in the following format +-- { +-- title, -- is shown to the player and should contain usefull information about the quest. +-- description, -- a small description of the mod. +-- max, -- is the desired maximum. If max is 1, no maximum is displayed. defaults to 1 +-- autoaccept, -- is true or false, wether the result of the quest should be dealt by this mode or the registering mod. +-- callback -- when autoaccept is true, at the end of the quest, it gets removed and callback is called. +-- -- function(playername, questname, metadata) +-- } +-- +-- returns true, when the quest was successfully registered +-- returns falls, when there was already such a quest + +quests.start_quest(playername, questname) +-- starts a quest for a specified player +-- +-- playername - the name of the player +-- questname - the name of the quest, which was registered with quests.register_quest +-- metadata - optional additional data +-- +-- returns false on failure +-- returns true if the quest was started + +quests.update_quest(playername, questname, value) +-- when something happens that has effect on a quest, a mod should call this method +-- playername is the name of the player +-- questname is the quest which gets updated +-- the quest gets updated by value +-- this method calls a previously specified callback if autoaccept is true +-- returns true if the quest is finished +-- returns false if there is no such quest or the quest continues + +quests.accept_quest(playername, questname) +-- When the mod handels the end of quests himself, e.g. you have to talk to somebody to finish the quest, +-- you have to call this method to end a quest +-- returns true, when the quest is completed +-- returns false, when the quest is still ongoing + +quests.abort_quest(playername, questname) +-- call this method, when you want to end a quest even when it was not finished +-- example: the player failed +-- +-- returns false if the quest was not aborted +-- returns true when the quest was aborted + +quests.show_hud(playername) +-- shows the hud to player playername + +quests.hide_hud(playername) +-- hides the hud for player playername + +quests.show_formspec(playername) +-- shows the player playername his/her questlog + +quests.get_metadata(playername, questname) +-- get metadata of the quest if the quest exists, else return nil + +quests.set_metadata(playername, questname, metadata) +-- set metadata of the quest diff --git a/mods/quests/central_message.lua b/mods/quests/central_message.lua new file mode 100644 index 00000000..ff3402e0 --- /dev/null +++ b/mods/quests/central_message.lua @@ -0,0 +1,12 @@ +if (cmsg) then + function quests.show_message(t, playername, text) + if (quests.hud[playername].central_message_enabled) then + local player = minetest.get_player_by_name(playername) + cmsg.push_message_player(player, text, quests.colors[t]) + minetest.sound_play("quests_" .. t, {to_player = playername}) + end + end +else + function quests.show_message(...) + end +end diff --git a/mods/quests/core.lua b/mods/quests/core.lua new file mode 100644 index 00000000..5c37cf3d --- /dev/null +++ b/mods/quests/core.lua @@ -0,0 +1,184 @@ +-- Boilerplate to support localized strings if intllib mod is installed. +local S +if minetest.get_modpath("intllib") then + S = intllib.Getter() +else + -- If you don't use insertions (@1, @2, etc) you can use this: + S = function(s) return s end +end + + +-- registers a quest for later use +-- +-- questname is the name of the quest to identify it later +-- it should follow the naming conventions: "modname:questname" +-- quest is a table in the following format +-- { +-- title, -- is shown to the player and should contain usefull information about the quest. +-- description, -- a small description of the mod. +-- max, -- is the desired maximum. If max is 1, no maximum is displayed. defaults to 1 +-- autoaccept, -- is true or false, wether the result of the quest should be dealt by this mode or the registering mod. +-- callback -- when autoaccept is true, at the end of the quest, it gets removed and callback is called. +-- -- function(playername, questname, metadata) +-- } +-- +-- returns true, when the quest was successfully registered +-- returns falls, when there was already such a quest +function quests.register_quest(questname, quest) + if (quests.registered_quests[questname] ~= nil) then + return false -- The quest was not registered since there already a quest with that name + end + quests.registered_quests[questname] = + { title = quest.title or S("missing title"), + description = quest.description or S("missing description"), + max = quest.max or 1, + autoaccept = quest.autoaccept or false, + callback = quest.callback, } + return true +end + +-- starts a quest for a specified player +-- +-- playername - the name of the player +-- questname - the name of the quest, which was registered with quests.register_quest +-- metadata - optional additional data +-- +-- returns false on failure +-- returns true if the quest was started +function quests.start_quest(playername, questname, metadata) + if (quests.registered_quests[questname] == nil) then + return false + end + if (quests.active_quests[playername] == nil) then + quests.active_quests[playername] = {} + end + if (quests.active_quests[playername][questname] ~= nil) then + return false -- the player has already this quest + end + quests.active_quests[playername][questname] = {value = 0, metadata = metadata} + + quests.update_hud(playername) + quests.show_message("new", playername, S("New quest:") .. " " .. quests.registered_quests[questname].title) + return true +end + +-- when something happens that has effect on a quest, a mod should call this method +-- playername is the name of the player +-- questname is the quest which gets updated +-- the quest gets updated by value +-- this method calls a previously specified callback if autoaccept is true +-- +-- returns true if the quest is finished +-- returns false if there is no such quest or the quest continues +function quests.update_quest(playername, questname, value) + if (quests.active_quests[playername] == nil) then + quests.active_quests[playername] = {} + end + if (quests.active_quests[playername][questname] == nil) then + return false -- there is no such quest + end + if (quests.active_quests[playername][questname].finished) then + return false -- the quest is already finished + end + if (value == nil) then + return false -- no value given + end + quests.active_quests[playername][questname]["value"] = quests.active_quests[playername][questname]["value"] + value + if (quests.active_quests[playername][questname]["value"] >= quests.registered_quests[questname]["max"]) then + quests.active_quests[playername][questname]["value"] = quests.registered_quests[questname]["max"] + if (quests.registered_quests[questname]["autoaccept"]) then + if (quests.registered_quests[questname]["callback"] ~= nil) then + quests.registered_quests[questname]["callback"](playername, questname, + quests.active_quests[playername][questname].metadata) + end + quests.accept_quest(playername,questname) + quests.update_hud(playername) + end + return true -- the quest is finished + end + quests.update_hud(playername) + return false -- the quest continues +end + +-- When the mod handels the end of quests himself, e.g. you have to talk to somebody to finish the quest, +-- you have to call this method to end a quest +-- returns true, when the quest is completed +-- returns false, when the quest is still ongoing +function quests.accept_quest(playername, questname) + if (quests.active_quests[playername][questname] and not quests.active_quests[playername][questname].finished) then + if (quests.successfull_quests[playername] == nil) then + quests.successfull_quests[playername] = {} + end + if (quests.successfull_quests[playername][questname] ~= nil) then + quests.successfull_quests[playername][questname].count = quests.successfull_quests[playername][questname].count + 1 + else + quests.successfull_quests[playername][questname] = {count = 1} + end + quests.active_quests[playername][questname].finished = true + for _,quest in ipairs(quests.hud[playername].list) do + if (quest.name == questname) then + local player = minetest.get_player_by_name(playername) + player:hud_change(quest.id, "number", quests.colors.success) + end + end + quests.show_message("success", playername, S("Quest completed:") .. " " .. quests.registered_quests[questname].title) + minetest.after(3, function(playername, questname) + quests.active_quests[playername][questname] = nil + quests.update_hud(playername) + end, playername, questname) + return true -- the quest is finished, the mod can give a reward + end + return false -- the quest hasn't finished +end + +-- call this method, when you want to end a quest even when it was not finished +-- example: the player failed +-- +-- returns false if the quest was not aborted +-- returns true when the quest was aborted +function quests.abort_quest(playername, questname) + if (questname == nil) then + return false + end + if (quests.failed_quests[playername] == nil) then + quests.failed_quests[playername] = {} + end + if (quests.active_quests[playername][questname] == nil) then + return false + end + if (quests.failed_quests[playername][questname] ~= nil) then + quests.failed_quests[playername][questname].count = quests.failed_quests[playername][questname].count + 1 + else + quests.failed_quests[playername][questname] = { count = 1 } + end + + quests.active_quests[playername][questname].finished = true + for _,quest in ipairs(quests.hud[playername].list) do + if (quest.name == questname) then + local player = minetest.get_player_by_name(playername) + player:hud_change(quest.id, "number", quests.colors.failed) + end + end + quests.show_message("failed", playername, S("Quest failed:") .. " " .. quests.registered_quests[questname].title) + minetest.after(3, function(playername, questname) + quests.active_quests[playername][questname] = nil + quests.update_hud(playername) + end, playername, questname) +end + +-- get metadata of the quest if the quest exists, else return nil +function quests.get_metadata(playername, questname) + if (quests.active_quests[playername] == nil or quests.active_quests[playername][questname] == nil) then + return nil + end + return quests.active_quests[playername][questname].metadata +end + +-- set metadata of the quest +function quests.set_metadata(playername, questname, metadata) + if (quests.active_quests[playername] == nil or quests.active_quests[playername][questname] == nil) then + return + end + quests.active_quests[playername][questname].metadata = metadata +end + diff --git a/mods/quests/depends.txt b/mods/quests/depends.txt new file mode 100644 index 00000000..0650d900 --- /dev/null +++ b/mods/quests/depends.txt @@ -0,0 +1,4 @@ +intllib? +unified_inventory? +inventory_plus? +central_message? diff --git a/mods/quests/formspecs.lua b/mods/quests/formspecs.lua new file mode 100644 index 00000000..5db7cd70 --- /dev/null +++ b/mods/quests/formspecs.lua @@ -0,0 +1,284 @@ +-- Boilerplate to support localized strings if intllib mod is installed. +local S +if minetest.get_modpath("intllib") then + S = intllib.Getter() +else + -- If you don't use insertions (@1, @2, etc) you can use this: + S = function(s) return s end +end + + +-- construct the questlog +function quests.create_formspec(playername, tab, integrated) + local queststringlist = {} + local questlist = {} + quests.formspec_lists[playername] = quests.formspec_lists[playername] or {} + quests.formspec_lists[playername].id = 1 + quests.formspec_lists[playername].list = {} + tab = tab or quests.formspec_lists[playername].tab or "1" + if (tab == "1") then + questlist = quests.active_quests[playername] or {} + elseif (tab == "2") then + questlist = quests.successfull_quests[playername] or {} + elseif (tab == "3") then + questlist = quests.failed_quests[playername] or {} + end + quests.formspec_lists[playername].tab = tab + + local no_quests = true + for questname,questspecs in pairs(questlist) do + if (questspecs.finished == nil) then + local queststring = quests.registered_quests[questname]["title"] + if (questspecs["count"] and questspecs["count"] > 1) then + queststring = queststring .. " - " .. questspecs["count"] + elseif(not questspecs["count"] and quests.registered_quests[questname]["max"] ~= 1) then + queststring = queststring .. " - (" .. quests.round(questspecs["value"], 2) .. "/" .. quests.registered_quests[questname]["max"] .. ")" + end + table.insert(queststringlist, queststring) + table.insert(quests.formspec_lists[playername].list, questname) + no_quests = false + end + end + local formspec = "" + if (not integrated) then + formspec = formspec .. "size[7,9]" + end + formspec = formspec .. "tabheader[0,0;quests_header;" .. S("Open quests") .. "," .. S("Finished quests") .. "," .. S("Failed quests") .. ";" .. tab .. "]" + if (no_quests) then + formspec = formspec .. "label[0.25,0.25;" .. S("There are no quests in this category.") .. "]" + else + formspec = formspec .. "textlist[0.25,0.25;6.5,6.5;quests_questlist;"..table.concat(queststringlist, ",") .. ";1;false]" + end + if (quests.formspec_lists[playername].tab == "1") then + formspec = formspec .."button[0.25,7;3,.7;quests_abort;" .. S("Abort quest") .. "]" + end + formspec = formspec .. "button[3.75,7;3,.7;quests_config;" .. S("Configure") .. "]".. + "button[.25,8;3,.7;quests_info;" .. S("Info") .. "]".. + "button_exit[3.75,8;3,.7;quests_exit;" .. S("Exit") .. "]" + return formspec +end + +-- construct the configuration +function quests.create_config(playername, integrated) + local formspec = "" + if (not integrated) then + formspec = formspec .. "size[7,3]" + end + formspec = formspec .. "checkbox[.25,.25;quests_config_enable;" .. S("Enable HUD") .. ";" + if(quests.hud[playername] ~= nil and quests.hud[playername].list ~= nil) then + formspec = formspec .. "true" + else + formspec = formspec .. "false" + end + formspec = formspec .. "]checkbox[.25,.75;quests_config_autohide;" .. S("Autohide HUD") .. ";" + if(quests.hud[playername] ~= nil and quests.hud[playername].autohide) then + formspec = formspec .. "true" + else + formspec = formspec .. "false" + end + formspec = formspec .. "]checkbox[.25,1.25;quests_config_central_message;" .. S("Central messages") .. ";" + if(quests.hud[playername] ~= nil and quests.hud[playername].central_message_enabled) then + formspec = formspec .. "true" + else + formspec = formspec .. "false" + end + formspec = formspec .. "]" .. + "button[.25,2.25;3,.7;quests_config_return;" .. S("Return") .. "]" + return formspec +end + +local function wordwrap(text, linelength) + local lines = text:split("\n") + local ret = "" + for i = 1,#lines do + local line = lines[i] + while (#line > linelength) do + local split = false + local j = linelength + while (not split) do + if (string.sub(line, j, j) == " ") then + split = true + ret = ret .. string.sub(line, 1, j) .. "\n" + line = string.sub(line, j + 1) + end + if (j <= 1) then + break + end + j = j - 1 + end + if (not split) then + ret = ret .. string.sub(line, 1, linelength) .. "\n" + line = string.sub(line, linelength); + end + end + ret = ret .. line .. "\n" + end + return ret +end + +-- construct the info formspec +function quests.create_info(playername, questname, integrated) + local formspec = "" + if (not integrated) then + formspec = formspec .. "size[9,6.5]" + end + formspec = formspec .. "label[0.5,0.5;" + + if (questname) then + formspec = formspec .. quests.registered_quests[questname].title .. "]" .. + "box[.4,1.5;8.2,4.5;#999999]" .. + "label[.5,1.5;" .. + wordwrap(quests.registered_quests[questname].description, 60) .. "]" + + if (quests.formspec_lists[playername].tab == "1") then + formspec = formspec .. "button[.5,6;3,.7;quests_info_abort;" .. S("Abort quest") .. "]" + end + else + formspec = formspec .. S("No quest specified.") .. "]" + end + formspec = formspec .. "button[3.25,6;3,.7;quests_info_return;" .. S("Return") .. "]" + return formspec +end + +-- show the player playername his/her questlog +function quests.show_formspec(playername) + minetest.show_formspec(playername, "quests:questlog", quests.create_formspec(playername)) +end + +-- chatcommand to see a full list of quests: +minetest.register_chatcommand("quests", { + params = "", + description = S("Show all open quests"), + func = function(name, param) + minetest.show_formspec(name, "quests:questlog", quests.create_formspec(name)) + return true + end +}) + +-- Handle the return fields of the questlog +minetest.register_on_player_receive_fields(function(player, formname, fields) + if (player == nil) then + return + end + local playername = player:get_player_name(); + if (playername == "") then + return + end + +-- questlog + if (fields["quests_header"]) then + if (formname == "quests:questlog") then + minetest.show_formspec(playername, "quests:questlog", quests.create_formspec(playername, fields["quests_header"])) + else + if (fields["quests_header"] == "1") then + unified_inventory.set_inventory_formspec(player, "quests") + elseif (fields["quests_header"] == "2") then + unified_inventory.set_inventory_formspec(player, "quests_successfull") + return + else + unified_inventory.set_inventory_formspec(player, "quests_failed") + return + end + end + return + end + if (fields["quests_questlist"]) then + local event = minetest.explode_textlist_event(fields["quests_questlist"]) + if (event.type == "CHG") then + quests.formspec_lists[playername].id = event.index + end + end + if (fields["quests_abort"]) then + if (quests.formspec_lists[playername].id == nil) then + return + end + quests.abort_quest(playername, quests.formspec_lists[playername]["list"][quests.formspec_lists[playername].id]) + if (formname == "quests:questlog") then + minetest.show_formspec(playername, "quests:questlog", quests.create_formspec(playername)) + else + unified_inventory.set_inventory_formspec(player, "quests") + end + end + if (fields["quests_config"]) then + if (formname == "quests:questlog") then + minetest.show_formspec(playername, "quests:config", quests.create_config(playername)) + else + unified_inventory.set_inventory_formspec(player, "quests_config") + end + end + if (fields["quests_info"]) then + if (formname == "quests:questlog") then + minetest.show_formspec(playername, "quests:info", quests.create_info(playername, quests.formspec_lists[playername].list[quests.formspec_lists[playername].id])) + else + unified_inventory.set_inventory_formspec(player, "quests_info") + end + end + +-- config + if (fields["quests_config_enable"]) then + quests.hud[playername].autohide = false + if (fields["quests_config_enable"] == "true") then + quests.show_hud(playername) + else + quests.hide_hud(playername) + end + if (formname == "quests:config") then + minetest.show_formspec(playername, "quests:config", quests.create_config(playername)) + else + unified_inventory.set_inventory_formspec(player, "quests_config") + end + end + if (fields["quests_config_autohide"]) then + if (fields["quests_config_autohide"] == "true") then + quests.hud[playername].autohide = true + quests.update_hud(playername) + else + quests.hud[playername].autohide = false + end + if (formname == "quests:config") then + minetest.show_formspec(playername, "quests:config", quests.create_config(playername)) + else + unified_inventory.set_inventory_formspec(player, "quests_config") + end + end + if (fields["quests_config_central_message"]) then + if (fields["quests_config_central_message"] == "true") then + quests.hud[playername].central_message_enabled = true + else + quests.hud[playername].central_message_enabled = false + end + if (formname == "quests:config") then + minetest.show_formspec(playername, "quests:config", quests.create_config(playername)) + else + unified_inventory.set_inventory_formspec(player, "quests_config") + end + end + + if (fields["quests_config_return"]) then + if (formname == "quests:config") then + minetest.show_formspec(playername, "quests:questlog", quests.create_formspec(playername)) + else + unified_inventory.set_inventory_formspec(player, "quests") + end + end + +-- info + if (fields["quests_info_abort"]) then + if (quests.formspec_lists[playername].id == nil) then + return + end + quests.abort_quest(playername, quests.formspec_lists[playername]["list"][quests.formspec_lists[playername].id]) + if (formname == "quests:info") then + minetest.show_formspec(playername, "quests:questlog", quests.create_formspec(playername)) + else + unified_inventory.set_inventory_formspec(player, "quests") + end + end + if (fields["quests_info_return"]) then + if (formname == "quests:info") then + minetest.show_formspec(playername, "quests:questlog", quests.create_formspec(playername)) + else + unified_inventory.set_inventory_formspec(player, "quests") + end + end +end) diff --git a/mods/quests/hud.lua b/mods/quests/hud.lua new file mode 100644 index 00000000..527186bd --- /dev/null +++ b/mods/quests/hud.lua @@ -0,0 +1,233 @@ +-- Boilerplate to support localized strings if intllib mod is installed. +local S +if minetest.get_modpath("intllib") then + S = intllib.Getter() +else + -- If you don't use insertions (@1, @2, etc) you can use this: + S = function(s) return s end +end + +local show_max = 10 -- the maximum visible quests. + +local hud_config = { position = {x = 1, y = 0.2}, + offset = { x = -200, y = 0}, + number = quests.colors.new } + +-- call this function to enable the HUD for the player that shows his quests +-- the HUD can only show up to show_max quests +function quests.show_hud(playername, autohide) + if (quests.hud[playername] == nil) then + quests.hud[playername] = { autohide = autohide} + end + if (quests.hud[playername].list ~= nil) then + return + end + local hud = { + hud_elem_type = "text", + alignment = {x=1, y=1}, + position = {x = hud_config.position.x, y = hud_config.position.y}, + offset = {x = hud_config.offset.x, y = hud_config.offset.y}, + number = hud_config.number, + text = S("Quests:") } + + + + local player = minetest.get_player_by_name(playername) + if (player == nil) then + return false + end + quests.hud[playername].list = {} + table.insert(quests.hud[playername].list, { value=0, id=player:hud_add(hud) }) + minetest.after(0, quests.update_hud, playername) +end + +-- call this method to hide the hud +function quests.hide_hud(playername) + local player = minetest.get_player_by_name(playername) + if (player == nil or quests.hud[playername] == nil or quests.hud[playername].list == nil) then + return + end + for _,quest in pairs(quests.hud[playername].list) do + player:hud_remove(quest.id) + if (quest.id_background ~= nil) then + player:hud_remove(quest.id_background) + end + if (quest.id_bar ~= nil) then + player:hud_remove(quest.id_bar) + end + end + quests.hud[playername].list = nil +end + + +local function get_quest_hud_string(questname, quest) + local quest_string = quests.registered_quests[questname].title + if (quests.registered_quests[questname].max ~= 1) then + quest_string = quest_string .. "\n ("..quests.round(quest.value, 2).."/"..quests.registered_quests[questname].max..")" + end + return quest_string +end + +-- only for internal use +-- updates the hud +function quests.update_hud(playername) + if (quests.hud[playername] == nil or quests.active_quests[playername] == nil) then + return + end + if (quests.hud[playername].list == nil) then + if (quests.hud[playername].autohide and next(quests.active_quests[playername]) ~= nil) then + quests.show_hud(playername) + end + return + end + local player = minetest.get_player_by_name(playername) + if (player == nil) then + return + end + + -- Check for changes in the hud + local i = 2 -- the first element is the title + local change = false + local visible = {} + local remove = {} + for j,hud_element in ipairs(quests.hud[playername].list) do + if (hud_element.name ~= nil) then + if (quests.active_quests[playername][hud_element.name] ~= nil) then + if (hud_element.value ~= quests.active_quests[playername][hud_element.name].value) then + hud_element.value = quests.active_quests[playername][hud_element.name].value + player:hud_change(hud_element.id, "text", get_quest_hud_string(hud_element.name, quests.active_quests[playername][hud_element.name])) + if (hud_element.id_bar ~= nil) then + player:hud_change(hud_element.id_bar, "scale", + { x = math.floor(20 * hud_element.value / quests.registered_quests[hud_element.name].max), + y = 1}) + end + end + if (i ~= j) then + player:hud_change(hud_element.id, "offset", { x= hud_config.offset.x, y=hud_config.offset.y + (i-1) *40}) + if (hud_element.id_background ~= nil) then + player:hud_change(hud_element.id_background, "offset", { x= hud_config.offset.x, y=hud_config.offset.y + (i-1) *40 + 22}) + end + if (hud_element.id_bar ~= nil) then + player:hud_change(hud_element.id_bar, "offset", { x= hud_config.offset.x + 2, y=hud_config.offset.y + (i-1) *40 + 24}) + end + + end + visible[hud_element.name] = true + i = i + 1 + else + player:hud_remove(hud_element.id) + if (hud_element.id_background ~= nil) then + player:hud_remove(hud_element.id_background) + end + if (hud_element.id_bar ~= nil) then + player:hud_remove(hud_element.id_bar) + end + table.insert(remove, j) + end + end + end + --remove ended quests + if (remove[1] ~= nil) then + for _,j in ipairs(remove) do + table.remove(quests.hud[playername].list, j) + i = i - 1 + end + end + + if (i >= show_max + 1) then + return + end + -- add new quests + local counter = i - 1 + for questname,questspecs in pairs(quests.active_quests[playername]) do + if (not visible[questname]) then + local id = player:hud_add({ hud_elem_type = "text", + alignment = { x=1, y= 1 }, + position = {x = hud_config.position.x, y = hud_config.position.y}, + offset = {x = hud_config.offset.x, y = hud_config.offset.y + counter * 40}, + number = hud_config.number, + text = get_quest_hud_string(questname, questspecs) }) + local id_background + local id_bar + if (quests.registered_quests[questname].max ~= 1) then + id_background = player:hud_add({ hud_elem_type = "image", + scale = { x = 1, y = 1 }, + size = { x = 2, y = 4 }, + alignment = { x = 1, y = 1 }, + position = { x = hud_config.position.x, y = hud_config.position.y }, + offset = { x = hud_config.offset.x, y = hud_config.offset.y + counter * 40 + 22 }, + text = "quests_questbar_background.png" }) + id_bar = player:hud_add({hud_elem_type = "image", + scale = { x = math.floor(20 * questspecs.value / quests.registered_quests[questname].max), + y = 1 }, + alignment = { x = 1, y = 1 }, + position = { x = hud_config.position.x, y = hud_config.position.y }, + offset = { x = hud_config.offset.x + 2, y = hud_config.offset.y + counter * 40 + 24 }, + text = "quests_questbar.png" }) + end + + table.insert(quests.hud[playername].list, { name = questname, + id = id, + id_background = id_background, + id_bar = id_bar, + value = questspecs.value }) + counter = counter + 1 + if (counter >= show_max + 1) then + break + end + end + end + + if (quests.hud[playername].autohide) then + if (next(quests.active_quests[playername]) == nil) then + player:hud_change(quests.hud[playername].list[1].id, "text", S("No more Quests")) + minetest.after(3, function(playername) + if (next(quests.active_quests[playername]) ~= nil) then + player:hud_change(quests.hud[playername].list[1].id, "text", S("Quests:")) + else + quests.hide_hud(playername) + end + end, playername) + end + end +end + + + +-- show the HUDs +--for playername,id in pairs(quests.hud) do +-- if (id ~= nil) then +-- quests.hud[playername] = nil +-- minetest.after(10, function(playername) +-- quests.show_hud(playername) +-- quests.update_hud(playername) +-- end, playername) +-- end +--end + +minetest.register_on_joinplayer(function(player) + local playername = player:get_player_name() + if (quests.hud[playername] ~= nil) then + if (not(quests.hud[playername].first)) then + return + end + local list = quests.hud[playername].list + local autohide = quests.hud[playername].autohide + local central_message_enabled = quests.hud[playername].central_message_enabled + quests.hud[playername] = { + autohide = autohide, + central_message_enabled = central_message_enabled + } + if (list ~= nil) then + minetest.after(1, function(playername) + quests.show_hud(playername) + end, playername) + end + else -- new player + quests.hud[playername] = { + autohide = true, + central_message_enabled = true + } + quests.active_quests[playername] = {} + end +end) diff --git a/mods/quests/init.lua b/mods/quests/init.lua new file mode 100644 index 00000000..8bd4eff1 --- /dev/null +++ b/mods/quests/init.lua @@ -0,0 +1,66 @@ +-- reading previous quests +local file = io.open(minetest.get_worldpath().."/quests", "r") +if file then + minetest.log("action", "Reading quests...") + quests = minetest.deserialize(file:read("*all")) + file:close() +end +quests = quests or {} +quests.registered_quests = {} +quests.active_quests = quests.active_quests or {} +quests.successfull_quests = quests.successfull_quests or {} +quests.failed_quests = quests.failed_quests or {} +quests.hud = quests.hud or {} +for idx,_ in pairs(quests.hud) do + quests.hud[idx].first = true +end + + +quests.formspec_lists = {} +function quests.round(num, n) + local mult = 10^(n or 0) + return math.floor(num * mult + .5) / mult +end + +quests.colors = { + new = "0xAAAA00", + success = "0x00AD00", + failed = "0xAD0000" +} + + +local MP = minetest.get_modpath("quests") + +dofile(MP .. "/central_message.lua") +dofile(MP .. "/core.lua") +dofile(MP .. "/hud.lua") +dofile(MP .. "/formspecs.lua") + +-- support for unified_inventory +if (minetest.get_modpath("unified_inventory") ~= nil) then + dofile(minetest.get_modpath("quests") .. "/unified_inventory.lua") +elseif (minetest.get_modpath("inventory_plus") ~= nil) then + dofile(minetest.get_modpath("quests") .. "/inventory_plus.lua") +end + + +-- write the quests to file +minetest.register_on_shutdown(function() + minetest.log("action", "Writing quests to file") + for playername, quest in pairs(quests.active_quests) do + for questname, questspecs in pairs(quest) do + if (questspecs.finished) then + quests.active_quests[playername][questname] = nil -- make sure no finished quests are saved as unfinished + end + end + end + local file = io.open(minetest.get_worldpath().."/quests", "w") + if (file) then + file:write(minetest.serialize({ --registered_quests = quests.registered_quests, + active_quests = quests.active_quests, + successfull_quests = quests.successfull_quests, + failed_quests = quests.failed_quests, + hud = quests.hud})) + file:close() + end +end) diff --git a/mods/quests/inventory_plus.lua b/mods/quests/inventory_plus.lua new file mode 100644 index 00000000..24b65883 --- /dev/null +++ b/mods/quests/inventory_plus.lua @@ -0,0 +1,9 @@ +minetest.register_on_joinplayer(function(player) + inventory_plus.register_button(player, "quests") +end) + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if (fields.quests) then + quests.show_formspec(player:get_player_name()) + end +end) diff --git a/mods/quests/locale/de.txt b/mods/quests/locale/de.txt new file mode 100644 index 00000000..10ca0d57 --- /dev/null +++ b/mods/quests/locale/de.txt @@ -0,0 +1,20 @@ +missing description = fehlende Beschreibung +missing title = fehlender Titel +Quests: = Quests: +No more Quests = Keine weiteren Quests +Abort quest = Quest abbrechen +Configure = Konfigurieren +Enable HUD = HUD einschalten +Autohide HUD = HUD automatisch verstecken +Exit = Verlassen +Failed quests = Gescheiterte Quests +Finished quests = Beendete Quests +Info = Info +No quest specified. = Keine Quest ausgewählt. +Open quests = Offene Quests +Return = Zurück +Show all open quests = Zeige alle offenen Quests +There are no quests in this category. = Es gibt keine Quests in dieser Kategorie. +New quest: = Neue Quest: +Quest completed: = Quest beendet: +Quest failed: = Quest gescheitert: diff --git a/mods/quests/locale/template.txt b/mods/quests/locale/template.txt new file mode 100644 index 00000000..76e1b77b --- /dev/null +++ b/mods/quests/locale/template.txt @@ -0,0 +1,20 @@ +missing description = +missing title = +Quests: = +No more Quests = +Abort quest = +Configure = +Enable HUD = +Autohide HUD = +Exit = +Failed quests = +Finished quests = +Info = +No quest specified. = +Open quests = +Return = +Show all open quests = +There are no quests in this category. = +New quest: = +Quest completed: = +Quest failed: = diff --git a/mods/quests/sounds/quests_failed.ogg b/mods/quests/sounds/quests_failed.ogg new file mode 100644 index 00000000..ce2970d5 Binary files /dev/null and b/mods/quests/sounds/quests_failed.ogg differ diff --git a/mods/quests/sounds/quests_new.ogg b/mods/quests/sounds/quests_new.ogg new file mode 100644 index 00000000..c71a67d6 Binary files /dev/null and b/mods/quests/sounds/quests_new.ogg differ diff --git a/mods/quests/sounds/quests_success.ogg b/mods/quests/sounds/quests_success.ogg new file mode 100644 index 00000000..270972fe Binary files /dev/null and b/mods/quests/sounds/quests_success.ogg differ diff --git a/mods/quests/textures/inventory_plus_quests.png b/mods/quests/textures/inventory_plus_quests.png new file mode 100644 index 00000000..05363b5a Binary files /dev/null and b/mods/quests/textures/inventory_plus_quests.png differ diff --git a/mods/quests/textures/quests_questbar.png b/mods/quests/textures/quests_questbar.png new file mode 100644 index 00000000..bc63aaa5 Binary files /dev/null and b/mods/quests/textures/quests_questbar.png differ diff --git a/mods/quests/textures/quests_questbar_background.png b/mods/quests/textures/quests_questbar_background.png new file mode 100644 index 00000000..ef980c9b Binary files /dev/null and b/mods/quests/textures/quests_questbar_background.png differ diff --git a/mods/quests/unified_inventory.lua b/mods/quests/unified_inventory.lua new file mode 100644 index 00000000..4855533d --- /dev/null +++ b/mods/quests/unified_inventory.lua @@ -0,0 +1,47 @@ +unified_inventory.register_button("quests", { + type = "image", + image = "inventory_plus_quests.png", + tooltip = "Show the questlog", +-- action = function(player) +-- quests.show_formspec(player:get_player_name()) +-- end +}) + +unified_inventory.register_page("quests", { + get_formspec = function(player) + local playername = player:get_player_name() + local formspec = quests.create_formspec(playername, "1", true) + return {formspec = formspec, draw_inventory=false} + end +}) + +unified_inventory.register_page("quests_successfull", { + get_formspec = function(player) + local playername = player:get_player_name() + local formspec = quests.create_formspec(playername, "2", true) + return {formspec = formspec, draw_inventory=false} + end +}) + +unified_inventory.register_page("quests_failed", { + get_formspec = function(player) + local playername = player:get_player_name() + local formspec = quests.create_formspec(playername, "3", true) + return {formspec = formspec, draw_inventory=false} + end +}) + +unified_inventory.register_page("quests_config", { + get_formspec = function(player) + local playername = player:get_player_name() + local formspec = quests.create_config(playername, true) + return {formspec = formspec, draw_inventory = false } + end +}) +unified_inventory.register_page("quests_info", { + get_formspec = function(player) + local playername = player:get_player_name() + local formspec = quests.create_info(playername, quests.formspec_lists[playername].list[quests.formspec_lists[playername].id], true) + return {formspec = formspec, draw_inventory = false } + end +})