1 -- Boilerplate to support localized strings if intllib mod is installed.
3 if minetest
.get_modpath("intllib") then
6 S
= function(s
,a
,...)a
={a
,...}return s
:gsub("@(%d+)",function(n
)return a
[tonumber(n
)]end)end
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
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
35 local mininggroups
= {}
37 local item_name_overrides
= {
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
= {}
50 local yesno
= function(bool
)
51 if bool
==true then return S("Yes")
52 elseif bool
==false then return S("No")
56 local groups_to_string
= function(grouptable
, filter
)
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
64 gstring
= gstring
.. S(", ")
66 if groupdefs
[id
] ~= nil and doc
.sub
.items
.settings
.friendly_group_names
== true then
67 gstring
= gstring
.. groupdefs
[id
]
69 gstring
= gstring
.. id
71 groups_count
= groups_count
+ 1
74 if groups_count
== 0 then
77 return gstring
, groups_count
81 -- Replaces all newlines with spaces
82 local scrub_newlines
= function(text
)
83 local new
, x
= string.gsub(text
, "\n", " ")
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
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
100 elseif string.sub(text
, #text
, #text
) == "\n" then
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
)
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
]
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
]
142 local burntime_to_text
= function(burntime
)
143 if burntime
== nil then
145 elseif burntime
== 1 then
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
158 local factoid_toolcaps
= function(tool_capabilities
, check_uses
)
159 if forbidden_core_factoids
.tool_capabilities
then
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
= ""
174 for k
,v
in pairs(groupcaps
) do
175 -- Mining capabilities
176 local minrating
, maxrating
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
190 local maxlevel
= v
.maxlevel
192 -- Default from tool.h
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
]
203 local mintimestr
, maxtimestr
204 local maxlevel_calc
= maxlevel
205 if maxlevel_calc
< 1 then
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
)
217 miningtimesstr
= miningtimesstr
..
218 S("• @1, rating @2: @3 s",
219 doc
.sub
.items
.get_group_name(k
), rating
,
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
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
)
239 miningusesstr
= miningusesstr
.. S("• @1, level @2: Unlimited", doc
.sub
.items
.get_group_name(k
), level
)
241 miningusesstr
= miningusesstr
.. "\n"
242 useslines
= useslines
+ 1
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
)
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"
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
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"
289 --[[ Factoid for the mining times properties of a node. Extracted infos:
290 - dig_immediate group
291 - Digging times/groups
294 local factoid_mining_node
= function(data
)
295 if forbidden_core_factoids
.node_mining
then
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
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"
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
345 mstring
= mstring
.. S("Toughness level: @1", level
).."\n"
347 if minegroupcount
> 0 then
348 datastring
= datastring
.. mstring
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
)
366 if handrange
== nil then handrange
= 4 end
367 if itemrange
~= nil then
368 return S("Range: @1", itemrange
)
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
381 local formstring
= ""
382 local result
, decremented
= minetest
.get_craft_result({method
= "fuel", items
= {itemstring
}})
383 if result
~= nil and result
.time
> 0 then
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
)
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
)
403 -- Shows the itemstring of an item
404 local factoid_itemstring
= function(itemstring
, playername
)
405 if forbidden_core_factoids
.itemstring
then
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
)
417 local entry_image
= function(data
)
418 local formstring
= ""
420 if data
.itemstring
~= "air" then
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
.."]"
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
.."]"
429 formstring
= formstring
.. "item_image["..(doc
.FORMSPEC
.ENTRY_END_X
-1)..","..doc
.FORMSPEC
.ENTRY_START_Y
..";1,1;"..data
.itemstring
.."]"
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
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
)
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
)
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
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"
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
)
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
)
561 datastring
= datastring
.. factoid_itemstring(data
.itemstring
, playername
)
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
)
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
)
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,
586 description
= S("Item reference of blocks and other things which are capable of occupying space"),
587 build_formspec
= function(data
, playername
)
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"
601 datastring
= datastring
.. S("Pointable: Only by special items") .. "\n"
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
615 datastring
= datastring
.. S("• Renewable") .. "\n"
617 datastring
= datastring
.. S("• Not renewable") .. "\n"
620 datastring
= datastring
.. S("• No flowing") .. "\n"
622 datastring
= datastring
.. S("• Flowing range: @1", range
) .. "\n"
624 datastring
= datastring
.. S("• Viscosity: @1", viscos
) .. "\n"
626 datastring
= newline2(datastring
)
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
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"
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
)
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
)
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
683 if data
.def
.diggable
and is_silent(data
.def
, "dig") and is_silent(data
.def
, "dug") then
686 if is_silent(data
.def
, "place") and is_silent(data
.def
, "place_failed") and data
.itemstring
~= "air" then
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"
695 datastring
= datastring
.. S("Walking on this block is completely silent.").."\n"
698 datastring
= datastring
.. S("Mining this block is completely silent.").."\n"
701 datastring
= datastring
.. S("Building this block is completely silent.").."\n"
705 datastring
= datastring
.. factoid_custom("nodes", "sound", data
)
706 datastring
= newline2(datastring
)
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"
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"
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
)
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
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
775 table.insert(nodes
, itemstring
)
782 if item_name_overrides
[nodes
[n]]
~= nil then
783 name
= item_name_overrides
[nodes
[n]]
785 name
= description_for_formspec(nodes
[n
])
788 nstring
= nstring
.. S(", ")
791 nstring
= nstring
.. name
793 nstring
= nstring
.. S("Unknown Node")
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
)
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
)
813 datastring
= datastring
.. factoid_custom("nodes", "mining", data
)
815 datastring
= newline(datastring
)
817 datastring
= datastring
.. factoid_mining_node(data
)
818 datastring
= newline2(datastring
)
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()
834 datastring
= datastring
.. S("This block will drop the following when mined: @1×@2.", count
, desc
).."\n"
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
= ""
844 dropstring_base
= S("This block will drop the following items when mined: %s.")
846 if #data
.def
.drop
.items
== 1 then
847 dropstring_base
= S("This block will drop the following when mined: %s.")
849 dropstring_base
= S("This block will randomly drop one of the following when mined: %s.")
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
= {}
857 local rarity_history
= {}
858 for i
=1,#data
.def
.drop
.items
do
859 local local_rarity
= data
.def
.drop
.items
[i
].rarity
862 if local_rarity
== nil then
866 -- Chained probability
867 table.insert(rarity_history
, local_rarity
)
869 for r
=1, #rarity_history
do
871 if r
> 1 and rarity_history
[r
-1] == 1 then
875 if r
== #rarity_history
then
876 chance_factor
= 1/rarity_history
[r
]
878 chance_factor
= (rarity_history
[r
]-1)/rarity_history
[r
]
880 chance
= chance
* chance_factor
886 rarity
= local_rarity
889 -- Exclude impossible drops
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
}
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
915 local comp
= function(p1
, p2
)
916 return p1
.rarity
< p2
.rarity
918 table.sort(probtables
, comp
)
920 -- Output probability table
922 for i
=1, #probtables
do
925 dropstring
= dropstring
.. S(", ")
927 local probtable
= probtables
[i
]
929 local dropstring_this
= ""
930 for _
, itemtable
in pairs(probtable
.items
) do
932 -- Final list seperator
933 dropstring_this
= dropstring_this
.. S(" and ")
935 local desc
= S(itemtable
.desc
)
936 local count
= itemtable
.count
938 desc
= S("@1×@2", count
, desc
)
940 dropstring_this
= dropstring_this
.. desc
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
)
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
)
958 dropstring_this
= S("@1 (@2%)", dropstring_this
, fchance
)
962 dropstring
= dropstring
.. dropstring_this
965 if max ~= nil and max > 1 then
966 datastring
= datastring
.. string.format(dropstring_base
, max, dropstring
)
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)
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
}
996 if entries
[1].eid
== "" then return true end
997 if entries
[2].eid
== "" then return false end
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
1009 if type(entries
[e
].data
.def
._doc_items_durability
) == "number" then
1010 comp
[e
].uses
= entries
[e
].data
.def
._doc_items_durability
1015 return comp
[1].uses
> comp
[2].uses
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
1025 local gc
= comp
[e
].gc
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
1036 if groupcount
== 0 then
1039 if v
.uses
== nil then
1040 -- Default from tool.h
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
1055 if groups
[k
] ~= true then
1056 groupcount
= groupcount
+ 1
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
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
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
1081 return comp
[1].mintime
< comp
[2].mintime
1083 -- Final tiebreaker: Sort by group name
1085 if comp
[1].group
and comp
[2].group
then
1086 return comp
[1].group
< comp
[2].group
1092 build_formspec
= function(data
, playername
)
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)
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
)
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)
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
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
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
, ",")
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
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
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,
1242 longdesc
= help
.longdesc
[""],
1243 usagehelp
= help
.usagehelp
[""],
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
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
1278 name
= scrub_newlines(name
)
1290 doc
.add_entry(category_id
, id
, infotable
)
1296 add_entries(minetest
.registered_nodes
, "nodes")
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,
1309 * Having item in inventory (not instantly revealed) ]]
1311 local function reveal_item(playername
, itemstring
)
1313 if itemstring
== nil or itemstring
== "" or playername
== nil or playername
== "" then
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"
1327 doc
.mark_entry_as_revealed(playername
, category_id
, itemstring
)
1331 local function reveal_items_in_inventory(player
)
1332 local inv
= player
:get_inventory()
1333 local list
= inv
:get_list("main")
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
)
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
)
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())
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())
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
)
1383 minetest
.register_on_joinplayer(function(player
)
1384 reveal_items_in_inventory(player
)
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). ]]
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
)
1405 minetest
.after(0, gather_descs
)