]> git.lizzy.rs Git - dragonfireclient.git/blob - builtin/game/register.lua
Predict failing placement of ignore nodes
[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
407 core.callback_origins = {}
408
409 function core.run_callbacks(callbacks, mode, ...)
410         assert(type(callbacks) == "table")
411         local cb_len = #callbacks
412         if cb_len == 0 then
413                 if mode == 2 or mode == 3 then
414                         return true
415                 elseif mode == 4 or mode == 5 then
416                         return false
417                 end
418         end
419         local ret = nil
420         for i = 1, cb_len do
421                 local origin = core.callback_origins[callbacks[i]]
422                 if origin then
423                         core.set_last_run_mod(origin.mod)
424                 end
425                 local cb_ret = callbacks[i](...)
426
427                 if mode == 0 and i == 1 then
428                         ret = cb_ret
429                 elseif mode == 1 and i == cb_len then
430                         ret = cb_ret
431                 elseif mode == 2 then
432                         if not cb_ret or i == 1 then
433                                 ret = cb_ret
434                         end
435                 elseif mode == 3 then
436                         if cb_ret then
437                                 return cb_ret
438                         end
439                         ret = cb_ret
440                 elseif mode == 4 then
441                         if (cb_ret and not ret) or i == 1 then
442                                 ret = cb_ret
443                         end
444                 elseif mode == 5 and cb_ret then
445                         return cb_ret
446                 end
447         end
448         return ret
449 end
450
451 function core.run_priv_callbacks(name, priv, caller, method)
452         local def = core.registered_privileges[priv]
453         if not def or not def["on_" .. method] or
454                         not def["on_" .. method](name, caller) then
455                 for _, func in ipairs(core["registered_on_priv_" .. method]) do
456                         if not func(name, caller, priv) then
457                                 break
458                         end
459                 end
460         end
461 end
462
463 --
464 -- Callback registration
465 --
466
467 local function make_registration()
468         local t = {}
469         local registerfunc = function(func)
470                 t[#t + 1] = func
471                 core.callback_origins[func] = {
472                         mod = core.get_current_modname() or "??",
473                         name = debug.getinfo(1, "n").name or "??"
474                 }
475                 --local origin = core.callback_origins[func]
476                 --print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
477         end
478         return t, registerfunc
479 end
480
481 local function make_registration_reverse()
482         local t = {}
483         local registerfunc = function(func)
484                 table.insert(t, 1, func)
485                 core.callback_origins[func] = {
486                         mod = core.get_current_modname() or "??",
487                         name = debug.getinfo(1, "n").name or "??"
488                 }
489                 --local origin = core.callback_origins[func]
490                 --print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
491         end
492         return t, registerfunc
493 end
494
495 local function make_registration_wrap(reg_fn_name, clear_fn_name)
496         local list = {}
497
498         local orig_reg_fn = core[reg_fn_name]
499         core[reg_fn_name] = function(def)
500                 local retval = orig_reg_fn(def)
501                 if retval ~= nil then
502                         if def.name ~= nil then
503                                 list[def.name] = def
504                         else
505                                 list[retval] = def
506                         end
507                 end
508                 return retval
509         end
510
511         local orig_clear_fn = core[clear_fn_name]
512         core[clear_fn_name] = function()
513                 for k in pairs(list) do
514                         list[k] = nil
515                 end
516                 return orig_clear_fn()
517         end
518
519         return list
520 end
521
522 local function make_wrap_deregistration(reg_fn, clear_fn, list)
523         local unregister = function (key)
524                 if type(key) ~= "string" then
525                         error("key is not a string", 2)
526                 end
527                 if not list[key] then
528                         error("Attempt to unregister non-existent element - '" .. key .. "'", 2)
529                 end
530                 local temporary_list = table.copy(list)
531                 clear_fn()
532                 for k,v in pairs(temporary_list) do
533                         if key ~= k then
534                                 reg_fn(v)
535                         end
536                 end
537         end
538         return unregister
539 end
540
541 core.registered_on_player_hpchanges = { modifiers = { }, loggers = { } }
542
543 function core.registered_on_player_hpchange(player, hp_change, reason)
544         local last
545         for i = #core.registered_on_player_hpchanges.modifiers, 1, -1 do
546                 local func = core.registered_on_player_hpchanges.modifiers[i]
547                 hp_change, last = func(player, hp_change, reason)
548                 if type(hp_change) ~= "number" then
549                         local debuginfo = debug.getinfo(func)
550                         error("The register_on_hp_changes function has to return a number at " ..
551                                 debuginfo.short_src .. " line " .. debuginfo.linedefined)
552                 end
553                 if last then
554                         break
555                 end
556         end
557         for i, func in ipairs(core.registered_on_player_hpchanges.loggers) do
558                 func(player, hp_change, reason)
559         end
560         return hp_change
561 end
562
563 function core.register_on_player_hpchange(func, modifier)
564         if modifier then
565                 core.registered_on_player_hpchanges.modifiers[#core.registered_on_player_hpchanges.modifiers + 1] = func
566         else
567                 core.registered_on_player_hpchanges.loggers[#core.registered_on_player_hpchanges.loggers + 1] = func
568         end
569         core.callback_origins[func] = {
570                 mod = core.get_current_modname() or "??",
571                 name = debug.getinfo(1, "n").name or "??"
572         }
573 end
574
575 core.registered_biomes      = make_registration_wrap("register_biome",      "clear_registered_biomes")
576 core.registered_ores        = make_registration_wrap("register_ore",        "clear_registered_ores")
577 core.registered_decorations = make_registration_wrap("register_decoration", "clear_registered_decorations")
578
579 core.unregister_biome = make_wrap_deregistration(core.register_biome,
580                 core.clear_registered_biomes, core.registered_biomes)
581
582 core.registered_on_chat_messages, core.register_on_chat_message = make_registration()
583 core.registered_on_chatcommands, core.register_on_chatcommand = make_registration()
584 core.registered_globalsteps, core.register_globalstep = make_registration()
585 core.registered_playerevents, core.register_playerevent = make_registration()
586 core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration()
587 core.registered_on_shutdown, core.register_on_shutdown = make_registration()
588 core.registered_on_punchnodes, core.register_on_punchnode = make_registration()
589 core.registered_on_placenodes, core.register_on_placenode = make_registration()
590 core.registered_on_dignodes, core.register_on_dignode = make_registration()
591 core.registered_on_generateds, core.register_on_generated = make_registration()
592 core.registered_on_newplayers, core.register_on_newplayer = make_registration()
593 core.registered_on_dieplayers, core.register_on_dieplayer = make_registration()
594 core.registered_on_respawnplayers, core.register_on_respawnplayer = make_registration()
595 core.registered_on_prejoinplayers, core.register_on_prejoinplayer = make_registration()
596 core.registered_on_joinplayers, core.register_on_joinplayer = make_registration()
597 core.registered_on_leaveplayers, core.register_on_leaveplayer = make_registration()
598 core.registered_on_player_receive_fields, core.register_on_player_receive_fields = make_registration_reverse()
599 core.registered_on_cheats, core.register_on_cheat = make_registration()
600 core.registered_on_crafts, core.register_on_craft = make_registration()
601 core.registered_craft_predicts, core.register_craft_predict = make_registration()
602 core.registered_on_protection_violation, core.register_on_protection_violation = make_registration()
603 core.registered_on_item_eats, core.register_on_item_eat = make_registration()
604 core.registered_on_punchplayers, core.register_on_punchplayer = make_registration()
605 core.registered_on_priv_grant, core.register_on_priv_grant = make_registration()
606 core.registered_on_priv_revoke, core.register_on_priv_revoke = make_registration()
607 core.registered_on_authplayers, core.register_on_authplayer = make_registration()
608 core.registered_can_bypass_userlimit, core.register_can_bypass_userlimit = make_registration()
609 core.registered_on_modchannel_message, core.register_on_modchannel_message = make_registration()
610 core.registered_on_player_inventory_actions, core.register_on_player_inventory_action = make_registration()
611 core.registered_allow_player_inventory_actions, core.register_allow_player_inventory_action = make_registration()
612 core.registered_on_rightclickplayers, core.register_on_rightclickplayer = make_registration()
613
614 --
615 -- Compatibility for on_mapgen_init()
616 --
617
618 core.register_on_mapgen_init = function(func) func(core.get_mapgen_params()) end