From 41ff6cecee3f0ee63417cd9709220ed381e8e747 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sun, 10 Jul 2016 04:08:24 +0100 Subject: [PATCH 1/4] Cleanup in story mod --- mods/story/init.lua | 102 ++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 61 deletions(-) diff --git a/mods/story/init.lua b/mods/story/init.lua index 38d3502..eb8e6b1 100644 --- a/mods/story/init.lua +++ b/mods/story/init.lua @@ -1,38 +1,19 @@ story = {} --- string -function string:split(inSplitPattern, outResults) - if not outResults then - outResults = {} - end - local theStart = 1 - local theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart ) - while theSplitStart do - table.insert( outResults, string.sub( self, theStart, theSplitStart-1 ) ) - theStart = theSplitEnd + 1 - theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart ) - end - table.insert( outResults, string.sub( self, theStart ) ) - return outResults -end - -- form -story.talk_form = "size[8,7.5;]" -story.talk_form = story.talk_form..default.gui_colors -story.talk_form = story.talk_form..default.gui_bg ---story.talk_form = story.talk_form.."image[0,0.0;3,8;story_player.png]" -story.talk_form = story.talk_form.."label[0,0;%s]" +story.talk_form = "size[8,7.5;]" .. + default.gui_colors .. default.gui_bg .. "label[0,0;%s]" --story.talk_form = story.talk_form.."image[6,0.0;3,8;story_character_1.png]" -story.get_talk_form = function(text) +function story.get_talk_form(text) return string.format(story.talk_form, text) end function story.show_dialog(player, text) local lines = text:split("\n") for i,t in ipairs(lines) do - minetest.after((i-1)*2.3, function(player, t) + minetest.after((i-1)*2.3, function(player, t) cmsg.push_message_player(player, t) end, player,t) end @@ -64,11 +45,12 @@ end) -- generator -story.generator = {} -story.generator.parts = {} -story.generator.dialogs = {} -story.generator.players_stories = {} -story.generator.file = minetest.get_worldpath() .. "/story" +story.generator = { + parts = {}, + dialogs = {}, + players_stories = {}, + file = minetest.get_worldpath() .. "/story" +} function story.generator.load_stories() local input = io.open(story.generator.file, "r") @@ -205,7 +187,7 @@ function story.generator.get_quest(player) return nil end -quests.callback = function (player) +quests.callback = function(player) print("[quest] done") if (story.generator.players_stories[player:get_player_name()].wait_for and story.generator.players_stories[player:get_player_name()].wait_for == "quest") then @@ -224,23 +206,24 @@ function story.generator.run(part, player, line_pos) for k,v in pairs(lines) do if i > line_pos-1 then local cmd = v:split(" ") - if cmd[1] then + local operator = cmd[1] + if operator then print("[INFO] run line... " .. v) - if cmd[1] == "$dialog" and cmd[2] then + if operator == "$dialog" and cmd[2] then if story.generator.get_dialog(cmd[2], player) then story.generator.players_stories[player:get_player_name()].text = story.generator.get_dialog(cmd[2], player) end - end - if cmd[1] == "$create" then + elseif operator == "$create" then story.generator.show(player, story.generator.players_stories[player:get_player_name()].pos) - end - if cmd[1] == "$place" and cmd[2] and cmd[3] then - if cmd[2] == "at" then - if places.pos[cmd[3]] then - story.generator.players_stories[player:get_player_name()].pos = places.pos[cmd[3]] + elseif operator == "$place" and cmd[2] and cmd[3] then + local place_mode = cmd[2] + local at = cmd[3] + if place_mode == "at" then + if places.pos[at] then + story.generator.players_stories[player:get_player_name()].pos = places.pos[at] end - elseif cmd[2] == "near" then - if cmd[3] == "player" then + elseif place_mode == "near" then + if place_mode == "player" then if cmd[4] then local place = minetest:get_player_by_name(cmd[4]):getpos() story.generator.players_stories[player:get_player_name()].pos = {x=place.x+math.random(-5, 5), y=place.y, z=place.z+math.random(-5, 5)} @@ -248,30 +231,31 @@ function story.generator.run(part, player, line_pos) local place = player:getpos() story.generator.players_stories[player:get_player_name()].pos = {x=place.x+math.random(-5, 5), y=place.y, z=place.z+math.random(-5, 5)} end - elseif places.pos[cmd[3]] then - local place = places.pos[cmd[3]] + elseif places.pos[place_mode] then + local place = places.pos[place_mode] story.generator.players_stories[player:get_player_name()].pos = {x=place.x+math.random(-5, 5), y=place.y, z=place.z+math.random(-5, 5)} end else - if places.pos[cmd[3]] then + if places.pos[place_mode] then story.generator.players_stories[player:get_player_name()].pos = places.pos[cmd[3]] end end - end - if cmd[1] == "$quest" and cmd[2] and cmd[3] and cmd[4] and cmd[5] and tonumber(cmd[4]) and tonumber(cmd[5]) then + elseif operator == "$quest" and cmd[2] and cmd[3] and cmd[4] and cmd[5] and tonumber(cmd[4]) and tonumber(cmd[5]) then + local type = cmd[2] + local node = cmd[3] + local max = tonumber(cmd[4]) + local xp = tonumber(cmd[5]) local q_id = quests.add_quest(player:get_player_name(), { - quest_type = cmd[2], - node = cmd[3], + quest_type = type, + node = node, progress = 0, done = false, - max = tonumber(cmd[4]), - xp = tonumber(cmd[5]) + max = max, + xp = xp }) - end - if cmd[1] == "$pos" then + elseif operator == "$pos" then story.generator.players_stories[player:get_player_name()].pos = {x=0,y=10,z=0} - end - if cmd[1] == "$next" and cmd[2] then + elseif operator == "$next" and cmd[2] then if cmd[2] == "rnd" then if cmd[3] and cmd[4] and cmd[5] then local rnd = math.random(3) @@ -296,20 +280,16 @@ function story.generator.run(part, player, line_pos) out = {part=cmd[2], wait=false} end return out - end - if cmd[1] == "$wait" then + elseif operator == "$wait" then return {cmd="$wait", param=i, wait=true, param2 = cmd[2] or "talk"} - end - if cmd[1] == "$spawn" and cmd[2] and cmd[3] then + elseif operator == "$spawn" and cmd[2] and cmd[3] then if places.pos[cmd[3]] then minetest.add_entity(places.pos[cmd[3]], cmd[2]) end - end - if cmd[1] == "$quit" then + elseif operator == "$quit" then out = {part="", wait=false, quit=true} return out - end - if cmd[1] == "$give" and cmd[2] and cmd[3] then + elseif operator == "$give" and cmd[2] and cmd[3] then player:get_inventory():add_item("main", cmd[2].. " " .. cmd[3]) end end From 7d282c7bf802607806093423f88381139b20928e Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sun, 10 Jul 2016 02:57:03 +0100 Subject: [PATCH 2/4] Cleanup quests mod --- mods/quests/init.lua | 56 +++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/mods/quests/init.lua b/mods/quests/init.lua index 2654ee6..81a4527 100644 --- a/mods/quests/init.lua +++ b/mods/quests/init.lua @@ -5,7 +5,7 @@ quests.player_quests = {} quests.file = minetest.get_worldpath() .. "/quests" quests.callback = nil -function quests.load_quests() +function quests.load() local input = io.open(quests.file, "r") if input then local str = input:read("*all") @@ -13,7 +13,7 @@ function quests.load_quests() if minetest.deserialize(str) then quests.player_quests = minetest.deserialize(str) end - else + else print("[WARNING] quest file is empty") end io.close(input) @@ -22,7 +22,7 @@ function quests.load_quests() end end -function quests.save_quests() +function quests.save() if quests.player_quests then local output = io.open(quests.file, "w") local str = minetest.serialize(quests.player_quests) @@ -37,14 +37,18 @@ function quests.add_quest(player, quest) end print("[quests] add quest") table.insert(quests.player_quests[player], quest) - quests.save_quests() + quests.save() return #quests.player_quests[player] end -quests.show_quests_form = "size[8,7.5;]" -quests.show_quests_form = quests.show_quests_form..default.gui_colors -quests.show_quests_form = quests.show_quests_form..default.gui_bg -quests.show_quests_form = quests.show_quests_form.."label[0,0;%s]" +function quests.finish_quest(player, quest) + xp.add_xp(digger, quest.xp) + quest.done = true + quests.callback(player) +end + +quests.show_quests_form = "size[8,7.5;]" .. default.gui_colors .. + default.gui_bg .. "label[0,0;%s]" minetest.register_chatcommand("quests", { params = "", @@ -59,7 +63,7 @@ minetest.register_chatcommand("quests", { end local s = quests.show_quests_form local txt = "" - for k,v in pairs(quests.player_quests[name]) do + for k, v in pairs(quests.player_quests[name]) do txt = txt .. " -> " .. v.quest_type .. " " .. v.node .. " (" .. tostring(v.progress) .. "/" .. tostring(v.max) .. ")\n" end s = string.format(s, txt) @@ -69,42 +73,36 @@ minetest.register_chatcommand("quests", { }) minetest.register_on_dignode(function(pos, oldnode, digger) - if not digger or not digger:is_player() then - return - end - if not quests.player_quests[digger:get_player_name()] then + if not digger or not digger:is_player() or + not quests.player_quests[digger:get_player_name()] then return end + table.foreach(quests.player_quests[digger:get_player_name()], function(k, v) print("[quests] run quest " .. v.quest_type .. ", " .. v.node) if v.quest_type == "dignode" and oldnode.name == v.node then v.progress = v.progress + 1 - if v.progress > (v.max-1) and v.done == false then - xp.add_xp(digger, v.xp) - v.done = true - quests.callback(digger) + if v.progress > (v.max-1) and not v.done then + quests.finish_quest(digger, v) end - quests.save_quests() + quests.save() end end) end) minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing) - if not placer or not placer:is_player() then - return - end - if not quests.player_quests[placer:get_player_name()] then + if not placer or not placer:is_player() or + not quests.player_quests[placer:get_player_name()] then return end + table.foreach(quests.player_quests[placer:get_player_name()], function(k, v) if v.quest_type == "placenode" and newnode.name == v.node then v.progress = v.progress + 1 - if v.progress > (v.max-1) and v.done == false then - xp.add_xp(placer, v.xp) - v.done = true - quests.callback(placer) + if v.progress > (v.max-1) and not v.done then + quests.finish_quest(placer, v) end - quests.save_quests() + quests.save() end end) end) @@ -113,12 +111,12 @@ minetest.register_on_newplayer(function(player) quests.player_quests[player:get_player_name()] = {} end) -quests.load_quests() +quests.load() -- exploring minetest.register_node("quests:map", { description = "Map", - tiles = {"quests_map_top.png", "quests_map_top.png", "quests_map.png", "quests_map.png", "quests_map.png", "quests_map.png"}, + tiles = {"quests_map_top.png", "quests_map_top.png", "quests_map.png", "quests_map.png", "quests_map.png", "quests_map.png"}, groups = {quest = 1, cracky = 3}, on_punch = function(pos, node, player, pointed_thing) xp.add_xp(player, math.random(3, 30)) From c50fdcc4d0126b3335d23a43130abb39e99c6d6e Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sun, 10 Jul 2016 04:02:26 +0100 Subject: [PATCH 3/4] Add quest goals --- mods/quests/README.md | 37 ++++++++++++ mods/quests/init.lua | 137 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 149 insertions(+), 25 deletions(-) create mode 100644 mods/quests/README.md diff --git a/mods/quests/README.md b/mods/quests/README.md new file mode 100644 index 0000000..33ebeff --- /dev/null +++ b/mods/quests/README.md @@ -0,0 +1,37 @@ +# Quests + +Mod to manage tasks. + +Todo: + +* Add support for more goals than digging and placing +* Multiple goal.requires +* Call backs on quest completion + +## Creating quests + +Here is a basic quest: + +```lua +local quest = quests.new(name, "Preparing a small feast") +quests.add_dig_goal(quest, "Harvest wheat", "farming:wheat_8", 5) +quests.add_dig_goal(quest, "Harvest apples", "default:apple", 3) +quests.add_quest(name, quest) +``` + +This will show up in the quest menu as: + + -> Preparing a small feast + [ ] Harvest wheat (0/5) + [ ] Harvest apples (0/3) + +For longer quests, certain goals need to be done in an order. +To do this, use goal.require: + +```lua +local quest = quests.new(name, "Breaking Bread") +local g1 = quests.add_dig_goal(quest, "Harvest wheat", "treasure:raregem", 1) +local g2 = quests.add_give_goal(quest, "Return to Bob the Farmer", bob, "treasure:raregem", 1) +g2.requires = g1 +quest.next = quest2 +``` diff --git a/mods/quests/init.lua b/mods/quests/init.lua index 81a4527..b5c907b 100644 --- a/mods/quests/init.lua +++ b/mods/quests/init.lua @@ -42,31 +42,128 @@ function quests.add_quest(player, quest) end function quests.finish_quest(player, quest) - xp.add_xp(digger, quest.xp) + xp.add_xp(minetest.get_player_by_name(player), quest.xp) quest.done = true - quests.callback(player) + if quests.callback then + quests.callback(player) + end +end + +function quests.finish_goal(player, quest, goal) + goal.done = true + if not quest.done then + local all_done = true + for i = 1, #quest.goals do + if not quest.goals[i].done then + all_done = false + break + end + end + if all_done then + quests.finish_quest(player, quest) + end + end + quests.save() +end + +function quests.new(player, title) + local quest = { + title = title, + done = false, + goals = {}, + xp = 0 + } + + return quest +end + +function quests.add_dig_goal(quest, title, node, number) + local goal = { + title = title, + type = "dig", + node = node, + max = number, + progress = 0, + done = false + } + table.insert(quest.goals, goal) + return goal +end + +function quests.add_place_goal(quest, title, node, number) + local goal = { + title = title, + type = "placenode", + node = node, + max = number, + progress = 0, + done = false + } + table.insert(quest.goals, goal) + return goal +end + +function quests.process_node_count_goals(player, type, node) + local player_quests = quests.player_quests[player] + table.foreach(player_quests, function(_, quest) + table.foreach(quest.goals, function(_, goal) + if (not goal.requires or goal.requires.done) and + goal.type == type and goal.node == node then + goal.progress = goal.progress + 1 + if goal.progress >= goal.max then + goal.progress = goal.max + if goal.done then + quests.finish_goal(player, quest, goal) + end + end + quests.save() + end + end) + end) end quests.show_quests_form = "size[8,7.5;]" .. default.gui_colors .. default.gui_bg .. "label[0,0;%s]" +function quests.format_goal(player, quest, goal) + -- TODO: support formatting for more than just digging and placing + if goal.done then + return " [x] " .. goal.title .. " (" .. tostring(goal.progress) .. + "/" .. tostring(goal.max) .. ")\n" + else + return " [ ] " .. goal.title .. " (" .. tostring(goal.progress) .. + "/" .. tostring(goal.max) .. ")\n" + end +end + minetest.register_chatcommand("quests", { params = "", description = "Shows your quests", privs = {}, func = function(name, text) - if not quests.player_quests[name] then + local player_quests = quests.player_quests[name] + if not player_quests or #player_quests == 0 then local s = quests.show_quests_form s = string.format(s, "You have not got any quests yet.") minetest.show_formspec(name, "quests:show_quests", s) return end + local s = quests.show_quests_form local txt = "" - for k, v in pairs(quests.player_quests[name]) do - txt = txt .. " -> " .. v.quest_type .. " " .. v.node .. " (" .. tostring(v.progress) .. "/" .. tostring(v.max) .. ")\n" + for _, quest in pairs(player_quests) do + if quest.done then + txt = txt .. " -> " .. quest.title .. " (Completed)\n" + else + txt = txt .. " -> " .. quest.title .. "\n" + for _, goal in pairs(quest.goals) do + if not goal.requires or goal.requires.done then + txt = txt .. quests.format_goal(name, quest, goal) + end + end + end end - s = string.format(s, txt) + s = string.format(s, minetest.formspec_escape(txt)) minetest.show_formspec(name, "quests:show_quests", s) return true, "" end, @@ -78,16 +175,7 @@ minetest.register_on_dignode(function(pos, oldnode, digger) return end - table.foreach(quests.player_quests[digger:get_player_name()], function(k, v) - print("[quests] run quest " .. v.quest_type .. ", " .. v.node) - if v.quest_type == "dignode" and oldnode.name == v.node then - v.progress = v.progress + 1 - if v.progress > (v.max-1) and not v.done then - quests.finish_quest(digger, v) - end - quests.save() - end - end) + quests.process_node_count_goals(digger:get_player_name(), "dig", oldnode.name) end) minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing) @@ -96,19 +184,18 @@ minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack return end - table.foreach(quests.player_quests[placer:get_player_name()], function(k, v) - if v.quest_type == "placenode" and newnode.name == v.node then - v.progress = v.progress + 1 - if v.progress > (v.max-1) and not v.done then - quests.finish_quest(placer, v) - end - quests.save() - end - end) + quests.process_node_count_goals(placer:get_player_name(), "placenode", newnode.name) end) minetest.register_on_newplayer(function(player) quests.player_quests[player:get_player_name()] = {} + + local name = player:get_player_name() + local quest = quests.new(name, "Quest 1") + local q1 = quests.add_dig_goal(quest, "Harvest dirt", "default:dirt", 5) + local q2 = quests.add_dig_goal(quest, "Harvest sand", "default:sand", 5) + q2.requires = q1 + quests.add_quest(name, quest) end) quests.load() From f31d0421da4de9e7d989e93bf06108884760c10d Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sun, 10 Jul 2016 04:21:21 +0100 Subject: [PATCH 4/4] Update story to use the new quests API --- mods/story/init.lua | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/mods/story/init.lua b/mods/story/init.lua index eb8e6b1..b82a26b 100644 --- a/mods/story/init.lua +++ b/mods/story/init.lua @@ -245,14 +245,17 @@ function story.generator.run(part, player, line_pos) local node = cmd[3] local max = tonumber(cmd[4]) local xp = tonumber(cmd[5]) - local q_id = quests.add_quest(player:get_player_name(), { - quest_type = type, - node = node, - progress = 0, - done = false, - max = max, - xp = xp - }) + + local quest = quests.new(player:get_player_name(), "Story") + quest.xp = xp + if type == "dignode" then + quests.add_dig_goal(quest, type .. " " .. node, node, max) + elseif type == "placenode" then + quests.add_place_goal(quest, type .. " " .. node, node, max) + else + error("Unknown quest type!") + end + quests.add_quest(name, quest) elseif operator == "$pos" then story.generator.players_stories[player:get_player_name()].pos = {x=0,y=10,z=0} elseif operator == "$next" and cmd[2] then