Merge pull request #40 from rubenwardy/quests_cleanup
Quest goals and API
This commit is contained in:
commit
ec2a66fd27
37
mods/quests/README.md
Normal file
37
mods/quests/README.md
Normal file
@ -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
|
||||
```
|
@ -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,88 +37,173 @@ 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(minetest.get_player_by_name(player), quest.xp)
|
||||
quest.done = true
|
||||
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,
|
||||
})
|
||||
|
||||
minetest.register_on_dignode(function(pos, oldnode, digger)
|
||||
if not digger or not digger:is_player() then
|
||||
if not digger or not digger:is_player() or
|
||||
not quests.player_quests[digger:get_player_name()] then
|
||||
return
|
||||
end
|
||||
if 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)
|
||||
end
|
||||
quests.save_quests()
|
||||
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)
|
||||
if not placer or not placer:is_player() then
|
||||
if not placer or not placer:is_player() or
|
||||
not quests.player_quests[placer:get_player_name()] then
|
||||
return
|
||||
end
|
||||
if 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)
|
||||
end
|
||||
quests.save_quests()
|
||||
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_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))
|
||||
|
@ -2,14 +2,11 @@ story = {}
|
||||
|
||||
-- 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
|
||||
|
||||
@ -48,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")
|
||||
@ -189,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
|
||||
@ -208,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)}
|
||||
@ -232,30 +231,34 @@ 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
|
||||
local q_id = quests.add_quest(player:get_player_name(), {
|
||||
quest_type = cmd[2],
|
||||
node = cmd[3],
|
||||
progress = 0,
|
||||
done = false,
|
||||
max = tonumber(cmd[4]),
|
||||
xp = tonumber(cmd[5])
|
||||
})
|
||||
end
|
||||
if cmd[1] == "$pos" 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 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}
|
||||
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)
|
||||
@ -280,20 +283,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
|
||||
|
Loading…
x
Reference in New Issue
Block a user