From 2324eaccce387f93707a618b4f7b1939c3bdc19c Mon Sep 17 00:00:00 2001 From: raymoo Date: Thu, 11 Feb 2016 23:57:16 -0800 Subject: [PATCH] Initial commit (everything) --- depends.txt | 0 description.txt | 1 + init.lua | 155 +++++++++++++++++++++++++++++++ mod.conf | 1 + test.lua | 83 +++++++++++++++++ textures/progress_tree_check.png | Bin 0 -> 256 bytes 6 files changed, 240 insertions(+) create mode 100644 depends.txt create mode 100644 description.txt create mode 100644 init.lua create mode 100644 mod.conf create mode 100644 test.lua create mode 100644 textures/progress_tree_check.png diff --git a/depends.txt b/depends.txt new file mode 100644 index 0000000..e69de29 diff --git a/description.txt b/description.txt new file mode 100644 index 0000000..bdab295 --- /dev/null +++ b/description.txt @@ -0,0 +1 @@ +Library for tech/skill/whatever advancement trees (DAGs). diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..fb16f82 --- /dev/null +++ b/init.lua @@ -0,0 +1,155 @@ + +progress_tree = {} + +-- A progress tree is a map from string names to nodes in adjacency list +-- representation. + +local tree_methods = {} +local tree_metatab = { __index = tree_methods } + +function progress_tree.new_tree() + local ret = {} + setmetatable(ret, tree_metatab) + return ret +end + + +-- A node is a table with these fields: +-- - children - A list of child nodes +-- - parents - A list of parent nodes + +local node_methods = {} +local node_metatab = { __index = node_methods } + +function progress_tree.new_node(parents) + local ret = { children = {}, + parents = parents, + } + setmetatable(ret, node_metatab) + + return ret +end + + +-- Adds one node, and takes a list of parents. Each parent must be in the tree +-- already. +function tree_methods.add(self, name, parents) + for i, parent in ipairs(parents) do + if not self[parent] then + local err_msg = "[progress_tree]" + .. " Node " .. name .. + " is child of nonexistent parent " .. parent + error(err_msg) + end + end + + if self[name] then + error("[progress_tree] Duplicate node " .. name) + end + + self[name] = progress_tree.new_node(parents) + + for i, parent in ipairs(parents) do + table.insert(self[parent].children, name) + end +end + + +-- Each player can ahve data related to their completion of the tree, namely a table +-- with these fields: +-- - tree - The original tree this data tracks +-- - remaining_deps - A map from node names to the number of unfulfilled +-- prerequisite nodes. +-- - available - A set { ["node1"] = true, ["node2"] = true, ... } of nodes +-- that have no prerequisites left to fulfill, and have not been taken yet +-- - learned - A set of nodes the player has taken already. + +local pd_methods = {} +local pd_metatab = { __index = pd_methods } + + +-- Argument is the tree to create the data against. learned is an optional +-- argument for a set of initial learned techs. +function progress_tree.new_player_data(tree, learned) + local data = { tree = tree, + remaining_deps = {}, + available = {}, + learned = {}, + } + setmetatable(data, pd_metatab) + + for name, node in pairs(tree) do + local rem_deps = #(node.parents) + data.remaining_deps[name] = rem_deps + if rem_deps == 0 then + data.available[name] = true + end + end + + if learned then + for k in pairs(learned) do + data:learn(k) + end + end + + return data +end + + +-- Serialization to a string +function pd_methods.serialize(self) + return minetest.serialize(self.learned) +end + + +-- Deserialization. Data can only be deserialized against a particular tree. +-- Returns nil on failure. +function progress_tree.deserialize_player_data(tree, learned_string) + local learned = minetest.deserialize(learned_string) + if not learned then return end + + return progress_tree.new_player_data(tree, learned) +end + + +-- Tells whether the tech is already learned +function pd_methods.knows(self, name) + return self.learned[name] == true -- Want false when not in +end + + +-- Tells whether the tech is learnable with the current level of techs. +function pd_methods.can_learn(self, name) + return (not self.learned[name]) and self.available[name] == true +end + + +-- Learn a new tech. Returns true on success, returns false on failure. It can +-- fail if the tech doesn't exist or if it is already learned. It does not fail +-- if it is learned out of order, so use can_learn if you want to be sure. +function pd_methods.learn(self, name) + if self:knows(name) then return false end + + local node = self.tree[name] + if not node then return false end + + local children = node.children + + -- Decrement the remaining deps for each child + for i, child in ipairs(children) do + local new_rem = self.remaining_deps[child] - 1 + print(child, new_rem) + -- If the player learned it out of order, don't put it as + -- available. + if new_rem == 0 and not self:knows(child) then + self.available[child] = true + end + self.remaining_deps[child] = new_rem + end + + self.learned[name] = true + self.available[name] = nil +end + + +dofile(minetest.get_modpath(minetest.get_current_modname()) .. "/test.lua") diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..9fc12ae --- /dev/null +++ b/mod.conf @@ -0,0 +1 @@ +progress_tree \ No newline at end of file diff --git a/test.lua b/test.lua new file mode 100644 index 0000000..1443101 --- /dev/null +++ b/test.lua @@ -0,0 +1,83 @@ + +local test_tree = progress_tree.new_tree() + +test_tree:add("rock", {}) +test_tree:add("wood", {}) +test_tree:add("plant", {}) +test_tree:add("axe", {"rock", "wood"}) +test_tree:add("plank", {"wood"}) +test_tree:add("plankaxe", {"axe", "plank"}) +test_tree:add("wheat", {"plant"}) + +local data = progress_tree.new_player_data(test_tree, {"wood"}) + +local infos = { + rock = {x=0, y=0, texture="default_stone.png", desc="Rock"}, + wood = {x=1.5, y=0, texture="default_tree.png", desc="Wood"}, + plant = {x=5, y=0, texture="default_grass.png", desc="Plant"}, + axe = {x=1, y=1.5, texture="default_tool_stoneaxe.png", desc="Axe"}, + plank = {x=2.5, y=1.5, texture="default_stick.png", desc="Plank"}, + plankaxe = {x=2, y=3, texture="default_tool_woodaxe.png", desc="Plank Axe"}, + wheat = {x=5, y=1.5, texture="farming_wheat.png", desc="Wheat"}, +} + + +local function build_formspec() + local formspec = "size[8,8]" + print(dump(data.available)) + local nodes = {} + + for k in pairs(data.learned) do + local info = infos[k] + local texture = info.texture .. "^progress_tree_check.png^[colorize:#00FF00:50" + local fs = "image_button[" .. info.x .. "," .. info.y .. ";1,1;" + .. minetest.formspec_escape(texture) .. ";" .. k .. ";]" + local tooltip = "tooltip[" .. k .. ";" .. info.desc .. "]" + table.insert(nodes, fs) + table.insert(nodes, tooltip) + end + + for k in pairs(data.available) do + local info = infos[k] + local fs = "image_button[" .. info.x .. "," .. info.y .. ";1,1;" .. info.texture + .. ";" .. k .. ";]" + local tooltip = "tooltip[" .. k .. ";" .. info.desc .. "]" + table.insert(nodes, fs) + table.insert(nodes, tooltip) + end + + formspec = formspec .. table.concat(nodes) + + return formspec +end + + +local function show(player) + minetest.show_formspec(player:get_player_name(), "progress_tree:test", build_formspec()) +end + + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname ~= "progress_tree:test" then return end + + for node in pairs(data.available) do + if fields[node] then + data:learn(node) + end + end + + if not fields["quit"] then + show(player) + end +end) + + +minetest.register_craftitem("progress_tree:test_book", { + description = "Ultimate Techs", + groups = { not_in_creative_inventory = 1 }, + inventory_image = "default_book.png", + + on_use = function(itemstack, player) + show(player) + end, +}) diff --git a/textures/progress_tree_check.png b/textures/progress_tree_check.png new file mode 100644 index 0000000000000000000000000000000000000000..9366103596521f00b683c7e6657280fad2bd7b92 GIT binary patch literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkER%3IPL=BWMf-!?-gyG>zg(X7lr4?iwpNT}%CSlHs1$gpEhPK-Hs sI@g<%f;$S9J;-a$3toO8xBej`lb3Sh;o2!BKsy;cUHx3vIVCg!0JKX{1ONa4 literal 0 HcmV?d00001