]> git.lizzy.rs Git - dragonfireclient.git/blob - builtin/game/register.lua
Fix Minetest blaming the wrong mod for errors (#12241)
[dragonfireclient.git] / builtin / game / register.lua
1 -- Minetest: builtin/misc_register.lua
2
3 local S = core.get_translator("__builtin")
4
5 --
6 -- Make raw registration functions inaccessible to anyone except this file
7 --
8
9 local register_item_raw = core.register_item_raw
10 core.register_item_raw = nil
11
12 local unregister_item_raw = core.unregister_item_raw
13 core.unregister_item_raw = nil
14
15 local register_alias_raw = core.register_alias_raw
16 core.register_alias_raw = nil
17
18 --
19 -- Item / entity / ABM / LBM registration functions
20 --
21
22 core.registered_abms = {}
23 core.registered_lbms = {}
24 core.registered_entities = {}
25 core.registered_items = {}
26 core.registered_nodes = {}
27 core.registered_craftitems = {}
28 core.registered_tools = {}
29 core.registered_aliases = {}
30
31 -- For tables that are indexed by item name:
32 -- If table[X] does not exist, default to table[core.registered_aliases[X]]
33 local alias_metatable = {
34         __index = function(t, name)
35                 return rawget(t, core.registered_aliases[name])
36         end
37 }
38 setmetatable(core.registered_items, alias_metatable)
39 setmetatable(core.registered_nodes, alias_metatable)
40 setmetatable(core.registered_craftitems, alias_metatable)
41 setmetatable(core.registered_tools, alias_metatable)
42
43 -- These item names may not be used because they would interfere
44 -- with legacy itemstrings
45 local forbidden_item_names = {
46         MaterialItem = true,
47         MaterialItem2 = true,
48         MaterialItem3 = true,
49         NodeItem = true,
50         node = true,
51         CraftItem = true,
52         craft = true,
53         MBOItem = true,
54         ToolItem = true,
55         tool = true,
56 }
57
58 local function check_modname_prefix(name)
59         if name:sub(1,1) == ":" then
60                 -- If the name starts with a colon, we can skip the modname prefix
61                 -- mechanism.
62                 return name:sub(2)
63         else
64                 -- Enforce that the name starts with the correct mod name.
65                 local expected_prefix = core.get_current_modname() .. ":"
66                 if name:sub(1, #expected_prefix) ~= expected_prefix then
67                         error("Name " .. name .. " does not follow naming conventions: " ..
68                                 "\"" .. expected_prefix .. "\" or \":\" prefix required")
69                 end
70
71                 -- Enforce that the name only contains letters, numbers and underscores.
72                 local subname = name:sub(#expected_prefix+1)
73                 if subname:find("[^%w_]") then
74                         error("Name " .. name .. " does not follow naming conventions: " ..
75                                 "contains unallowed characters")
76                 end
77
78                 return name
79         end
80 end
81
82 function core.register_abm(spec)
83         -- Add to core.registered_abms
84         assert(type(spec.action) == "function", "Required field 'action' of type function")
85         core.registered_abms[#core.registered_abms + 1] = spec
86         spec.mod_origin = core.get_current_modname() or "??"
87 end
88
89 function core.register_lbm(spec)
90         -- Add to core.registered_lbms
91         check_modname_prefix(spec.name)
92         assert(type(spec.action) == "function", "Required field 'action' of type function")
93         core.registered_lbms[#core.registered_lbms + 1] = spec
94         spec.mod_origin = core.get_current_modname() or "??"
95 end
96
97 function core.register_entity(name, prototype)
98         -- Check name
99         if name == nil then
100                 error("Unable to register entity: Name is nil")
101         end
102         name = check_modname_prefix(tostring(name))
103
104         prototype.name = name
105         prototype.__index = prototype  -- so that it can be used as a metatable
106
107         -- Add to core.registered_entities
108         core.registered_entities[name] = prototype
109         prototype.mod_origin = core.get_current_modname() or "??"
110 end
111
112 function core.register_item(name, itemdef)
113         -- Check name
114         if name == nil then
115                 error("Unable to register item: Name is nil")
116         end
117         name = check_modname_prefix(tostring(name))
118         if forbidden_item_names[name] then
119                 error("Unable to register item: Name is forbidden: " .. name)
120         end
121         itemdef.name = name
122
123         -- Apply defaults and add to registered_* table
124         if itemdef.type == "node" then
125                 -- Use the nodebox as selection box if it's not set manually
126                 if itemdef.drawtype == "nodebox" and not itemdef.selection_box then
127                         itemdef.selection_box = itemdef.node_box
128                 elseif itemdef.drawtype == "fencelike" and not itemdef.selection_box then
129                         itemdef.selection_box = {
130                                 type = "fixed",
131                                 fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8},
132                         }
133                 end
134                 if itemdef.light_source and itemdef.light_source > core.LIGHT_MAX then
135                         itemdef.light_source = core.LIGHT_MAX
136                         core.log("warning", "Node 'light_source' value exceeds maximum," ..
137                                 " limiting to maximum: " ..name)
138                 end
139                 setmetatable(itemdef, {__index = core.nodedef_default})
140                 core.registered_nodes[itemdef.name] = itemdef
141         elseif itemdef.type == "craft" then
142                 setmetatable(itemdef, {__index = core.craftitemdef_default})
143                 core.registered_craftitems[itemdef.name] = itemdef
144         elseif itemdef.type == "tool" then
145                 setmetatable(itemdef, {__index = core.tooldef_default})
146                 core.registered_tools[itemdef.name] = itemdef
147         elseif itemdef.type == "none" then
148                 setmetatable(itemdef, {__index = core.noneitemdef_default})
149         else
150                 error("Unable to register item: Type is invalid: " .. dump(itemdef))
151         end
152
153         -- Flowing liquid uses param2
154         if itemdef.type == "node" and itemdef.liquidtype == "flowing" then
155                 itemdef.paramtype2 = "flowingliquid"
156         end
157
158         -- BEGIN Legacy stuff
159         if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then
160                 core.register_craft({
161                         type="cooking",
162                         output=itemdef.cookresult_itemstring,
163                         recipe=itemdef.name,
164                         cooktime=itemdef.furnace_cooktime
165                 })
166         end
167         if itemdef.furnace_burntime ~= nil and itemdef.furnace_burntime >= 0 then
168                 core.register_craft({
169                         type="fuel",
170                         recipe=itemdef.name,
171                         burntime=itemdef.furnace_burntime
172                 })
173         end
174         -- END Legacy stuff
175
176         itemdef.mod_origin = core.get_current_modname() or "??"
177
178         -- Disable all further modifications
179         getmetatable(itemdef).__newindex = {}
180
181         --core.log("Registering item: " .. itemdef.name)
182         core.registered_items[itemdef.name] = itemdef
183         core.registered_aliases[itemdef.name] = nil
184         register_item_raw(itemdef)
185 end
186
187 function core.unregister_item(name)
188         if not core.registered_items[name] then
189                 core.log("warning", "Not unregistering item " ..name..
190                         " because it doesn't exist.")
191                 return
192         end
193         -- Erase from registered_* table
194         local type = core.registered_items[name].type
195         if type == "node" then
196                 core.registered_nodes[name] = nil
197         elseif type == "craft" then
198                 core.registered_craftitems[name] = nil
199         elseif type == "tool" then
200                 core.registered_tools[name] = nil
201         end
202         core.registered_items[name] = nil
203
204
205         unregister_item_raw(name)
206 end
207
208 function core.register_node(name, nodedef)
209         nodedef.type = "node"
210         core.register_item(name, nodedef)
211 end
212
213 function core.register_craftitem(name, craftitemdef)
214         craftitemdef.type = "craft"
215
216         -- BEGIN Legacy stuff
217         if craftitemdef.inventory_image == nil and craftitemdef.image ~= nil then
218                 craftitemdef.inventory_image = craftitemdef.image
219         end
220         -- END Legacy stuff
221
222         core.register_item(name, craftitemdef)
223 end
224
225 function core.register_tool(name, tooldef)
226         tooldef.type = "tool"
227         tooldef.stack_max = 1
228
229         -- BEGIN Legacy stuff
230         if tooldef.inventory_image == nil and tooldef.image ~= nil then
231                 tooldef.inventory_image = tooldef.image
232         end
233         if tooldef.tool_capabilities == nil and
234            (tooldef.full_punch_interval ~= nil or
235             tooldef.basetime ~= nil or
236             tooldef.dt_weight ~= nil or
237             tooldef.dt_crackiness ~= nil or
238             tooldef.dt_crumbliness ~= nil or
239             tooldef.dt_cuttability ~= nil or
240             tooldef.basedurability ~= nil or
241             tooldef.dd_weight ~= nil or
242             tooldef.dd_crackiness ~= nil or
243             tooldef.dd_crumbliness ~= nil or
244             tooldef.dd_cuttability ~= nil) then
245                 tooldef.tool_capabilities = {
246                         full_punch_interval = tooldef.full_punch_interval,
247                         basetime = tooldef.basetime,
248                         dt_weight = tooldef.dt_weight,
249                         dt_crackiness = tooldef.dt_crackiness,
250                         dt_crumbliness = tooldef.dt_crumbliness,
251                         dt_cuttability = tooldef.dt_cuttability,
252                         basedurability = tooldef.basedurability,
253                         dd_weight = tooldef.dd_weight,
254                         dd_crackiness = tooldef.dd_crackiness,
255                         dd_crumbliness = tooldef.dd_crumbliness,
256                         dd_cuttability = tooldef.dd_cuttability,
257                 }
258         end
259         -- END Legacy stuff
260
261         -- This isn't just legacy, but more of a convenience feature
262         local toolcaps = tooldef.tool_capabilities
263         if toolcaps and toolcaps.punch_attack_uses == nil then
264                 for _, cap in pairs(toolcaps.groupcaps or {}) do
265                         local level = (cap.maxlevel or 0) - 1
266                         if (cap.uses or 0) ~= 0 and level >= 0 then
267                                 toolcaps.punch_attack_uses = cap.uses * (3 ^ level)
268                                 break
269                         end
270                 end
271         end
272
273         core.register_item(name, tooldef)
274 end
275
276 function core.register_alias(name, convert_to)
277         if forbidden_item_names[name] then
278                 error("Unable to register alias: Name is forbidden: " .. name)
279         end
280         if core.registered_items[name] ~= nil then
281                 core.log("warning", "Not registering alias, item with same name" ..
282                         " is already defined: " .. name .. " -> " .. convert_to)
283         else
284                 --core.log("Registering alias: " .. name .. " -> " .. convert_to)
285                 core.registered_aliases[name] = convert_to
286                 register_alias_raw(name, convert_to)
287         end
288 end
289
290 function core.register_alias_force(name, convert_to)
291         if forbidden_item_names[name] then
292                 error("Unable to register alias: Name is forbidden: " .. name)
293         end
294         if core.registered_items[name] ~= nil then
295                 core.unregister_item(name)
296                 core.log("info", "Removed item " ..name..
297                         " while attempting to force add an alias")
298         end
299         --core.log("Registering alias: " .. name .. " -> " .. convert_to)
300         core.registered_aliases[name] = convert_to
301         register_alias_raw(name, convert_to)
302 end
303
304 function core.on_craft(itemstack, player, old_craft_list, craft_inv)
305         for _, func in ipairs(core.registered_on_crafts) do
306                 itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack
307         end
308         return itemstack
309 end
310
311 function core.craft_predict(itemstack, player, old_craft_list, craft_inv)
312         for _, func in ipairs(core.registered_craft_predicts) do
313                 itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack
314         end
315         return itemstack
316 end
317
318 -- Alias the forbidden item names to "" so they can't be
319 -- created via itemstrings (e.g. /give)
320 for name in pairs(forbidden_item_names) do
321         core.registered_aliases[name] = ""
322         register_alias_raw(name, "")
323 end
324
325 --
326 -- Built-in node definitions. Also defined in C.
327 --
328
329 core.register_item(":unknown", {
330         type = "none",
331         description = S("Unknown Item"),
332         inventory_image = "unknown_item.png",
333         on_place = core.item_place,
334         on_secondary_use = core.item_secondary_use,
335         on_drop = core.item_drop,
336         groups = {not_in_creative_inventory=1},
337         diggable = true,
338 })
339
340 core.register_node(":air", {
341         description = S("Air"),
342         inventory_image = "air.png",
343         wield_image = "air.png",
344         drawtype = "airlike",
345         paramtype = "light",
346         sunlight_propagates = true,
347         walkable = false,
348         pointable = false,
349         diggable = false,
350         buildable_to = true,
351         floodable = true,
352         air_equivalent = true,
353         drop = "",
354         groups = {not_in_creative_inventory=1},
355 })
356
357 core.register_node(":ignore", {
358         description = S("Ignore"),
359         inventory_image = "ignore.png",
360         wield_image = "ignore.png",
361         drawtype = "airlike",
362         paramtype = "none",
363         sunlight_propagates = false,
364         walkable = false,
365         pointable = false,
366         diggable = false,
367         buildable_to = true, -- A way to remove accidentally placed ignores
368         air_equivalent = true,
369         drop = "",
370         groups = {not_in_creative_inventory=1},
371         node_placement_prediction = "",
372         on_place = function(itemstack, placer, pointed_thing)
373                 core.chat_send_player(
374                                 placer:get_player_name(),
375                                 core.colorize("#FF0000",
376                                 S("You can't place 'ignore' nodes!")))
377                 return ""
378         end,
379 })
380
381 -- The hand (bare definition)
382 core.register_item(":", {
383         type = "none",
384         wield_image = "wieldhand.png",
385         groups = {not_in_creative_inventory=1},
386 })
387
388
389 function core.override_item(name, redefinition)
390         if redefinition.name ~= nil then
391                 error("Attempt to redefine name of "..name.." to "..dump(redefinition.name), 2)
392         end
393         if redefinition.type ~= nil then
394                 error("Attempt to redefine type of "..name.." to "..dump(redefinition.type), 2)
395         end
396         local item = core.registered_items[name]
397         if not item then
398                 error("Attempt to override non-existent item "..name, 2)
399         end
400         for k, v in pairs(redefinition) do
401                 rawset(item, k, v)
402         end
403         register_item_raw(item)
404 end
405
406 do
407         local default = {mod = "??", name = "??"}
408         core.callback_origins = setmetatable({}, {
409                 __index = function()
410                         return default
411                 end
412         })
413 end
414
415 function core.run_callbacks(callbacks, mode, ...)
416         assert(type(callbacks) == "table")
417         local cb_len = #callbacks
418         if cb_len == 0 then
419                 if mode == 2 or mode == 3 then
420                         return true
421                 elseif mode == 4 or mode == 5 then
422                         return false
423                 end
424         end
425         local ret = nil
426         for i = 1, cb_len do
427                 local origin = core.callback_origins[callbacks[i]]
428                 core.set_last_run_mod(origin.mod)
429                 local cb_ret = callbacks[i](...)
430
431                 if mode == 0 and i == 1 then
432                         ret = cb_ret
433                 elseif mode == 1 and i == cb_len then
434                         ret = cb_ret
435                 elseif mode == 2 then
436                         if not cb_ret or i == 1 then
437                                 ret = cb_ret
438                         end
439                 elseif mode == 3 then
440                         if cb_ret then
441                                 return cb_ret
442                         end
443                         ret = cb_ret
444                 elseif mode == 4 then
445                         if (cb_ret and not ret) or i == 1 then
446                                 ret = cb_ret
447                         end
448                 elseif mode == 5 and cb_ret then
449                         return cb_ret
450                 end
451         end
452         return ret
453 end
454
455 function core.run_priv_callbacks(name, priv, caller, method)
456         local def = core.registered_privileges[priv]
457         if not def or not def["on_" .. method] or
458                         not def["on_" .. method](name, caller) then
459                 for _, func in ipairs(core["registered_on_priv_" .. method]) do
460                         if not func(name, caller, priv) then
461                                 break
462                         end
463                 end
464         end
465 end
466
467 --
468 -- Callback registration
469 --
470
471 local function make_registration()
472         local t = {}
473         local registerfunc = function(func)
474                 t[#t + 1] = func
475                 core.callback_origins[func] = {
476                         mod = core.get_current_modname() or "??",
477                         name = debug.getinfo(1, "n").name or "??"
478                 }
479                 --local origin = core.callback_origins[func]
480                 --print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
481         end
482         return t, registerfunc
483 end
484
485 local function make_registration_reverse()
486         local t = {}
487         local registerfunc = function(func)
488                 table.insert(t, 1, func)
489                 core.callback_origins[func] = {
490                         mod = core.get_current_modname() or "??",
491                         name = debug.getinfo(1, "n").name or "??"
492                 }
493                 --local origin = core.callback_origins[func]
494                 --print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
495         end
496         return t, registerfunc
497 end
498
499 local function make_registration_wrap(reg_fn_name, clear_fn_name)
500         local list = {}
501
502         local orig_reg_fn = core[reg_fn_name]
503         core[reg_fn_name] = function(def)
504                 local retval = orig_reg_fn(def)
505                 if retval ~= nil then
506                         if def.name ~= nil then
507                                 list[def.name] = def
508                         else
509                                 list[retval] = def
510                         end
511                 end
512                 return retval
513         end
514
515         local orig_clear_fn = core[clear_fn_name]
516         core[clear_fn_name] = function()
517                 for k in pairs(list) do
518                         list[k] = nil
519                 end
520                 return orig_clear_fn()
521         end
522
523         return list
524 end
525
526 local function make_wrap_deregistration(reg_fn, clear_fn, list)
527         local unregister = function (key)
528                 if type(key) ~= "string" then
529                         error("key is not a string", 2)
530                 end
531                 if not list[key] then
532                         error("Attempt to unregister non-existent element - '" .. key .. "'", 2)
533                 end
534                 local temporary_list = table.copy(list)
535                 clear_fn()
536                 for k,v in pairs(temporary_list) do
537                         if key ~= k then
538                                 reg_fn(v)
539                         end
540                 end
541         end
542         return unregister
543 end
544
545 core.registered_on_player_hpchanges = { modifiers = { }, loggers = { } }
546
547 function core.registered_on_player_hpchange(player, hp_change, reason)
548         local last
549         for i = #core.registered_on_player_hpchanges.modifiers, 1, -1 do
550                 local func = core.registered_on_player_hpchanges.modifiers[i]
551                 hp_change, last = func(player, hp_change, reason)
552                 if type(hp_change) ~= "number" then
553                         local debuginfo = debug.getinfo(func)
554                         error("The register_on_hp_changes function has to return a number at " ..
555                                 debuginfo.short_src .. " line " .. debuginfo.linedefined)
556                 end
557                 if last then
558                         break
559                 end
560         end
561         for i, func in ipairs(core.registered_on_player_hpchanges.loggers) do
562                 func(player, hp_change, reason)
563         end
564         return hp_change
565 end
566
567 function core.register_on_player_hpchange(func, modifier)
568         if modifier then
569                 core.registered_on_player_hpchanges.modifiers[#core.registered_on_player_hpchanges.modifiers + 1] = func
570         else
571                 core.registered_on_player_hpchanges.loggers[#core.registered_on_player_hpchanges.loggers + 1] = func
572         end
573         core.callback_origins[func] = {
574                 mod = core.get_current_modname() or "??",
575                 name = debug.getinfo(1, "n").name or "??"
576         }
577 end
578
579 core.registered_biomes      = make_registration_wrap("register_biome",      "clear_registered_biomes")
580 core.registered_ores        = make_registration_wrap("register_ore",        "clear_registered_ores")
581 core.registered_decorations = make_registration_wrap("register_decoration", "clear_registered_decorations")
582
583 core.unregister_biome = make_wrap_deregistration(core.register_biome,
584                 core.clear_registered_biomes, core.registered_biomes)
585
586 core.registered_on_chat_messages, core.register_on_chat_message = make_registration()
587 core.registered_on_chatcommands, core.register_on_chatcommand = make_registration()
588 core.registered_globalsteps, core.register_globalstep = make_registration()
589 core.registered_playerevents, core.register_playerevent = make_registration()
590 core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration()
591 core.registered_on_shutdown, core.register_on_shutdown = make_registration()
592 core.registered_on_punchnodes, core.register_on_punchnode = make_registration()
593 core.registered_on_placenodes, core.register_on_placenode = make_registration()
594 core.registered_on_dignodes, core.register_on_dignode = make_registration()
595 core.registered_on_generateds, core.register_on_generated = make_registration()
596 core.registered_on_newplayers, core.register_on_newplayer = make_registration()
597 core.registered_on_dieplayers, core.register_on_dieplayer = make_registration()
598 core.registered_on_respawnplayers, core.register_on_respawnplayer = make_registration()
599 core.registered_on_prejoinplayers, core.register_on_prejoinplayer = make_registration()
600 core.registered_on_joinplayers, core.register_on_joinplayer = make_registration()
601 core.registered_on_leaveplayers, core.register_on_leaveplayer = make_registration()
602 core.registered_on_player_receive_fields, core.register_on_player_receive_fields = make_registration_reverse()
603 core.registered_on_cheats, core.register_on_cheat = make_registration()
604 core.registered_on_crafts, core.register_on_craft = make_registration()
605 core.registered_craft_predicts, core.register_craft_predict = make_registration()
606 core.registered_on_protection_violation, core.register_on_protection_violation = make_registration()
607 core.registered_on_item_eats, core.register_on_item_eat = make_registration()
608 core.registered_on_punchplayers, core.register_on_punchplayer = make_registration()
609 core.registered_on_priv_grant, core.register_on_priv_grant = make_registration()
610 core.registered_on_priv_revoke, core.register_on_priv_revoke = make_registration()
611 core.registered_on_authplayers, core.register_on_authplayer = make_registration()
612 core.registered_can_bypass_userlimit, core.register_can_bypass_userlimit = make_registration()
613 core.registered_on_modchannel_message, core.register_on_modchannel_message = make_registration()
614 core.registered_on_player_inventory_actions, core.register_on_player_inventory_action = make_registration()
615 core.registered_allow_player_inventory_actions, core.register_allow_player_inventory_action = make_registration()
616 core.registered_on_rightclickplayers, core.register_on_rightclickplayer = make_registration()
617 core.registered_on_liquid_transformed, core.register_on_liquid_transformed = make_registration()
618
619 --
620 -- Compatibility for on_mapgen_init()
621 --
622
623 core.register_on_mapgen_init = function(func) func(core.get_mapgen_params()) end