1 local S
= minetest
.get_translator("doc_items")
2 local N
= function(s
) return s
end
7 doc
.sub
.items
.temp
= {}
8 doc
.sub
.items
.temp
.deco
= S("This is a decorational block.")
9 doc
.sub
.items
.temp
.build
= S("This block is a building block for creating various buildings.")
10 doc
.sub
.items
.temp
.craftitem
= S("This item is primarily used for crafting other items.")
12 doc
.sub
.items
.temp
.eat
= S("Hold it in your hand, then leftclick to eat it.")
13 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?")
14 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.")
16 doc
.sub
.items
.settings
= {}
17 doc
.sub
.items
.settings
.friendly_group_names
= minetest
.settings
:get_bool("doc_items_friendly_group_names", false)
18 doc
.sub
.items
.settings
.itemstring
= minetest
.settings
:get_bool("doc_items_show_itemstrings", false)
22 local mininggroups
= {}
24 local item_name_overrides
= {
31 -- This table contains which of the builtin factoids must NOT be displayed because
32 -- they have been disabled by a mod
33 local forbidden_core_factoids
= {}
36 local yesno
= function(bool
)
37 if bool
==true then return S("Yes")
38 elseif bool
==false then return S("No")
42 local groups_to_string
= function(grouptable
, filter
)
44 local groups_count
= 0
45 for id
, value
in pairs(grouptable
) do
46 if (filter
== nil or filter
[id
] == true) then
47 -- Readable group name
48 if groups_count
> 0 then
50 gstring
= gstring
.. S(", ")
52 if groupdefs
[id
] ~= nil and doc
.sub
.items
.settings
.friendly_group_names
== true then
53 gstring
= gstring
.. groupdefs
[id
]
55 gstring
= gstring
.. id
57 groups_count
= groups_count
+ 1
60 if groups_count
== 0 then
63 return gstring
, groups_count
67 -- Removes all text after the first newline (including the newline)
68 local scrub_newlines
= function(text
)
69 local spl
= string.split(text
, "\n")
70 if spl
and #spl
> 0 then
77 --[[ Append a newline to text, unless it already ends with a newline. ]]
78 local newline
= function(text
)
79 if string.sub(text
, #text
, #text
) == "\n" or text
== "" then
86 --[[ Make sure the text ends with two newlines by appending any missing newlines at the end, if neccessary. ]]
87 local newline2
= function(text
)
88 if string.sub(text
, #text
-1, #text
) == "\n\n" or text
== "" then
90 elseif string.sub(text
, #text
, #text
) == "\n" then
98 -- Extract suitable item description for formspec
99 local description_for_formspec
= function(itemstring
)
100 if minetest
.registered_items
[itemstring
] == nil then
101 -- Huh? The item doesn't exist for some reason. Better give a dummy string
102 minetest
.log("warning", "[doc] Unknown item detected: "..tostring(itemstring
))
103 return S("Unknown item (@1)", tostring(itemstring
))
106 -- The tt mod modifies the description, we'll use the original one
107 if minetest
.registered_items
[itemstring
]._tt_original_description
then
108 description
= minetest
.registered_items
[itemstring
]._tt_original_description
110 description
= minetest
.registered_items
[itemstring
].description
112 if description
== nil or description
== "" then
113 return minetest
.formspec_escape(itemstring
)
115 return minetest
.formspec_escape(scrub_newlines(description
))
119 local get_entry_name
= function(itemstring
)
120 local def
= minetest
.registered_items
[itemstring
]
121 if def
._doc_items_entry_name
~= nil then
122 return def
._doc_items_entry_name
123 elseif item_name_overrides
[itemstring
] ~= nil then
124 return item_name_overrides
[itemstring
]
126 return def
.description
130 doc
.sub
.items
.get_group_name
= function(groupname
)
131 if groupdefs
[groupname
] ~= nil and doc
.sub
.items
.settings
.friendly_group_names
== true then
132 return groupdefs
[groupname
]
138 local burntime_to_text
= function(burntime
)
139 if burntime
== nil then
141 elseif burntime
== 1 then
144 return S("@1 seconds", burntime
)
148 --[[ Convert tool capabilities to readable text. Extracted information:
149 * Mining capabilities
150 * Durability (when mining
151 * Full punch interval
154 local factoid_toolcaps
= function(tool_capabilities
, check_uses
)
155 if forbidden_core_factoids
.tool_capabilities
then
159 local formstring
= ""
160 if check_uses
== nil then check_uses
= false end
161 if tool_capabilities
~= nil and tool_capabilities
~= {} then
162 local groupcaps
= tool_capabilities
.groupcaps
163 if groupcaps
~= nil then
164 local miningcapstr
= ""
165 local miningtimesstr
= ""
166 local miningusesstr
= ""
170 for k
,v
in pairs(groupcaps
) do
171 -- Mining capabilities
172 local minrating
, maxrating
174 for rating
, time
in pairs(v
.times
) do
175 if minrating
== nil then minrating
= rating
else
176 if minrating
> rating
then minrating
= rating
end
178 if maxrating
== nil then maxrating
= rating
else
179 if maxrating
< rating
then maxrating
= rating
end
186 local maxlevel
= v
.maxlevel
188 -- Default from tool.h
191 miningcapstr
= miningcapstr
.. S("• @1: @2", doc
.sub
.items
.get_group_name(k
), maxlevel
)
192 miningcapstr
= miningcapstr
.. "\n"
193 caplines
= caplines
+ 1
195 for rating
=3, 1, -1 do
196 if v
.times
~= nil and v
.times
[rating
] ~= nil then
197 local maxtime
= v
.times
[rating
]
199 local mintimestr
, maxtimestr
200 local maxlevel_calc
= maxlevel
201 if maxlevel_calc
< 1 then
204 mintime
= maxtime
/ maxlevel_calc
205 mintimestr
= string.format("%.1f", mintime
)
206 maxtimestr
= string.format("%.1f", maxtime
)
207 if mintimestr
~= maxtimestr
then
208 miningtimesstr
= miningtimesstr
..
209 S("• @1, rating @2: @3 s - @4 s",
210 doc
.sub
.items
.get_group_name(k
), rating
,
211 mintimestr
, maxtimestr
)
213 miningtimesstr
= miningtimesstr
..
214 S("• @1, rating @2: @3 s",
215 doc
.sub
.items
.get_group_name(k
), rating
,
218 miningtimesstr
= miningtimesstr
.. "\n"
219 timelines
= timelines
+ 1
223 -- Number of mining uses
224 local base_uses
= v
.uses
225 if not base_uses
then
226 -- Default from tool.h
229 if check_uses
and base_uses
> 0 then
230 for level
=0, maxlevel
do
231 local real_uses
= base_uses
* math
.pow(3, maxlevel
- level
)
232 if real_uses
< 65535 then
233 miningusesstr
= miningusesstr
.. S("• @1, level @2: @3 uses", doc
.sub
.items
.get_group_name(k
), level
, real_uses
)
235 miningusesstr
= miningusesstr
.. S("• @1, level @2: Unlimited", doc
.sub
.items
.get_group_name(k
), level
)
237 miningusesstr
= miningusesstr
.. "\n"
238 useslines
= useslines
+ 1
243 formstring
= formstring
.. S("This tool is capable of mining.") .. "\n"
244 formstring
= formstring
.. S("Maximum toughness levels:") .. "\n"
245 formstring
= formstring
.. miningcapstr
246 formstring
= newline(formstring
)
248 if timelines
> 0 then
249 formstring
= formstring
.. S("Mining times:") .. "\n"
250 formstring
= formstring
.. miningtimesstr
252 if useslines
> 0 then
253 formstring
= formstring
.. S("Mining durability:") .. "\n"
254 formstring
= formstring
.. miningusesstr
256 if caplines
> 0 or useslines
> 0 or timelines
> 0 then
257 formstring
= newline2(formstring
)
262 local damage_groups
= tool_capabilities
.damage_groups
263 if damage_groups
~= nil then
264 formstring
= formstring
.. S("This is a melee weapon which deals damage by punching.") .. "\n"
266 formstring
= formstring
.. S("Maximum damage per hit:") .. "\n"
267 for k
,v
in pairs(damage_groups
) do
268 formstring
= formstring
.. S("• @1: @2 HP", doc
.sub
.items
.get_group_name(k
), v
)
269 formstring
= formstring
.. "\n"
272 -- Full punch interval
274 if tool_capabilities
.full_punch_interval
~= nil then
275 punch
= tool_capabilities
.full_punch_interval
277 formstring
= formstring
.. S("Full punch interval: @1 s", string.format("%.1f", punch
))
278 formstring
= formstring
.. "\n"
285 --[[ Factoid for the mining times properties of a node. Extracted infos:
286 - dig_immediate group
287 - Digging times/groups
290 local factoid_mining_node
= function(data
)
291 if forbidden_core_factoids
.node_mining
then
295 local datastring
= ""
296 if data
.def
.pointable
~= false and (data
.def
.liquid_type
== "none" or data
.def
.liquid_type
== nil) then
297 -- Check if there are no mining groups at all
298 local nogroups
= true
299 for groupname
,_
in pairs(mininggroups
) do
300 if data
.def
.groups
[groupname
] ~= nil or groupname
== "dig_immediate" then
306 if data
.def
.drop
~= "" then
307 if data
.def
.groups
.dig_immediate
== 2 then
308 datastring
= datastring
.. S("This block can be mined by any mining tool in half a second.").."\n"
309 elseif data
.def
.groups
.dig_immediate
== 3 then
310 datastring
= datastring
.. S("This block can be mined by any mining tool immediately.").."\n"
311 -- Note: “unbreakable” is an unofficial group for undiggable blocks
312 elseif data
.def
.diggable
== false or nogroups
or data
.def
.groups
.immortal
== 1 or data
.def
.groups
.unbreakable
== 1 then
313 datastring
= datastring
.. S("This block can not be mined by ordinary mining tools.").."\n"
316 if data
.def
.groups
.dig_immediate
== 2 then
317 datastring
= datastring
.. S("This block can be destroyed by any mining tool in half a second.").."\n"
318 elseif data
.def
.groups
.dig_immediate
== 3 then
319 datastring
= datastring
.. S("This block can be destroyed by any mining tool immediately.").."\n"
320 elseif data
.def
.diggable
== false or nogroups
or data
.def
.groups
.immortal
== 1 or data
.def
.groups
.unbreakable
== 1 then
321 datastring
= datastring
.. S("This block can not be destroyed by ordinary mining tools.").."\n"
324 -- Expose “ordinary” mining groups (crumbly, cracky, etc.) and level group
325 -- Skip this for immediate digging to avoid redundancy
326 if data
.def
.groups
.dig_immediate
~= 3 then
327 local mstring
= S("This block can be mined by mining tools which match any of the following mining ratings and its toughness level.").."\n"
328 mstring
= mstring
.. S("Mining ratings:").."\n"
329 local minegroupcount
= 0
330 for group
,_
in pairs(mininggroups
) do
331 local rating
= data
.def
.groups
[group
]
332 if rating
~= nil then
333 mstring
= mstring
.. S("• @1: @2", doc
.sub
.items
.get_group_name(group
), rating
).."\n"
334 minegroupcount
= minegroupcount
+ 1
337 local level
= data
.def
.groups
.level
341 mstring
= mstring
.. S("Toughness level: @1", level
).."\n"
343 if minegroupcount
> 0 then
344 datastring
= datastring
.. mstring
351 -- Pointing range of itmes
352 local range_factoid
= function(itemstring
, def
)
353 local handrange
= minetest
.registered_items
[""].range
354 local itemrange
= def
.range
355 if itemstring
== "" then
356 if handrange
~= nil then
357 return S("Range: @1", itemrange
)
362 if handrange
== nil then handrange
= 4 end
363 if itemrange
~= nil then
364 return S("Range: @1", itemrange
)
366 return S("Range: @1 (@2)", get_entry_name(""), handrange
)
371 -- Smelting fuel factoid
372 local factoid_fuel
= function(itemstring
, ctype
)
373 if forbidden_core_factoids
.fuel
then
377 local formstring
= ""
378 local result
, decremented
= minetest
.get_craft_result({method
= "fuel", items
= {itemstring
}})
379 if result
~= nil and result
.time
> 0 then
381 local burntext
= burntime_to_text(result
.time
)
382 if ctype
== "tools" then
383 base
= S("This tool can serve as a smelting fuel with a burning time of @1.", burntext
)
384 elseif ctype
== "nodes" then
385 base
= S("This block can serve as a smelting fuel with a burning time of @1.", burntext
)
387 base
= S("This item can serve as a smelting fuel with a burning time of @1.", burntext
)
389 formstring
= formstring
.. base
390 local replaced
= decremented
.items
[1]:get_name()
391 if not decremented
.items
[1]:is_empty() and replaced
~= itemstring
then
392 formstring
= formstring
.. S(" Using it as fuel turns it into: @1.", description_for_formspec(replaced
))
394 formstring
= newline(formstring
)
399 -- Shows the itemstring of an item
400 local factoid_itemstring
= function(itemstring
, playername
)
401 if forbidden_core_factoids
.itemstring
then
405 local privs
= minetest
.get_player_privs(playername
)
406 if doc
.sub
.items
.settings
.itemstring
or (privs
.give
or privs
.debug
) then
407 return S("Itemstring: \"@1\"", itemstring
)
413 local entry_image
= function(data
)
414 local formstring
= ""
416 if data
.itemstring
~= "air" then
418 if data
.itemstring
== "" then
419 formstring
= formstring
.. "image["..(doc
.FORMSPEC
.ENTRY_END_X
-1)..","..doc
.FORMSPEC
.ENTRY_START_Y
..";1,1;"..
420 minetest
.registered_items
[""].wield_image
.."]"
422 elseif data
.image
~= nil then
423 formstring
= formstring
.. "image["..(doc
.FORMSPEC
.ENTRY_END_X
-1)..","..doc
.FORMSPEC
.ENTRY_START_Y
..";1,1;"..data
.image
.."]"
425 formstring
= formstring
.. "item_image["..(doc
.FORMSPEC
.ENTRY_END_X
-1)..","..doc
.FORMSPEC
.ENTRY_START_Y
..";1,1;"..data
.itemstring
.."]"
431 -- Stuff for factoids
432 local factoid_generators
= {}
433 factoid_generators
.nodes
= {}
434 factoid_generators
.tools
= {}
435 factoid_generators
.craftitems
= {}
437 --[[ Returns a list of all registered factoids for the specified category and type
438 * category_id: Identifier of the Documentation System category in which the factoid appears
439 * factoid_type: If set, oly returns factoid with a matching factoid_type.
440 If nil, all factoids for this category will be generated
441 * data: Entry data to parse ]]
442 local factoid_custom
= function(category_id
, factoid_type
, data
)
443 local ftable
= factoid_generators
[category_id
]
444 local datastring
= ""
445 -- Custom factoids are inserted here
447 if factoid_type
== nil or ftable
[i
].ftype
== factoid_type
then
448 datastring
= datastring
.. ftable
[i
].fgen(data
.itemstring
, data
.def
)
449 if datastring
~= "" then
450 datastring
= newline(datastring
)
457 -- Shows core information shared by all items, to be inserted at the top
458 local factoids_header
= function(data
, ctype
)
459 local datastring
= ""
460 if not forbidden_core_factoids
.basics
then
462 local longdesc
= data
.longdesc
463 local usagehelp
= data
.usagehelp
464 if longdesc
~= nil then
465 datastring
= datastring
.. S("Description: @1", longdesc
)
466 datastring
= newline2(datastring
)
468 if usagehelp
~= nil then
469 datastring
= datastring
.. S("Usage help: @1", usagehelp
)
470 datastring
= newline2(datastring
)
472 datastring
= datastring
.. factoid_custom(ctype
, "use", data
)
473 datastring
= newline2(datastring
)
475 if data
.itemstring
~= "" then
476 datastring
= datastring
.. S("Maximum stack size: @1", data
.def
.stack_max
)
477 datastring
= newline(datastring
)
479 datastring
= datastring
.. range_factoid(data
.itemstring
, data
.def
)
481 datastring
= newline2(datastring
)
483 if data
.def
.liquids_pointable
== true then
484 if ctype
== "nodes" then
485 datastring
= datastring
.. S("This block points to liquids.").."\n"
486 elseif ctype
== "tools" then
487 datastring
= datastring
.. S("This tool points to liquids.").."\n"
488 elseif ctype
== "craftitems" then
489 datastring
= datastring
.. S("This item points to liquids.").."\n"
492 if data
.def
.on_use
~= nil then
493 if ctype
== "nodes" then
494 datastring
= datastring
.. S("Punches with this block don't work as usual; melee combat and mining are either not possible or work differently.").."\n"
495 elseif ctype
== "tools" then
496 datastring
= datastring
.. S("Punches with this tool don't work as usual; melee combat and mining are either not possible or work differently.").."\n"
497 elseif ctype
== "craftitems" then
498 datastring
= datastring
.. S("Punches with this item don't work as usual; melee combat and mining are either not possible or work differently.").."\n"
504 datastring
= newline(datastring
)
506 -- Show tool capability stuff, including durability if not overwritten by custom field
507 local check_uses
= false
508 if ctype
== "tools" then
509 check_uses
= data
.def
._doc_items_durability
== nil
511 datastring
= datastring
.. factoid_toolcaps(data
.def
.tool_capabilities
, check_uses
)
512 datastring
= newline2(datastring
)
517 -- Shows less important information shared by all items, to be inserted at the bottom
518 local factoids_footer
= function(data
, playername
, ctype
)
519 local datastring
= ""
520 datastring
= datastring
.. factoid_custom(ctype
, "groups", data
)
521 datastring
= newline2(datastring
)
523 -- Show other “exposable” groups
524 if not forbidden_core_factoids
.groups
then
525 local gstring
, gcount
= groups_to_string(data
.def
.groups
, miscgroups
)
526 if gstring
~= nil then
528 if ctype
== "nodes" then
529 datastring
= datastring
.. S("This block belongs to the @1 group.", gstring
) .. "\n"
530 elseif ctype
== "tools" then
531 datastring
= datastring
.. S("This tool belongs to the @1 group.", gstring
) .. "\n"
532 elseif ctype
== "craftitems" then
533 datastring
= datastring
.. S("This item belongs to the @1 group.", gstring
) .. "\n"
536 if ctype
== "nodes" then
537 datastring
= datastring
.. S("This block belongs to these groups: @1.", gstring
) .. "\n"
538 elseif ctype
== "tools" then
539 datastring
= datastring
.. S("This tool belongs to these groups: @1.", gstring
) .. "\n"
540 elseif ctype
== "craftitems" then
541 datastring
= datastring
.. S("This item belongs to these groups: @1.", gstring
) .. "\n"
546 datastring
= newline2(datastring
)
549 datastring
= datastring
.. factoid_fuel(data
.itemstring
, ctype
)
550 datastring
= newline2(datastring
)
552 -- Other custom factoids
553 datastring
= datastring
.. factoid_custom(ctype
, "misc", data
)
554 datastring
= newline2(datastring
)
557 datastring
= datastring
.. factoid_itemstring(data
.itemstring
, playername
)
562 function doc
.sub
.items
.register_factoid(category_id
, factoid_type
, factoid_generator
)
563 local ftable
= { fgen
= factoid_generator
, ftype
= factoid_type
}
564 if category_id
== "nodes" or category_id
== "tools" or category_id
== "craftitems" then
565 table.insert(factoid_generators
[category_id
], ftable
)
567 elseif category_id
== nil then
568 table.insert(factoid_generators
.nodes
, ftable
)
569 table.insert(factoid_generators
.tools
, ftable
)
570 table.insert(factoid_generators
.craftitems
, ftable
)
575 function doc
.sub
.items
.disable_core_factoid(factoid_name
)
576 forbidden_core_factoids
[factoid_name
] = true
579 doc
.add_category("nodes", {
580 hide_entries_by_default
= true,
582 description
= S("Item reference of blocks and other things which are capable of occupying space"),
583 build_formspec
= function(data
, playername
)
585 local formstring
= ""
586 local datastring
= ""
588 formstring
= entry_image(data
)
589 datastring
= factoids_header(data
, "nodes")
591 local liquid
= data
.def
.liquidtype
~= "none" and minetest
.get_item_group(data
.itemstring
, "fake_liquid") == 0
592 if not forbidden_core_factoids
.basics
then
593 datastring
= datastring
.. S("Collidable: @1", yesno(data
.def
.walkable
)) .. "\n"
594 if data
.def
.pointable
== true then
595 datastring
= datastring
.. S("Pointable: Yes") .. "\n"
597 datastring
= datastring
.. S("Pointable: Only by special items") .. "\n"
599 datastring
= datastring
.. S("Pointable: No") .. "\n"
602 datastring
= newline2(datastring
)
603 if not forbidden_core_factoids
.liquid
and liquid
then
604 datastring
= newline(datastring
, false)
605 datastring
= datastring
.. S("This block is a liquid with these properties:") .. "\n"
606 local range
, renew
, viscos
607 if data
.def
.liquid_range
then range
= data
.def
.liquid_range
else range
= 8 end
608 if data
.def
.liquid_renewable
~= nil then renew
= data
.def
.liquid_renewable
else renew
= true end
609 if data
.def
.liquid_viscosity
then viscos
= data
.def
.liquid_viscosity
else viscos
= 0 end
611 datastring
= datastring
.. S("• Renewable") .. "\n"
613 datastring
= datastring
.. S("• Not renewable") .. "\n"
616 datastring
= datastring
.. S("• No flowing") .. "\n"
618 datastring
= datastring
.. S("• Flowing range: @1", range
) .. "\n"
620 datastring
= datastring
.. S("• Viscosity: @1", viscos
) .. "\n"
622 datastring
= newline2(datastring
)
625 --- Direct interaction with the player
626 ---- Damage (very important)
627 if not forbidden_core_factoids
.node_damage
then
628 if data
.def
.damage_per_second
~= nil and data
.def
.damage_per_second
> 1 then
629 datastring
= datastring
.. S("This block causes a damage of @1 hit points per second.", data
.def
.damage_per_second
) .. "\n"
630 elseif data
.def
.damage_per_second
== 1 then
631 datastring
= datastring
.. S("This block causes a damage of @1 hit point per second.", data
.def
.damage_per_second
) .. "\n"
633 if data
.def
.drowning
then
634 if data
.def
.drowning
> 1 then
635 datastring
= datastring
.. S("This block decreases your breath and causes a drowning damage of @1 hit points every 2 seconds.", data
.def
.drowning
) .. "\n"
636 elseif data
.def
.drowning
== 1 then
637 datastring
= datastring
.. S("This block decreases your breath and causes a drowning damage of @1 hit point every 2 seconds.", data
.def
.drowning
) .. "\n"
640 local fdap
= data
.def
.groups
.fall_damage_add_percent
641 if fdap
~= nil and fdap
~= 0 then
643 datastring
= datastring
.. S("The fall damage on this block is increased by @1%.", fdap
) .. "\n"
644 elseif fdap
<= -100 then
645 datastring
= datastring
.. S("This block negates all fall damage.") .. "\n"
647 datastring
= datastring
.. S("The fall damage on this block is reduced by @1%.", math
.abs(fdap
)) .. "\n"
651 datastring
= datastring
.. factoid_custom("nodes", "damage", data
)
652 datastring
= newline2(datastring
)
655 if not forbidden_core_factoids
.node_movement
then
656 if data
.def
.groups
.disable_jump
== 1 then
657 datastring
= datastring
.. S("You can not jump while standing on this block.").."\n"
659 if data
.def
.climbable
== true then
660 datastring
= datastring
.. S("This block can be climbed.").."\n"
662 local bouncy
= data
.def
.groups
.bouncy
663 if bouncy
~= nil and bouncy
~= 0 then
664 datastring
= datastring
.. S("This block will make you bounce off with an elasticity of @1%.", bouncy
).."\n"
666 local slippery
= data
.def
.groups
.slippery
667 if slippery
~= nil and slippery
~= 0 then
668 datastring
= datastring
.. S("This block is slippery.") .. "\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
= N("This block will drop the following items when mined: @1.")
846 if #data
.def
.drop
.items
== 1 then
847 dropstring_base
= N("This block will drop the following when mined: @1.")
849 dropstring_base
= N("This block will randomly drop one of the following when mined: @1.")
852 dropstring_base
= N("This block will randomly drop up to @1 drops of the following possible drops when mined: @2.")
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
= 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
.. S(dropstring_base
, max, dropstring
)
968 datastring
= datastring
.. S(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_player_inventory_action(function(player
, action
, inventory
, inventory_info
)
1373 if player
== nil then return end
1374 local playername
= player
:get_player_name()
1376 if action
== "take" or action
== "put" then
1377 itemstack
= inventory_info
.stack
1379 if itemstack
~= nil and playername
~= nil and playername
~= "" and (not itemstack
:is_empty()) then
1380 reveal_item(playername
, itemstack
:get_name())
1384 minetest
.register_on_item_eat(function(hp_change
, replace_with_item
, itemstack
, user
, pointed_thing
)
1385 if user
== nil then return end
1386 local playername
= user
:get_player_name()
1387 if playername
~= nil and playername
~= "" and itemstack
~= nil and not itemstack
:is_empty() then
1388 reveal_item(playername
, itemstack
:get_name())
1389 if replace_with_item
~= nil then
1390 reveal_item(playername
, replace_with_item
)
1395 minetest
.register_on_joinplayer(function(player
)
1396 reveal_items_in_inventory(player
)
1399 --[[ Periodically check all items in player inventory and reveal them all.
1400 TODO: Check whether there's a serious performance impact on servers with many players.
1401 TODO: If possible, try to replace this functionality by updating the revealed items as
1402 soon the player obtained a new item (probably needs new Minetest callbacks). ]]
1405 minetest
.register_globalstep(function(dtime
)
1406 timer
= timer
+ dtime
1407 if timer
> checktime
then
1408 local players
= minetest
.get_connected_players()
1409 for p
=1, #players
do
1410 reveal_items_in_inventory(players
[p
])
1413 timer
= math
.fmod(timer
, checktime
)
1417 minetest
.register_on_mods_loaded(gather_descs
)