Update doc_items (fake items)
[MineClone/MineClone2.git] / mods / HELP / doc / doc_items / init.lua
blobbdbba21a407b9f402e78efbf7df056a62f88ba50
1 -- Boilerplate to support localized strings if intllib mod is installed.
2 local S
3 if minetest.get_modpath("intllib") then
4 S = intllib.Getter()
5 else
6 S = function(s,a,...)a={a,...}return s:gsub("@(%d+)",function(n)return a[tonumber(n)]end)end
7 end
9 doc.sub.items = {}
11 -- Template texts
12 doc.sub.items.temp = {}
13 doc.sub.items.temp.deco = S("This is a decorational block.")
14 doc.sub.items.temp.build = S("This block is a building block for creating various buildings.")
15 doc.sub.items.temp.craftitem = S("This item is primarily used for crafting other items.")
17 doc.sub.items.temp.eat = S("Hold it in your hand, then leftclick to eat it.")
18 doc.sub.items.temp.eat_bad = S("Hold it in your hand, then leftclick to eat it. But why would you want to do this?")
19 doc.sub.items.temp.rotate_node = S("This block's rotation is affected by the way you place it: Place it on the floor or ceiling for a vertical orientation; place it at the side for a horizontal orientation. Sneaking while placing it leads to a perpendicular orientation instead.")
21 doc.sub.items.settings = {}
22 doc.sub.items.settings.friendly_group_names = false
23 local setting = minetest.settings:get_bool("doc_items_friendly_group_names")
24 if setting ~= nil then
25 doc.sub.items.settings.friendly_group_names = setting
26 end
27 doc.sub.items.settings.itemstring = false
28 setting = minetest.settings:get_bool("doc_items_show_itemstrings")
29 if setting ~= nil then
30 doc.sub.items.settings.itemstring = setting
31 end
33 -- Local stuff
34 local groupdefs = {}
35 local mininggroups = {}
36 local miscgroups = {}
37 local item_name_overrides = {
38 [""] = S("Hand"),
39 ["air"] = S("Air")
41 local suppressed = {
42 ["ignore"] = true,
45 -- This table contains which of the builtin factoids must NOT be displayed because
46 -- they have been disabled by a mod
47 local forbidden_core_factoids = {}
49 -- Helper functions
50 local yesno = function(bool)
51 if bool==true then return S("Yes")
52 elseif bool==false then return S("No")
53 else return "N/A" end
54 end
56 local groups_to_string = function(grouptable, filter)
57 local gstring = ""
58 local groups_count = 0
59 for id, value in pairs(grouptable) do
60 if (filter == nil or filter[id] == true) then
61 -- Readable group name
62 if groups_count > 0 then
63 -- List seperator
64 gstring = gstring .. S(", ")
65 end
66 if groupdefs[id] ~= nil and doc.sub.items.settings.friendly_group_names == true then
67 gstring = gstring .. groupdefs[id]
68 else
69 gstring = gstring .. id
70 end
71 groups_count = groups_count + 1
72 end
73 end
74 if groups_count == 0 then
75 return nil, 0
76 else
77 return gstring, groups_count
78 end
79 end
81 -- Replaces all newlines with spaces
82 local scrub_newlines = function(text)
83 local new, x = string.gsub(text, "\n", " ")
84 return new
85 end
87 --[[ Append a newline to text, unless it already ends with a newline. ]]
88 local newline = function(text)
89 if string.sub(text, #text, #text) == "\n" or text == "" then
90 return text
91 else
92 return text .. "\n"
93 end
94 end
96 --[[ Make sure the text ends with two newlines by appending any missing newlines at the end, if neccessary. ]]
97 local newline2 = function(text)
98 if string.sub(text, #text-1, #text) == "\n\n" or text == "" then
99 return text
100 elseif string.sub(text, #text, #text) == "\n" then
101 return text .. "\n"
102 else
103 return text .. "\n\n"
108 -- Extract suitable item description for formspec
109 local description_for_formspec = function(itemstring)
110 if minetest.registered_items[itemstring] == nil then
111 -- Huh? The item doesn't exist for some reason. Better give a dummy string
112 minetest.log("warning", "[doc] Unknown item detected: "..tostring(itemstring))
113 return S("Unknown item (@1)", tostring(itemstring))
115 local description = minetest.registered_items[itemstring].description
116 if description == nil or description == "" then
117 return minetest.formspec_escape(itemstring)
118 else
119 return minetest.formspec_escape(scrub_newlines(description))
123 local get_entry_name = function(itemstring)
124 local def = minetest.registered_items[itemstring]
125 if def._doc_items_entry_name ~= nil then
126 return def._doc_items_entry_name
127 elseif item_name_overrides[itemstring] ~= nil then
128 return item_name_overrides[itemstring]
129 else
130 return def.description
134 doc.sub.items.get_group_name = function(groupname)
135 if groupdefs[groupname] ~= nil and doc.sub.items.settings.friendly_group_names == true then
136 return groupdefs[groupname]
137 else
138 return groupname
142 local burntime_to_text = function(burntime)
143 if burntime == nil then
144 return S("unknown")
145 elseif burntime == 1 then
146 return S("1 second")
147 else
148 return S("@1 seconds", burntime)
152 --[[ Convert tool capabilities to readable text. Extracted information:
153 * Mining capabilities
154 * Durability (when mining
155 * Full punch interval
156 * Damage groups
158 local factoid_toolcaps = function(tool_capabilities, check_uses)
159 if forbidden_core_factoids.tool_capabilities then
160 return ""
163 local formstring = ""
164 if check_uses == nil then check_uses = false end
165 if tool_capabilities ~= nil and tool_capabilities ~= {} then
166 local groupcaps = tool_capabilities.groupcaps
167 if groupcaps ~= nil then
168 local miningcapstr = ""
169 local miningtimesstr = ""
170 local miningusesstr = ""
171 local caplines = 0
172 local timelines = 0
173 local useslines = 0
174 for k,v in pairs(groupcaps) do
175 -- Mining capabilities
176 local minrating, maxrating
177 if v.times then
178 for rating, time in pairs(v.times) do
179 if minrating == nil then minrating = rating else
180 if minrating > rating then minrating = rating end
182 if maxrating == nil then maxrating = rating else
183 if maxrating < rating then maxrating = rating end
186 else
187 minrating = 1
188 maxrating = 1
190 local maxlevel = v.maxlevel
191 if not maxlevel then
192 -- Default from tool.h
193 maxlevel = 1
195 miningcapstr = miningcapstr .. S("• @1: @2", doc.sub.items.get_group_name(k), maxlevel)
196 miningcapstr = miningcapstr .. "\n"
197 caplines = caplines + 1
199 for rating=3, 1, -1 do
200 if v.times ~= nil and v.times[rating] ~= nil then
201 local maxtime = v.times[rating]
202 local mintime
203 local mintimestr, maxtimestr
204 local maxlevel_calc = maxlevel
205 if maxlevel_calc < 1 then
206 maxlevel_calc = 1
208 mintime = maxtime / maxlevel_calc
209 mintimestr = string.format("%.1f", mintime)
210 maxtimestr = string.format("%.1f", maxtime)
211 if mintimestr ~= maxtimestr then
212 miningtimesstr = miningtimesstr ..
213 S("• @1, rating @2: @3 s - @4 s",
214 doc.sub.items.get_group_name(k), rating,
215 mintimestr, maxtimestr)
216 else
217 miningtimesstr = miningtimesstr ..
218 S("• @1, rating @2: @3 s",
219 doc.sub.items.get_group_name(k), rating,
220 mintimestr)
222 miningtimesstr = miningtimesstr.. "\n"
223 timelines = timelines + 1
227 -- Number of mining uses
228 local base_uses = v.uses
229 if not base_uses then
230 -- Default from tool.h
231 base_uses = 20
233 if check_uses and base_uses > 0 then
234 for level=0, maxlevel do
235 local real_uses = base_uses * math.pow(3, maxlevel - level)
236 if real_uses < 65535 then
237 miningusesstr = miningusesstr .. S("• @1, level @2: @3 uses", doc.sub.items.get_group_name(k), level, real_uses)
238 else
239 miningusesstr = miningusesstr .. S("• @1, level @2: Unlimited", doc.sub.items.get_group_name(k), level)
241 miningusesstr = miningusesstr .. "\n"
242 useslines = useslines + 1
246 if caplines > 0 then
247 formstring = formstring .. S("This tool is capable of mining.") .. "\n"
248 formstring = formstring .. S("Maximum toughness levels:") .. "\n"
249 formstring = formstring .. miningcapstr
250 formstring = newline(formstring)
252 if timelines > 0 then
253 formstring = formstring .. S("Mining times:") .. "\n"
254 formstring = formstring .. miningtimesstr
256 if useslines > 0 then
257 formstring = formstring .. S("Mining durability:") .. "\n"
258 formstring = formstring .. miningusesstr
260 if caplines > 0 or useslines > 0 or timelines > 0 then
261 formstring = newline2(formstring)
265 -- Weapon data
266 local damage_groups = tool_capabilities.damage_groups
267 if damage_groups ~= nil then
268 formstring = formstring .. S("This is a melee weapon which deals damage by punching.") .. "\n"
269 -- Damage groups
270 formstring = formstring .. S("Maximum damage per hit:") .. "\n"
271 for k,v in pairs(damage_groups) do
272 formstring = formstring .. S("• @1: @2 HP", doc.sub.items.get_group_name(k), v)
273 formstring = formstring .. "\n"
276 -- Full punch interval
277 local punch = 1.0
278 if tool_capabilities.full_punch_interval ~= nil then
279 punch = tool_capabilities.full_punch_interval
281 formstring = formstring .. S("Full punch interval: @1 s", string.format("%.1f", punch))
282 formstring = formstring .. "\n"
286 return formstring
289 --[[ Factoid for the mining times properties of a node. Extracted infos:
290 - dig_immediate group
291 - Digging times/groups
292 - level group
294 local factoid_mining_node = function(data)
295 if forbidden_core_factoids.node_mining then
296 return ""
299 local datastring = ""
300 if data.def.pointable ~= false and (data.def.liquid_type == "none" or data.def.liquid_type == nil) then
301 -- Check if there are no mining groups at all
302 local nogroups = true
303 for groupname,_ in pairs(mininggroups) do
304 if data.def.groups[groupname] ~= nil or groupname == "dig_immediate" then
305 nogroups = false
306 break
309 -- dig_immediate
310 if data.def.drop ~= "" then
311 if data.def.groups.dig_immediate == 2 then
312 datastring = datastring .. S("This block can be mined by any mining tool in half a second.").."\n"
313 elseif data.def.groups.dig_immediate == 3 then
314 datastring = datastring .. S("This block can be mined by any mining tool immediately.").."\n"
315 -- Note: “unbreakable” is an unofficial group for undiggable blocks
316 elseif data.def.diggable == false or nogroups or data.def.groups.immortal == 1 or data.def.groups.unbreakable == 1 then
317 datastring = datastring .. S("This block can not be mined by ordinary mining tools.").."\n"
319 else
320 if data.def.groups.dig_immediate == 2 then
321 datastring = datastring .. S("This block can be destroyed by any mining tool in half a second.").."\n"
322 elseif data.def.groups.dig_immediate == 3 then
323 datastring = datastring .. S("This block can be destroyed by any mining tool immediately.").."\n"
324 elseif data.def.diggable == false or nogroups or data.def.groups.immortal == 1 or data.def.groups.unbreakable == 1 then
325 datastring = datastring .. S("This block can not be destroyed by ordinary mining tools.").."\n"
328 -- Expose “ordinary” mining groups (crumbly, cracky, etc.) and level group
329 -- Skip this for immediate digging to avoid redundancy
330 if data.def.groups.dig_immediate ~= 3 then
331 local mstring = S("This block can be mined by mining tools which match any of the following mining ratings and its toughness level.").."\n"
332 mstring = mstring .. S("Mining ratings:").."\n"
333 local minegroupcount = 0
334 for group,_ in pairs(mininggroups) do
335 local rating = data.def.groups[group]
336 if rating ~= nil then
337 mstring = mstring .. S("• @1: @2", doc.sub.items.get_group_name(group), rating).."\n"
338 minegroupcount = minegroupcount + 1
341 local level = data.def.groups.level
342 if not level then
343 level = 0
345 mstring = mstring .. S("Toughness level: @1", level).."\n"
347 if minegroupcount > 0 then
348 datastring = datastring .. mstring
352 return datastring
355 -- Pointing range of itmes
356 local range_factoid = function(itemstring, def)
357 local handrange = minetest.registered_items[""].range
358 local itemrange = def.range
359 if itemstring == "" then
360 if handrange ~= nil then
361 return S("Range: @1", itemrange)
362 else
363 return S("Range: 4")
365 else
366 if handrange == nil then handrange = 4 end
367 if itemrange ~= nil then
368 return S("Range: @1", itemrange)
369 else
370 return S("Range: @1 (@2)", get_entry_name(""), handrange)
375 -- Smelting fuel factoid
376 local factoid_fuel = function(itemstring, ctype)
377 if forbidden_core_factoids.fuel then
378 return ""
381 local formstring = ""
382 local result, decremented = minetest.get_craft_result({method = "fuel", items = {itemstring}})
383 if result ~= nil and result.time > 0 then
384 local base
385 local burntext = burntime_to_text(result.time)
386 if ctype == "tools" then
387 base = S("This tool can serve as a smelting fuel with a burning time of @1.", burntext)
388 elseif ctype == "nodes" then
389 base = S("This block can serve as a smelting fuel with a burning time of @1.", burntext)
390 else
391 base = S("This item can serve as a smelting fuel with a burning time of @1.", burntext)
393 formstring = formstring .. base
394 local replaced = decremented.items[1]:get_name()
395 if not decremented.items[1]:is_empty() and replaced ~= itemstring then
396 formstring = formstring .. S(" Using it as fuel turns it into: @1.", description_for_formspec(replaced))
398 formstring = newline(formstring)
400 return formstring
403 -- Shows the itemstring of an item
404 local factoid_itemstring = function(itemstring, playername)
405 if forbidden_core_factoids.itemstring then
406 return ""
409 local privs = minetest.get_player_privs(playername)
410 if doc.sub.items.settings.itemstring or (privs.give or privs.debug) then
411 return S("Itemstring: \"@1\"", itemstring)
412 else
413 return ""
417 local entry_image = function(data)
418 local formstring = ""
419 -- No image for air
420 if data.itemstring ~= "air" then
421 -- Hand
422 if data.itemstring == "" then
423 formstring = formstring .. "image["..(doc.FORMSPEC.ENTRY_END_X-1)..","..doc.FORMSPEC.ENTRY_START_Y..";1,1;"..
424 minetest.registered_items[""].wield_image.."]"
425 -- Other items
426 elseif data.image ~= nil then
427 formstring = formstring .. "image["..(doc.FORMSPEC.ENTRY_END_X-1)..","..doc.FORMSPEC.ENTRY_START_Y..";1,1;"..data.image.."]"
428 else
429 formstring = formstring .. "item_image["..(doc.FORMSPEC.ENTRY_END_X-1)..","..doc.FORMSPEC.ENTRY_START_Y..";1,1;"..data.itemstring.."]"
432 return formstring
435 -- Stuff for factoids
436 local factoid_generators = {}
437 factoid_generators.nodes = {}
438 factoid_generators.tools = {}
439 factoid_generators.craftitems = {}
441 --[[ Returns a list of all registered factoids for the specified category and type
442 * category_id: Identifier of the Documentation System category in which the factoid appears
443 * factoid_type: If set, oly returns factoid with a matching factoid_type.
444 If nil, all factoids for this category will be generated
445 * data: Entry data to parse ]]
446 local factoid_custom = function(category_id, factoid_type, data)
447 local ftable = factoid_generators[category_id]
448 local datastring = ""
449 -- Custom factoids are inserted here
450 for i=1,#ftable do
451 if factoid_type == nil or ftable[i].ftype == factoid_type then
452 datastring = datastring .. ftable[i].fgen(data.itemstring, data.def)
453 if datastring ~= "" then
454 datastring = newline(datastring)
458 return datastring
461 -- Shows core information shared by all items, to be inserted at the top
462 local factoids_header = function(data, ctype)
463 local datastring = ""
464 if not forbidden_core_factoids.basics then
466 local longdesc = data.longdesc
467 local usagehelp = data.usagehelp
468 if longdesc ~= nil then
469 datastring = datastring .. S("Description: @1", longdesc)
470 datastring = newline2(datastring)
472 if usagehelp ~= nil then
473 datastring = datastring .. S("Usage help: @1", usagehelp)
474 datastring = newline2(datastring)
476 datastring = datastring .. factoid_custom(ctype, "use", data)
477 datastring = newline2(datastring)
479 if data.itemstring ~= "" then
480 datastring = datastring .. S("Maximum stack size: @1", data.def.stack_max)
481 datastring = newline(datastring)
483 datastring = datastring .. range_factoid(data.itemstring, data.def)
485 datastring = newline2(datastring)
487 if data.def.liquids_pointable == true then
488 if ctype == "nodes" then
489 datastring = datastring .. S("This block points to liquids.").."\n"
490 elseif ctype == "tools" then
491 datastring = datastring .. S("This tool points to liquids.").."\n"
492 elseif ctype == "craftitems" then
493 datastring = datastring .. S("This item points to liquids.").."\n"
496 if data.def.on_use ~= nil then
497 if ctype == "nodes" then
498 datastring = datastring .. S("Punches with this block don't work as usual; melee combat and mining are either not possible or work differently.").."\n"
499 elseif ctype == "tools" then
500 datastring = datastring .. S("Punches with this tool don't work as usual; melee combat and mining are either not possible or work differently.").."\n"
501 elseif ctype == "craftitems" then
502 datastring = datastring .. S("Punches with this item don't work as usual; melee combat and mining are either not possible or work differently.").."\n"
508 datastring = newline(datastring)
510 -- Show tool capability stuff, including durability if not overwritten by custom field
511 local check_uses = false
512 if ctype == "tools" then
513 check_uses = data.def._doc_items_durability == nil
515 datastring = datastring .. factoid_toolcaps(data.def.tool_capabilities, check_uses)
516 datastring = newline2(datastring)
518 return datastring
521 -- Shows less important information shared by all items, to be inserted at the bottom
522 local factoids_footer = function(data, playername, ctype)
523 local datastring = ""
524 datastring = datastring .. factoid_custom(ctype, "groups", data)
525 datastring = newline2(datastring)
527 -- Show other “exposable” groups
528 if not forbidden_core_factoids.groups then
529 local gstring, gcount = groups_to_string(data.def.groups, miscgroups)
530 if gstring ~= nil then
531 if gcount == 1 then
532 if ctype == "nodes" then
533 datastring = datastring .. S("This block belongs to the @1 group.", gstring) .. "\n"
534 elseif ctype == "tools" then
535 datastring = datastring .. S("This tool belongs to the @1 group.", gstring) .. "\n"
536 elseif ctype == "craftitems" then
537 datastring = datastring .. S("This item belongs to the @1 group.", gstring) .. "\n"
539 else
540 if ctype == "nodes" then
541 datastring = datastring .. S("This block belongs to these groups: @1.", gstring) .. "\n"
542 elseif ctype == "tools" then
543 datastring = datastring .. S("This tool belongs to these groups: @1.", gstring) .. "\n"
544 elseif ctype == "craftitems" then
545 datastring = datastring .. S("This item belongs to these groups: @1.", gstring) .. "\n"
550 datastring = newline2(datastring)
552 -- Show fuel recipe
553 datastring = datastring .. factoid_fuel(data.itemstring, ctype)
554 datastring = newline2(datastring)
556 -- Other custom factoids
557 datastring = datastring .. factoid_custom(ctype, "misc", data)
558 datastring = newline2(datastring)
560 -- Itemstring
561 datastring = datastring .. factoid_itemstring(data.itemstring, playername)
563 return datastring
566 function doc.sub.items.register_factoid(category_id, factoid_type, factoid_generator)
567 local ftable = { fgen = factoid_generator, ftype = factoid_type }
568 if category_id == "nodes" or category_id == "tools" or category_id == "craftitems" then
569 table.insert(factoid_generators[category_id], ftable)
570 return true
571 elseif category_id == nil then
572 table.insert(factoid_generators.nodes, ftable)
573 table.insert(factoid_generators.tools, ftable)
574 table.insert(factoid_generators.craftitems, ftable)
575 return false
579 function doc.sub.items.disable_core_factoid(factoid_name)
580 forbidden_core_factoids[factoid_name] = true
583 doc.add_category("nodes", {
584 hide_entries_by_default = true,
585 name = S("Blocks"),
586 description = S("Item reference of blocks and other things which are capable of occupying space"),
587 build_formspec = function(data, playername)
588 if data then
589 local formstring = ""
590 local datastring = ""
592 formstring = entry_image(data)
593 datastring = factoids_header(data, "nodes")
595 local liquid = data.def.liquidtype ~= "none" and minetest.get_item_group(data.itemstring, "fake_liquid") == 0
596 if not forbidden_core_factoids.basics then
597 datastring = datastring .. S("Collidable: @1", yesno(data.def.walkable)) .. "\n"
598 if data.def.pointable == true then
599 datastring = datastring .. S("Pointable: Yes") .. "\n"
600 elseif liquid then
601 datastring = datastring .. S("Pointable: Only by special items") .. "\n"
602 else
603 datastring = datastring .. S("Pointable: No") .. "\n"
606 datastring = newline2(datastring)
607 if not forbidden_core_factoids.liquid and liquid then
608 datastring = newline(datastring, false)
609 datastring = datastring .. S("This block is a liquid with these properties:") .. "\n"
610 local range, renew, viscos
611 if data.def.liquid_range then range = data.def.liquid_range else range = 8 end
612 if data.def.liquid_renewable ~= nil then renew = data.def.liquid_renewable else renew = true end
613 if data.def.liquid_viscosity then viscos = data.def.liquid_viscosity else viscos = 0 end
614 if renew then
615 datastring = datastring .. S("• Renewable") .. "\n"
616 else
617 datastring = datastring .. S("• Not renewable") .. "\n"
619 if range == 0 then
620 datastring = datastring .. S("• No flowing") .. "\n"
621 else
622 datastring = datastring .. S("• Flowing range: @1", range) .. "\n"
624 datastring = datastring .. S("• Viscosity: @1", viscos) .. "\n"
626 datastring = newline2(datastring)
628 -- Global factoids
629 --- Direct interaction with the player
630 ---- Damage (very important)
631 if not forbidden_core_factoids.node_damage then
632 if data.def.damage_per_second ~= nil and data.def.damage_per_second > 1 then
633 datastring = datastring .. S("This block causes a damage of @1 hit points per second.", data.def.damage_per_second) .. "\n"
634 elseif data.def.damage_per_second == 1 then
635 datastring = datastring .. S("This block causes a damage of @1 hit point per second.", data.def.damage_per_second) .. "\n"
637 if data.def.drowning then
638 if data.def.drowning > 1 then
639 datastring = datastring .. S("This block decreases your breath and causes a drowning damage of @1 hit points every 2 seconds.", data.def.drowning) .. "\n"
640 elseif data.def.drowning == 1 then
641 datastring = datastring .. S("This block decreases your breath and causes a drowning damage of @1 hit point every 2 seconds.", data.def.drowning) .. "\n"
644 local fdap = data.def.groups.fall_damage_add_percent
645 if fdap ~= nil then
646 if fdap > 0 then
647 datastring = datastring .. S("The fall damage on this block is increased by @1%.", fdap) .. "\n"
648 elseif fdap <= -100 then
649 datastring = datastring .. S("This block negates all fall damage.") .. "\n"
650 else
651 datastring = datastring .. S("The fall damage on this block is reduced by @1%.", math.abs(fdap)) .. "\n"
655 datastring = datastring .. factoid_custom("nodes", "damage", data)
656 datastring = newline2(datastring)
658 ---- Movement
659 if not forbidden_core_factoids.node_movement then
660 if data.def.groups.disable_jump == 1 then
661 datastring = datastring .. S("You can not jump while standing on this block.").."\n"
663 if data.def.climbable == true then
664 datastring = datastring .. S("This block can be climbed.").."\n"
666 local bouncy = data.def.groups.bouncy
667 if bouncy ~= nil then
668 datastring = datastring .. S("This block will make you bounce off with an elasticity of @1%.", bouncy).."\n"
670 datastring = datastring .. factoid_custom("nodes", "movement", data)
671 datastring = newline2(datastring)
674 ---- Sounds
675 if not forbidden_core_factoids.sounds then
676 local function is_silent(def, soundtype)
677 return type(def.sounds) ~= "table" or def.sounds[soundtype] == nil or def.sounds[soundtype] == "" or (type(data.def.sounds[soundtype]) == "table" and (data.def.sounds[soundtype].name == nil or data.def.sounds[soundtype].name == ""))
679 local silentstep, silentdig, silentplace = false, false, false
680 if data.def.walkable and is_silent(data.def, "footstep") then
681 silentstep = true
683 if data.def.diggable and is_silent(data.def, "dig") and is_silent(data.def, "dug") then
684 silentdig = true
686 if is_silent(data.def, "place") and is_silent(data.def, "place_failed") and data.itemstring ~= "air" then
687 silentplace = true
689 if silentstep and silentdig and silentplace then
690 datastring = datastring .. S("This block is completely silent when walked on, mined or built.").."\n"
691 elseif silentdig and silentplace then
692 datastring = datastring .. S("This block is completely silent when mined or built.").."\n"
693 else
694 if silentstep then
695 datastring = datastring .. S("Walking on this block is completely silent.").."\n"
697 if silentdig then
698 datastring = datastring .. S("Mining this block is completely silent.").."\n"
700 if silentplace then
701 datastring = datastring .. S("Building this block is completely silent.").."\n"
705 datastring = datastring .. factoid_custom("nodes", "sound", data)
706 datastring = newline2(datastring)
708 -- Block activity
709 --- Gravity
710 if not forbidden_core_factoids.gravity then
711 if data.def.groups.falling_node == 1 then
712 datastring = datastring .. S("This block is affected by gravity and can fall.").."\n"
715 datastring = datastring .. factoid_custom("nodes", "gravity", data)
716 datastring = newline2(datastring)
718 --- Dropping and destruction
719 if not forbidden_core_factoids.drop_destroy then
720 if data.def.buildable_to == true then
721 datastring = datastring .. S("Building another block at this block will place it inside and replace it.").."\n"
722 if data.def.walkable then
723 datastring = datastring .. S("Falling blocks can go through this block; they destroy it when doing so.").."\n"
726 if data.def.walkable == false then
727 if data.def.buildable_to == false and data.def.drop ~= "" then
728 datastring = datastring .. S("This block will drop as an item when a falling block ends up inside it.").."\n"
729 else
730 datastring = datastring .. S("This block is destroyed when a falling block ends up inside it.").."\n"
733 if data.def.groups.attached_node == 1 then
734 if data.def.paramtype2 == "wallmounted" then
735 datastring = datastring .. S("This block will drop as an item when it is not attached to a surrounding block.").."\n"
736 else
737 datastring = datastring .. S("This block will drop as an item when no collidable block is below it.").."\n"
740 if data.def.floodable == true then
741 datastring = datastring .. S("Liquids can flow into this block and destroy it.").."\n"
744 datastring = datastring .. factoid_custom("nodes", "drop_destroy", data)
745 datastring = newline2(datastring)
747 -- Block appearance
748 --- Light
749 if not forbidden_core_factoids.light and data.def.light_source then
750 if data.def.light_source > 3 then
751 datastring = datastring .. S("This block is a light source with a light level of @1.", data.def.light_source).."\n"
752 elseif data.def.light_source > 0 then
753 datastring = datastring .. S("This block glows faintly with a light level of @1.", data.def.light_source).."\n"
755 if data.def.paramtype == "light" and data.def.sunlight_propagates then
756 datastring = datastring .. S("This block allows light to propagate with a small loss of brightness, and sunlight can even go through losslessly.").."\n"
757 elseif data.def.paramtype == "light" then
758 datastring = datastring .. S("This block allows light to propagate with a small loss of brightness.").."\n"
759 elseif data.def.sunlight_propagates then
760 datastring = datastring .. S("This block allows sunlight to propagate without loss in brightness.").."\n"
763 datastring = datastring .. factoid_custom("nodes", "light", data)
764 datastring = newline2(datastring)
766 --- List nodes/groups to which this node connects to
767 if not forbidden_core_factoids.connects_to and data.def.connects_to ~= nil then
768 local nodes = {}
769 local groups = {}
770 for c=1,#data.def.connects_to do
771 local itemstring = data.def.connects_to[c]
772 if string.sub(itemstring,1,6) == "group:" then
773 groups[string.sub(itemstring,7,#itemstring)] = 1
774 else
775 table.insert(nodes, itemstring)
779 local nstring = ""
780 for n=1,#nodes do
781 local name
782 if item_name_overrides[nodes[n]] ~= nil then
783 name = item_name_overrides[nodes[n]]
784 else
785 name = description_for_formspec(nodes[n])
787 if n > 1 then
788 nstring = nstring .. S(", ")
790 if name ~= nil then
791 nstring = nstring .. name
792 else
793 nstring = nstring .. S("Unknown Node")
796 if #nodes == 1 then
797 datastring = datastring .. S("This block connects to this block: @1.", nstring) .. "\n"
798 elseif #nodes > 1 then
799 datastring = datastring .. S("This block connects to these blocks: @1.", nstring) .. "\n"
802 local gstring, gcount = groups_to_string(groups)
803 if gcount == 1 then
804 datastring = datastring .. S("This block connects to blocks of the @1 group.", gstring) .. "\n"
805 elseif gcount > 1 then
806 datastring = datastring .. S("This block connects to blocks of the following groups: @1.", gstring) .. "\n"
810 datastring = newline2(datastring)
812 -- Mining groups
813 datastring = datastring .. factoid_custom("nodes", "mining", data)
815 datastring = newline(datastring)
817 datastring = datastring .. factoid_mining_node(data)
818 datastring = newline2(datastring)
820 -- Non-default drops
821 if not forbidden_core_factoids.drops and data.def.drop ~= nil and data.def.drop ~= data.itemstring and data.itemstring ~= "air" then
822 -- TODO: Calculate drop probabilities of max > 1 like for max == 1
823 local get_desc = function(stack)
824 return description_for_formspec(stack:get_name())
826 if data.def.drop == "" then
827 datastring = datastring .. S("This block won't drop anything when mined.").."\n"
828 elseif type(data.def.drop) == "string" then
829 local dropstack = ItemStack(data.def.drop)
830 if dropstack:get_name() ~= data.itemstring and dropstack:get_name() ~= 1 then
831 local desc = get_desc(dropstack)
832 local count = dropstack:get_count()
833 if count > 1 then
834 datastring = datastring .. S("This block will drop the following when mined: @1×@2.", count, desc).."\n"
835 else
836 datastring = datastring .. S("This block will drop the following when mined: @1.", desc).."\n"
839 elseif type(data.def.drop) == "table" and data.def.drop.items ~= nil then
840 local max = data.def.drop.max_items
841 local dropstring = ""
842 local dropstring_base = ""
843 if max == nil then
844 dropstring_base = S("This block will drop the following items when mined: %s.")
845 elseif max == 1 then
846 if #data.def.drop.items == 1 then
847 dropstring_base = S("This block will drop the following when mined: %s.")
848 else
849 dropstring_base = S("This block will randomly drop one of the following when mined: %s.")
851 else
852 dropstring_base = S("This block will randomly drop up to %d drops of the following possible drops when mined: %s.")
854 -- Save calculated probabilities into a table for later output
855 local probtables = {}
856 local probtable
857 local rarity_history = {}
858 for i=1,#data.def.drop.items do
859 local local_rarity = data.def.drop.items[i].rarity
860 local chance = 1
861 local rarity = 1
862 if local_rarity == nil then
863 local_rarity = 1
865 if max == 1 then
866 -- Chained probability
867 table.insert(rarity_history, local_rarity)
868 chance = 1
869 for r=1, #rarity_history do
870 local chance_factor
871 if r > 1 and rarity_history[r-1] == 1 then
872 chance = 0
873 break
875 if r == #rarity_history then
876 chance_factor = 1/rarity_history[r]
877 else
878 chance_factor = (rarity_history[r]-1)/rarity_history[r]
880 chance = chance * chance_factor
882 if chance > 0 then
883 rarity = 1/chance
885 else
886 rarity = local_rarity
887 chance = 1/rarity
889 -- Exclude impossible drops
890 if chance > 0 then
891 probtable = {}
892 probtable.items = {}
893 for j=1,#data.def.drop.items[i].items do
894 local dropstack = ItemStack(data.def.drop.items[i].items[j])
895 local itemstring = dropstack:get_name()
896 local desc = get_desc(dropstack)
897 local count = dropstack:get_count()
898 if not(itemstring == nil or itemstring == "" or count == 0) then
899 if probtable.items[itemstring] == nil then
900 probtable.items[itemstring] = {desc = desc, count = count}
901 else
902 probtable.items[itemstring].count = probtable.items[itemstring].count + count
906 probtable.rarity = rarity
907 if #data.def.drop.items[i].items > 0 then
908 table.insert(probtables, probtable)
912 -- Do some cleanup of the probability table
913 if max == 1 or max == nil then
914 -- Sort by rarity
915 local comp = function(p1, p2)
916 return p1.rarity < p2.rarity
918 table.sort(probtables, comp)
920 -- Output probability table
921 local pcount = 0
922 for i=1, #probtables do
923 if pcount > 0 then
924 -- List seperator
925 dropstring = dropstring .. S(", ")
927 local probtable = probtables[i]
928 local icount = 0
929 local dropstring_this = ""
930 for _, itemtable in pairs(probtable.items) do
931 if icount > 0 then
932 -- Final list seperator
933 dropstring_this = dropstring_this .. S(" and ")
935 local desc = S(itemtable.desc)
936 local count = itemtable.count
937 if count ~= 1 then
938 desc = S("@1×@2", count, desc)
940 dropstring_this = dropstring_this .. desc
941 icount = icount + 1
944 local rarity = probtable.rarity
945 local raritystring = ""
946 -- No percentage if there's only one possible guaranteed drop
947 if not(rarity == 1 and #data.def.drop.items == 1) then
948 local chance = (1/rarity)*100
949 if rarity > 200 then -- <0.5%
950 -- For very low percentages
951 dropstring_this = S("@1 (<0.5%)", dropstring_this)
952 else
953 -- Add circa indicator for percentages with decimal point
954 local fchance = string.format("%.0f", chance)
955 if math.fmod(chance, 1) > 0 then
956 dropstring_this = S("@1 (ca. @2%)", dropstring_this, fchance)
957 else
958 dropstring_this = S("@1 (@2%)", dropstring_this, fchance)
962 dropstring = dropstring .. dropstring_this
963 pcount = pcount + 1
965 if max ~= nil and max > 1 then
966 datastring = datastring .. string.format(dropstring_base, max, dropstring)
967 else
968 datastring = datastring .. string.format(dropstring_base, dropstring)
970 datastring = newline(datastring)
973 datastring = datastring .. factoid_custom("nodes", "drops", data)
974 datastring = newline2(datastring)
976 datastring = datastring .. factoids_footer(data, playername, "nodes")
978 formstring = formstring .. doc.widgets.text(datastring, nil, nil, doc.FORMSPEC.ENTRY_WIDTH - 1.2)
980 return formstring
981 else
982 return "label[0,1;NO DATA AVALIABLE!]"
987 doc.add_category("tools", {
988 hide_entries_by_default = true,
989 name = S("Tools and weapons"),
990 description = S("Item reference of all wieldable tools and weapons"),
991 sorting = "function",
992 -- Roughly sort tools based on their capabilities. Tools which dig the same stuff end up in the same group
993 sorting_data = function(entry1, entry2)
994 local entries = { entry1, entry2 }
995 -- Hand beats all
996 if entries[1].eid == "" then return true end
997 if entries[2].eid == "" then return false end
999 local comp = {}
1000 for e=1, 2 do
1001 comp[e] = {}
1003 -- No tool capabilities: Instant loser
1004 if entries[1].data.def.tool_capabilities == nil and entries[2].data.def.tool_capabilities ~= nil then return false end
1005 if entries[2].data.def.tool_capabilities == nil and entries[1].data.def.tool_capabilities ~= nil then return true end
1006 -- No tool capabilities for both: Compare by uses
1007 if entries[1].data.def.tool_capabilities == nil and entries[2].data.def.tool_capabilities == nil then
1008 for e=1, 2 do
1009 if type(entries[e].data.def._doc_items_durability) == "number" then
1010 comp[e].uses = entries[e].data.def._doc_items_durability
1011 else
1012 comp[e].uses = 0
1015 return comp[1].uses > comp[2].uses
1017 for e=1, 2 do
1018 comp[e].gc = entries[e].data.def.tool_capabilities.groupcaps
1020 -- No group capabilities = instant loser
1021 if comp[1].gc == nil then return false end
1022 if comp[2].gc == nil then return true end
1023 for e=1, 2 do
1024 local groups = {}
1025 local gc = comp[e].gc
1026 local group = nil
1027 local mintime = nil
1028 local groupcount = 0
1029 local realuses = nil
1030 for k,v in pairs(gc) do
1031 local maxlevel = v.maxlevel
1032 if maxlevel == nil then
1033 -- Default from tool.h
1034 maxlevel = 1
1036 if groupcount == 0 then
1037 group = k
1038 local uses = v.uses
1039 if v.uses == nil then
1040 -- Default from tool.h
1041 uses = 20
1043 realuses = uses * math.pow(3, maxlevel)
1045 if v.times and #v.times > 1 then
1046 for rating, time in pairs(v.times) do
1047 local realtime = time / maxlevel
1048 if mintime == nil or realtime < mintime then
1049 mintime = realtime
1052 else
1053 mintime = 0
1055 if groups[k] ~= true then
1056 groupcount = groupcount + 1
1057 groups[k] = true
1060 comp[e].count = groupcount
1061 comp[e].group = group
1062 comp[e].mintime = mintime
1063 if realuses ~= nil then
1064 comp[e].uses = realuses
1065 elseif type(entries[e].data.def._doc_items_durability) == "number" then
1066 comp[e].uses = entries[e].data.def._doc_items_durability
1067 else
1068 comp[e].uses = 0
1072 -- We want to sort out digging tools with multiple capabilities
1073 if comp[1].count > 1 and comp[1].count > comp[2].count then
1074 return false
1075 elseif comp[1].group == comp[2].group then
1076 -- Tiebreaker 1: Minimum digging time
1077 if comp[1].mintime == comp[2].mintime then
1078 -- Tiebreaker 2: Use count
1079 return comp[1].uses > comp[2].uses
1080 else
1081 return comp[1].mintime < comp[2].mintime
1083 -- Final tiebreaker: Sort by group name
1084 else
1085 if comp[1].group and comp[2].group then
1086 return comp[1].group < comp[2].group
1087 else
1088 return false
1091 end,
1092 build_formspec = function(data, playername)
1093 if data then
1094 local formstring = ""
1095 local datastring = ""
1097 formstring = entry_image(data)
1098 datastring = factoids_header(data, "tools")
1100 -- Overwritten durability info
1101 if type(data.def._doc_items_durability) == "number" then
1102 -- Fixed number of uses
1103 datastring = datastring .. S("Durability: @1 uses", data.def._doc_items_durability)
1104 datastring = newline2(datastring)
1105 elseif type(data.def._doc_items_durability) == "string" then
1106 -- Manually described durability
1107 datastring = datastring .. S("Durability: @1", data.def._doc_items_durability)
1108 datastring = newline2(datastring)
1111 datastring = datastring .. factoids_footer(data, playername, "tools")
1113 formstring = formstring .. doc.widgets.text(datastring, nil, nil, doc.FORMSPEC.ENTRY_WIDTH - 1.2)
1115 return formstring
1116 else
1117 return "label[0,1;NO DATA AVALIABLE!]"
1122 doc.add_category("craftitems", {
1123 hide_entries_by_default = true,
1124 name = S("Miscellaneous items"),
1125 description = S("Item reference of items which are neither blocks, tools or weapons (esp. crafting items)"),
1126 build_formspec = function(data, playername)
1127 if data then
1128 local formstring = ""
1129 local datastring = ""
1131 formstring = entry_image(data)
1132 datastring = factoids_header(data, "craftitems")
1133 datastring = datastring .. factoids_footer(data, playername, "craftitems")
1135 formstring = formstring .. doc.widgets.text(datastring, nil, nil, doc.FORMSPEC.ENTRY_WIDTH - 1.2)
1137 return formstring
1138 else
1139 return "label[0,1;NO DATA AVALIABLE!]"
1144 -- Register group definition stuff
1145 -- More (user-)friendly group names to replace the rather technical names
1146 -- for better understanding
1147 function doc.sub.items.add_friendly_group_names(groupnames)
1148 for internal, real in pairs(groupnames) do
1149 groupdefs[internal] = real
1153 -- Adds groups to be displayed in the generic “misc.” groups
1154 -- factoid. Those groups should be neither be used as mining
1155 -- groups nor as damage groups and should be relevant to the
1156 -- player in someway.
1157 function doc.sub.items.add_notable_groups(groupnames)
1158 for g=1,#groupnames do
1159 miscgroups[groupnames[g]] = true
1163 -- Collect information about all items
1164 local function gather_descs()
1165 -- Internal help texts for default items
1166 local help = {
1167 longdesc = {},
1168 usagehelp = {},
1171 -- 1st pass: Gather groups of interest
1172 for id, def in pairs(minetest.registered_items) do
1173 -- Gather all groups used for mining
1174 if def.tool_capabilities ~= nil then
1175 local groupcaps = def.tool_capabilities.groupcaps
1176 if groupcaps ~= nil then
1177 for k,v in pairs(groupcaps) do
1178 if mininggroups[k] ~= true then
1179 mininggroups[k] = true
1185 -- ... and gather all groups which appear in crafting recipes
1186 local crafts = minetest.get_all_craft_recipes(id)
1187 if crafts ~= nil then
1188 for c=1,#crafts do
1189 for k,v in pairs(crafts[c].items) do
1190 if string.sub(v,1,6) == "group:" then
1191 local groupstring = string.sub(v,7,-1)
1192 local groups = string.split(groupstring, ",")
1193 for g=1, #groups do
1194 miscgroups[groups[g]] = true
1201 -- ... and gather all groups used in connects_to
1202 if def.connects_to ~= nil then
1203 for c=1, #def.connects_to do
1204 if string.sub(def.connects_to[c],1,6) == "group:" then
1205 local group = string.sub(def.connects_to[c],7,-1)
1206 miscgroups[group] = true
1212 -- 2nd pass: Add entries
1214 -- Set default air text
1215 -- Custom longdesc and usagehelp may be set by mods through the add_helptexts function
1216 if minetest.registered_items["air"]._doc_items_longdesc then
1217 help.longdesc["air"] = minetest.registered_items["air"]._doc.items_longdesc
1218 else
1219 help.longdesc["air"] = S("A transparent block, basically empty space. It is usually left behind after digging something.")
1221 if minetest.registered_items["ignore"]._doc_items_create_entry ~= nil then
1222 suppressed["ignore"] = minetest.registered_items["ignore"]._doc_items_create_entry == true
1225 -- Add entry for the default tool (“hand”)
1226 -- Custom longdesc and usagehelp may be set by mods through the add_helptexts function
1227 local handdef = minetest.registered_items[""]
1228 if handdef._doc_items_create_entry ~= false then
1229 if handdef._doc_items_longdesc then
1230 help.longdesc[""] = handdef._doc_items_longdesc
1231 else
1232 -- Default text
1233 help.longdesc[""] = S("Whenever you are not wielding any item, you use the hand which acts as a tool with its own capabilities. When you are wielding an item which is not a mining tool or a weapon it will behave as if it would be the hand.")
1235 if handdef._doc_items_entry_name then
1236 item_name_overrides[""] = handdef._doc_items_entry_name
1238 doc.add_entry("tools", "", {
1239 name = item_name_overrides[""],
1240 hidden = handdef._doc_items_hidden == true,
1241 data = {
1242 longdesc = help.longdesc[""],
1243 usagehelp = help.usagehelp[""],
1244 itemstring = "",
1245 def = handdef,
1250 local add_entries = function(deftable, category_id)
1251 for id, def in pairs(deftable) do
1252 local name, ld, uh, im
1253 local forced = false
1254 if def._doc_items_create_entry == true and def ~= nil then forced = true end
1255 name = get_entry_name(id)
1256 if not (((def.description == nil or def.description == "") and def._doc_items_entry_name == nil) or (def._doc_items_create_entry == false) or (suppressed[id] == true)) or forced then
1257 if def._doc_items_longdesc then
1258 ld = def._doc_items_longdesc
1260 if help.longdesc[id] ~= nil then
1261 ld = help.longdesc[id]
1263 if def._doc_items_usagehelp then
1264 uh = def._doc_items_usagehelp
1266 if help.usagehelp[id] ~= nil then
1267 uh = help.usagehelp[id]
1269 if def._doc_items_image then
1270 im = def._doc_items_image
1272 local hidden
1273 if id == "air" or id == "" then hidden = false end
1274 if type(def._doc_items_hidden) == "boolean" then
1275 hidden = def._doc_items_hidden
1277 local custom_image
1278 name = scrub_newlines(name)
1279 local infotable = {
1280 name = name,
1281 hidden = hidden,
1282 data = {
1283 longdesc = ld,
1284 usagehelp = uh,
1285 image = im,
1286 itemstring = id,
1287 def = def,
1290 doc.add_entry(category_id, id, infotable)
1295 -- Add node entries
1296 add_entries(minetest.registered_nodes, "nodes")
1298 -- Add tool entries
1299 add_entries(minetest.registered_tools, "tools")
1301 -- Add craftitem entries
1302 add_entries(minetest.registered_craftitems, "craftitems")
1305 --[[ Reveal items as the player progresses through the game.
1306 Items are revealed by:
1307 * Digging, punching or placing node,
1308 * Crafting
1309 * Having item in inventory (not instantly revealed) ]]
1311 local function reveal_item(playername, itemstring)
1312 local category_id
1313 if itemstring == nil or itemstring == "" or playername == nil or playername == "" then
1314 return false
1316 if minetest.registered_nodes[itemstring] ~= nil then
1317 category_id = "nodes"
1318 elseif minetest.registered_tools[itemstring] ~= nil then
1319 category_id = "tools"
1320 elseif minetest.registered_craftitems[itemstring] ~= nil then
1321 category_id = "craftitems"
1322 elseif minetest.registered_items[itemstring] ~= nil then
1323 category_id = "craftitems"
1324 else
1325 return false
1327 doc.mark_entry_as_revealed(playername, category_id, itemstring)
1328 return true
1331 local function reveal_items_in_inventory(player)
1332 local inv = player:get_inventory()
1333 local list = inv:get_list("main")
1334 for l=1, #list do
1335 reveal_item(player:get_player_name(), list[l]:get_name())
1339 minetest.register_on_dignode(function(pos, oldnode, digger)
1340 if digger == nil then return end
1341 local playername = digger:get_player_name()
1342 if playername ~= nil and playername ~= "" and oldnode ~= nil then
1343 reveal_item(playername, oldnode.name)
1344 reveal_items_in_inventory(digger)
1346 end)
1348 minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing)
1349 if puncher == nil then return end
1350 local playername = puncher:get_player_name()
1351 if playername ~= nil and playername ~= "" and node ~= nil then
1352 reveal_item(playername, node.name)
1354 end)
1356 minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing)
1357 if placer == nil then return end
1358 local playername = placer:get_player_name()
1359 if playername ~= nil and playername ~= "" and itemstack ~= nil and not itemstack:is_empty() then
1360 reveal_item(playername, itemstack:get_name())
1362 end)
1364 minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv)
1365 if player == nil then return end
1366 local playername = player:get_player_name()
1367 if playername ~= nil and playername ~= "" and itemstack ~= nil and not itemstack:is_empty() then
1368 reveal_item(playername, itemstack:get_name())
1370 end)
1372 minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing)
1373 if user == nil then return end
1374 local playername = user:get_player_name()
1375 if playername ~= nil and playername ~= "" and itemstack ~= nil and not itemstack:is_empty() then
1376 reveal_item(playername, itemstack:get_name())
1377 if replace_with_item ~= nil then
1378 reveal_item(playername, replace_with_item)
1381 end)
1383 minetest.register_on_joinplayer(function(player)
1384 reveal_items_in_inventory(player)
1385 end)
1387 --[[ Periodically check all items in player inventory and reveal them all.
1388 TODO: Check whether there's a serious performance impact on servers with many players.
1389 TODO: If possible, try to replace this functionality by updating the revealed items as
1390 soon the player obtained a new item (probably needs new Minetest callbacks). ]]
1391 local checktime = 8
1392 local timer = 0
1393 minetest.register_globalstep(function(dtime)
1394 timer = timer + dtime
1395 if timer > checktime then
1396 local players = minetest.get_connected_players()
1397 for p=1, #players do
1398 reveal_items_in_inventory(players[p])
1401 timer = math.fmod(timer, checktime)
1403 end)
1405 minetest.after(0, gather_descs)