Add documentation for Treasurer’s group system
[minetest_treasurer.git] / init.lua
blobf5b8dd1d6947f2a8db619c9856119abbeb0c5805
1 --[==[
2 Treasurer
3 - A mod for Minetest
4 version 0.2
5 ]==]
7 --[=[
8 TABLE OF CONTENTS
9 part 1: Initialization
10 part 2: Treasure API
11 part 3: Treasure spawning mod handling
12 part 4: Internal functions
13 ]=]
15 --[=[
16 part 1: Initialization
17 ]=]
19 -- This creates the main table; all functions of this mod are stored in this table
20 treasurer = {}
22 -- Table which stores all the treasures
23 treasurer.treasures = {}
25 -- This table stores the treasures again, but this time sorted by groups
26 treasurer.groups = {}
28 -- Groups defined by the Treasurer API
29 treasurer.groups.treasurer = {}
30 treasurer.groups.treasurer.default = {}
32 -- Groups defined by the Minetest API
33 treasurer.groups.minetest = {}
35 --[[
36 format of treasure table:
37 treasure = {
38 name, -- treasure name, e.g. mymod:item
39 rarity, -- relative rarity on a scale from 0 to 1 (inclusive).
40 -- a rare treasure must not neccessarily be a precious treasure
41 count, -- count (see below)
42 preciousness, -- preciousness or “worth” of the treasure.
43 -- ranges from 0 (“scorched stuff”) to 10 (“diamond block”)
44 wear, -- wear (see below)
45 metadata, -- unused at the moment
48 treasures can be nodes or items
50 format of count type:
51 count = number -- it’s always number times
52 count = {min, max} -- it’s pseudorandomly between min and max times, math.random() will be used to chose the value
53 count = {min, max, prob_func} -- it’s between min and max times, and the value is given by prob_func (which is not neccessarily random [in the strictly mathematical sense])
55 format of wear type:
56 completely analogous to count type
58 format of prob_func function:
59 prob_func = function()
60 --> returns a random or pseudorandom number between 0 (inclusive) and 1 (exclusive)
61 prob_func is entirely optional, if it’s not used, treasurer will default to math.random.
62 You can use prob_func to define your own random function, in case you don’t like an even
63 distribution
65 format of treasurer_groups:
66 This is just a table of strings, each string stands for a group name.
70 --[=[
71 part 2: Treasurer API
72 ]=]
74 --[[
75 treasurer.register_treasure - registers a new treasure
76 (this means the treasure will be ready to be spawned by treasure spawning mods.
78 name: name of resulting ItemStack, e.g. “mymod:item”
79 rarity: rarity of treasure on a scale from 0 to 1 (inclusive). lower = rarer
80 preciousness: preciousness of treasure on a scale from 0 (“scorched stuff”) to 10 (“diamond block”).
81 count: optional value which specifies the multiplicity of the item. Default is 1. See count syntax help in this file.
82 wear: optional value which specifies the wear of the item. Default is 0, which disables the wear. See wear syntax help in this file.
83 treasurer_groups: (optional) a table of group names to assign this treasure to. If omitted, the treasure is added to the default group.
84 This function does some basic parameter checking to catch the most obvious mistakes. If invalid parameters have been passed, the input is rejected and the function returns false. However, it does not cover every possible mistake, so some invalid treasures may slip through.
86 returns: true on success, false on failure
88 function treasurer.register_treasure(name, rarity, preciousness, count, wear, treasurer_groups )
89 --[[ We don’t trust our input, so we first check if the parameters
90 have the correct types and refuse to add the treasure if a
91 parameter is malformed.
92 What follows is a bunch of parameter checks.
95 -- check wheather name is a string
96 if type(name) ~= "string" then
97 minetest.log("error","[treasure] I rejected a treasure because the name was of type \""..type(name).."\" instead of \"string\".")
98 return false
99 end
100 -- first check if rarity is even a number
101 if type(rarity) == "number" then
102 -- then check wheather the rarity lies in the allowed range
103 if rarity < 0 or rarity > 1 then
104 minetest.log("error", "[treasurer] I rejected the treasure \""..tostring(name).."\" because it’s rarity value is out of bounds. (it was "..tostring(rarity)..".)")
105 return false
107 else
108 minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had an illegal type of rarity. Given type was \""..type(rarity).."\".")
109 return false
112 -- check if preciousness is even a number
113 if type(preciousness) == "number" then
114 -- then check wheather the preciousness lies in the allowed range
115 if preciousness < 0 or preciousness > 10 then
116 minetest.log("error", "[treasurer] I rejected the treasure \""..tostring(name).."\" because it’s preciousness value is out of bounds. (it was "..tostring(preciousness)..".)")
117 return false
119 else
120 minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had an illegal type of preciousness. Given type was \""..type(preciousness).."\".")
121 return false
125 -- first check if count is of a correct type
126 if type(count) ~= "number" and type(count) ~= "nil" and type(count) ~= "table" then
127 minetest.log("error", "[treasurer] I rejected the treasure \""..tostring(name).."\" because it had an illegal type of “count”. Given type was \""..type(count).."\".")
128 return false
130 -- if count’s a table, check if it’s format is correct
131 if type(count) == "table" then
132 if(not (type(count[1]) == "number" and type(count[2]) == "number" and (type(count[3]) == "function" or type(count[3]) == "nil"))) then
133 minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had a malformed table for the count parameter.")
134 return false
138 -- now do the same for wear:
139 -- first check if wear is of a correct type
140 if type(wear) ~= "number" and type(wear) ~= "nil" and type(wear) ~= "table" then
141 minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had an illegal type of “wear”. Given type was \""..type(wear).."\".")
142 return false
144 -- if wear’s a table, check if it’s format is correct
145 if type(wear) == "table" then
146 if(not (type(wear[1]) == "number" and type(wear[2]) == "number" and (type(wear[3]) == "function" or type(wear[3]) == "nil"))) then
147 minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had a malformed table for the wear parameter.")
148 return false
152 -- check type of treasurer_group
153 if type(treasurer_groups) ~= "table" and type(treasurer_groups) ~= "nil" and type(treasurer_groups) ~= "string" then
154 minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because the treasure_group parameter is of type "..tosting(type(treasurer_groups)).." (expected: nil, string or table).")
155 return false
161 --[[ End of checks. If we reached this point of the code, all checks have been passed
162 and we finally register the treasure.]]
164 -- default count is 1
165 if count == nil then count = 1 end
166 -- default wear is 0
167 if wear == nil then wear = 0 end
168 local treasure = {
169 name = name,
170 rarity = rarity,
171 count = count,
172 wear = wear,
173 preciousness = preciousness,
174 metadata = "",
176 table.insert(treasurer.treasures, treasure)
178 --[[ Assign treasure to Treasurer group(s) or default if not provided ]]
179 -- default Treasurer group is default
180 if treasurer_groups == nil then treasurer_groups = "default" end
182 if(type(treasurer_groups) == "string") then
183 if(treasurer.groups.treasurer[treasurer_groups] == nil) then
184 treasurer.groups.treasurer[treasurer_groups] = {}
186 table.insert(treasurer.groups.treasurer[treasurer_groups], treasure)
187 elseif(type(treasurer_groups) == "table") then
188 for i=1,#treasurer_groups do
189 -- assign to Treasurer group (create table if it does not exist yet)
190 if(treasurer.groups.treasurer[treasurer_groups[i]] == nil) then
191 treasurer.groups.treasurer[treasurer_groups[i]] = {}
193 table.insert(treasurer.groups.treasurer[treasurer_groups[i]], treasure)
198 minetest.log("info","[treasurer] Treasure successfully registered: "..name)
199 return true
203 --[=[
204 part 3: Treasure spawning mod (TSM) handling
207 --[[
208 treasurer.select_random_treasures - request some treasures from treasurer
209 parameters:
210 count: (optional) amount of items in the treasure. If this value is nil, treasurer assumes a default of 1.
211 min_preciousness: (optional) don’t consider treasures with a lower preciousness. nil = no lower limit
212 max_preciousness: (optional) don’t consider treasures with a higher preciousness. nil = no lower limit
213 treasurer_group: (optional): Only consider treasures which are members of at least one of the members of the provided Treasurer group table. nil = consider all groups
214 returns:
215 a table of ItemStacks (the requested treasures) - may be empty
216 on error, it returns false
218 function treasurer.select_random_treasures(count, min_preciousness, max_preciousness, treasurer_groups)
219 if #treasurer.treasures == 0 and count >= 1 then
220 minetest.log("info","[treasurer] I was asked to return "..count.." treasure(s) but I can’t return any because no treasure was registered to me.")
221 return {}
223 if count == nil then count = 1 end
224 local sum = 0
225 local cumulate = {}
226 local randoms = {}
228 -- copy treasures into helper table
229 local p_treasures = {}
230 if(treasurer_groups == nil) then
231 -- if the group filter is not used (defaul behaviour), copy all treasures
232 for i=1,#treasurer.treasures do
233 table.insert(p_treasures, treasurer.treasures[i])
236 -- if the group filter IS used, copy only the treasures from the said groups
237 elseif(type(treasurer_groups) == "string") then
238 if(treasurer.groups.treasurer[treasurer_groups] ~= nil) then
239 for i=1,#treasurer.groups.treasurer[treasurer_groups] do
240 table.insert(p_treasures, treasurer.groups.treasurer[treasurer_groups][i])
242 else
243 minetest.log("info","[treasurer] I was asked to return "..count.." treasure(s) but I can’t return any because no treasure which fits to the given Treasurer group “"..treasurer_groups.."”.")
244 return {}
246 elseif(type(treasurer_groups) == "table") then
247 for t=1,#treasurer_groups do
248 if(treasurer.groups.treasurer[treasurer_groups[t]] ~= nil) then
249 for i=1,#treasurer.groups.treasurer[treasurer_groups[t]] do
250 table.insert(p_treasures, treasurer.groups.treasurer[treasurer_groups[t]][i])
254 else
255 minetest.log("error","[treasurer] treasurer.select_random_treasures was called with a malformed treasurer_groups parameter!")
256 return false
259 if(min_preciousness ~= nil) then
260 -- filter out too unprecious treasures
261 for t=#p_treasures,1,-1 do
262 if((p_treasures[t].preciousness) < min_preciousness) then
263 table.remove(p_treasures,t)
268 if(max_preciousness ~= nil) then
269 -- filter out too precious treasures
270 for t=#p_treasures,1,-1 do
271 if(p_treasures[t].preciousness > max_preciousness) then
272 table.remove(p_treasures,t)
277 for t=1,#p_treasures do
278 sum = sum + p_treasures[t].rarity
279 cumulate[t] = sum
281 for c=1,count do
282 randoms[c] = math.random() * sum
285 local treasures = {}
286 for c=1,count do
287 for t=1,#p_treasures do
288 if randoms[c] < cumulate[t] then
289 table.insert(treasures, p_treasures[t])
290 break
295 local itemstacks = {}
296 for i=1,#treasures do
297 itemstacks[i] = treasurer.treasure_to_itemstack(treasures[i])
299 if #itemstacks < count then
300 minetest.log("info","[treasurer] I was asked to return "..count.." treasure(s) but I could only return "..(#itemstacks)..".")
302 return itemstacks
305 --[=[
306 Part 4: internal functions
309 --[[ treasurer.treasure_to_itemstack - converts a treasure table to an
310 ItemStack
311 parameter:
312 treasure: a treasure (see format in the head of this file)
313 returns:
314 an ItemStack
316 function treasurer.treasure_to_itemstack(treasure)
317 local itemstack = {}
318 itemstack.name = treasure.name
319 itemstack.count = treasurer.determine_count(treasure)
320 itemstack.wear = treasurer.determine_wear(treasure)
321 itemstack.metadata = treasure.metadata
323 return ItemStack(itemstack)
326 --[[
327 This determines the count of a treasure by taking the various different
328 possible types of the count value into account
329 This function assumes that the treasure table is valid.
330 returns: the count
332 function treasurer.determine_count(treasure)
333 if(type(treasure.count)=="number") then
334 return treasure.count
335 else
336 local min,max,prob = treasure.count[1], treasure.count[2], treasure.count[3]
337 if(prob == nil) then
338 return(math.floor(min + math.random() * (max-min)))
339 else
340 return(math.floor(min + prob() * (max-min)))
345 --[[
346 This determines the wear of a treasure by taking the various different
347 possible types of the wear value into account.
348 This function assumes that the treasure table is valid.
349 returns: the count
351 function treasurer.determine_wear(treasure)
352 if(type(treasure.wear)=="number") then
353 return treasure.wear
354 else
355 local min,max,prob = treasure.wear[1], treasure.wear[2], treasure.wear[3]
356 if(prob == nil) then
357 return(math.floor(min + math.random() * (max-min)))
358 else
359 return(math.floor(min + prob() * (max-min)))