init.lua 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. -- ZCG mod for minetest
  2. -- See README for more information
  3. -- Released by Zeg9 under WTFPL
  4. if not minetest.global_exists("zcg") then zcg = {} end
  5. zcg.modpath = minetest.get_modpath("zcg")
  6. zcg.users = zcg.users or {}
  7. zcg.crafts = zcg.crafts or {}
  8. zcg.itemlist = zcg.itemlist or {}
  9. zcg.grouplist = zcg.grouplist or {}
  10. -- Localize for performance.
  11. local math_floor = math.floor
  12. -- Keep this code for future debugging if we use new groups in recipes.
  13. --[[
  14. minetest.after(5, function()
  15. for k, v in pairs(zcg.grouplist) do
  16. minetest.chat_send_all(k)
  17. end
  18. end)
  19. --]]
  20. zcg.items_in_group = function(group)
  21. local items = {}
  22. local ok = true
  23. for name, item in pairs(minetest.registered_items) do
  24. -- the node should be in all groups
  25. ok = true
  26. for _, g in ipairs(group:split(',')) do
  27. if not item.groups[g] then
  28. ok = false
  29. end
  30. end
  31. if ok then table.insert(items,name) end
  32. end
  33. return items
  34. end
  35. function zcg.canonical_group_item(group)
  36. -- Hopefully we already know about this group.
  37. if zcg.grouplist[group] then
  38. return zcg.grouplist[group]
  39. end
  40. -- Otherwise, we have to fall back to using the first item that has this group,
  41. -- that we can find. This is less than ideal but still meaningful. Sort of.
  42. local items = zcg.items_in_group(group)
  43. if items and items[1] then
  44. zcg.grouplist[group] = {
  45. item = items[1],
  46. description = nil,
  47. }
  48. return zcg.grouplist[group]
  49. end
  50. -- Must not be the name of a node, but cannot be an empty string.
  51. -- This really should not happen, but we have to return *something* to avoid a crash.
  52. zcg.grouplist[group] = {
  53. item = "unknown",
  54. description = nil,
  55. }
  56. return zcg.grouplist[group]
  57. end
  58. local table_copy = function(table)
  59. local out = {}
  60. for k,v in pairs(table) do
  61. out[k] = v
  62. end
  63. return out
  64. end
  65. zcg.add_craft = function(input, realout, output, groups)
  66. if minetest.get_item_group(output, "not_in_craft_guide") > 0 then
  67. return
  68. end
  69. if not groups then groups = {} end
  70. local c = {}
  71. c.width = input.width
  72. c.type = input.type
  73. c.items = input.items
  74. if type(realout) == "string" then
  75. c.result = realout
  76. elseif type(realout) == "table" then
  77. --minetest.log(dump(realout))
  78. if type(realout.output) == "string" then
  79. c.result = realout.output
  80. elseif type(realout.output) == "table" then
  81. -- Recipe output should be two items (separating recipe).
  82. assert(type(realout.output[1]) == "string")
  83. assert(type(realout.output[2]) == "string")
  84. c.result = table.copy(realout.output)
  85. end
  86. end
  87. assert(type(c.result) == "string" or type(c.result) == "table")
  88. if c.items == nil then return end
  89. --[[
  90. for i, item in pairs(c.items) do
  91. if item:sub(0,6) == "group:" then
  92. -- The recipe item name may contain a count value.
  93. -- We must extract just the name after the "group:".
  94. local strpart = item:sub(7)
  95. local parts = string.split(strpart, " ")
  96. assert(type(parts[1]) == "string")
  97. local groupname = parts[1]
  98. -- For testing.
  99. zcg.canonical_group_item(groupname)
  100. local groupcount = parts[2] or 1
  101. if groups[groupname] ~= nil then
  102. c.items[i] = groups[groupname] .. " " .. groupcount
  103. else
  104. for _, gi in ipairs(zcg.items_in_group(groupname)) do
  105. local g2 = groups
  106. g2[groupname] = gi
  107. zcg.add_craft({
  108. width = c.width,
  109. type = c.type,
  110. items = table_copy(c.items)
  111. }, realout, output, g2) -- it is needed to copy the table, else groups won't work right
  112. end
  113. return
  114. end
  115. end
  116. end
  117. --]]
  118. if c.width == 0 then c.width = 3 end
  119. table.insert(zcg.crafts[output], c)
  120. end
  121. zcg.load_crafts = function(name)
  122. zcg.crafts[name] = {}
  123. local _recipes = minetest.get_all_craft_recipes(name)
  124. if _recipes then
  125. for i, recipe in ipairs(_recipes) do
  126. if (recipe and recipe.items and recipe.type) then
  127. assert(type(recipe.output) ~= "nil")
  128. zcg.add_craft(recipe, recipe.output, name)
  129. end
  130. end
  131. end
  132. if zcg.crafts[name] == nil or #zcg.crafts[name] == 0 then
  133. zcg.crafts[name] = nil
  134. else
  135. table.insert(zcg.itemlist,name)
  136. end
  137. end
  138. zcg.formspec = function(pn)
  139. local page = zcg.users[pn].page
  140. local alt = zcg.users[pn].alt
  141. local current_item = zcg.users[pn].current_item
  142. local current_group = zcg.users[pn].current_group
  143. local formspec = "size[8,8.5]" ..
  144. default.gui_bg ..
  145. default.gui_bg_img ..
  146. default.gui_slots ..
  147. "button[0,0.5;2,.5;main;Back]"
  148. if zcg.users[pn].history.index > 1 then
  149. formspec = formspec .. "image_button[0,1.5;1,1;zcg_previous.png;zcg_previous;;false;false;zcg_previous_press.png]"
  150. else
  151. formspec = formspec .. "image[0,1.5;1,1;zcg_previous_inactive.png]"
  152. end
  153. if zcg.users[pn].history.index < #zcg.users[pn].history.list then
  154. formspec = formspec .. "image_button[1,1.5;1,1;zcg_next.png;zcg_next;;false;false;zcg_next_press.png]"
  155. else
  156. formspec = formspec .. "image[1,1.5;1,1;zcg_next_inactive.png]"
  157. end
  158. -- Show craft recipe
  159. if current_item ~= "" then
  160. if zcg.crafts[current_item] then
  161. if alt > #zcg.crafts[current_item] then
  162. alt = #zcg.crafts[current_item]
  163. end
  164. if alt > 1 then
  165. formspec = formspec .. "button[7,0.5;1,1;zcg_alt:"..(alt-1)..";^]"
  166. end
  167. if alt < #zcg.crafts[current_item] then
  168. formspec = formspec .. "button[7,2.5;1,1;zcg_alt:"..(alt+1)..";v]"
  169. end
  170. local c = zcg.crafts[current_item][alt]
  171. if c then
  172. local x = 3
  173. local y = 0
  174. -- Crafting recipe generated here.
  175. for i, item in pairs(c.items) do
  176. local stack = ItemStack(item)
  177. local itemname = stack:get_name()
  178. local label = ""
  179. local groupdata
  180. local groupname
  181. if item:find("^group:") then
  182. label = "G"
  183. -- The group may have an item count. Get just the group name.
  184. local gname = item:sub(7)
  185. gname = gname:split(" ")
  186. assert(type(gname[1]) == "string")
  187. gname = gname[1]
  188. groupdata = zcg.canonical_group_item(gname)
  189. itemname = "group:" .. gname:gsub(",", ":") -- comma cannot be used in formspecs!
  190. groupname = gname
  191. item = groupdata.item
  192. --minetest.chat_send_all(itemname)
  193. end
  194. formspec = formspec .. "item_image_button[" .. ((i-1)%c.width+x) .. "," ..
  195. (math_floor((i-1)/c.width+y)+0.5) .. ";1,1;" .. item .. ";zcg:" ..
  196. itemname .. ";" .. label .. "]"
  197. if groupdata then
  198. local text = "Anything in \"" .. minetest.colorize("gold", "group:" .. groupname) .. "\""
  199. if groupdata.description then
  200. text = "Any " .. minetest.colorize("gold", groupdata.description)
  201. end
  202. formspec = formspec .. "tooltip[zcg:" .. itemname .. ";" ..
  203. minetest.formspec_escape(text) .. "]"
  204. end
  205. end
  206. if c.type == "normal" or
  207. c.type == "cooking" or
  208. c.type == "grinding" or
  209. c.type == "cutting" or
  210. c.type == "extracting" or
  211. c.type == "alloying" or
  212. c.type == "separating" or
  213. c.type == "compressing" or
  214. c.type == "anvil" or
  215. c.type == "crushing" then
  216. formspec = formspec .. "image[6,2.5;1,1;zcg_method_"..c.type..".png]"
  217. else -- we don't have an image for other types of crafting
  218. formspec = formspec .. "label[0,2.5;Method: "..c.type.."]"
  219. end
  220. if c.type == "normal" then
  221. formspec = formspec .. "label[0,2.5;Method: Crafting]"
  222. elseif c.type == "cooking" then
  223. formspec = formspec .. "label[0,2.5;Method: Cooking/Smelting]"
  224. elseif c.type == "grinding" then
  225. formspec = formspec .. "label[0,2.5;Method: Grinding]"
  226. elseif c.type == "crushing" then
  227. formspec = formspec .. "label[0,2.5;Method: Crushing]"
  228. elseif c.type == "cutting" then
  229. formspec = formspec .. "label[0,2.5;Method: Cutting]"
  230. elseif c.type == "extracting" then
  231. formspec = formspec .. "label[0,2.5;Method: Extracting]"
  232. elseif c.type == "compressing" then
  233. formspec = formspec .. "label[0,2.5;Method: Compressing]"
  234. elseif c.type == "alloying" then
  235. formspec = formspec .. "label[0,2.5;Method: Alloying]"
  236. elseif c.type == "separating" then
  237. formspec = formspec .. "label[0,2.5;Method: Separating]"
  238. elseif c.type == "anvil" then
  239. formspec = formspec .. "label[0,2.5;Method: Hammering]"
  240. end
  241. if type(c.result) == "string" then
  242. local n1 = c.result
  243. formspec = formspec .. "image[6,1.5;1,1;zcg_craft_arrow.png]"
  244. formspec = formspec .. "item_image_button[7,1.5;1,1;".. n1 ..";zcg_out:" .. n1 .. ";]"
  245. elseif type(c.result) == "table" then
  246. -- Separating recipes have two outputs.
  247. local n1 = c.result[1]
  248. local n2 = c.result[2]
  249. formspec = formspec .. "item_image_button[6,1.5;1,1;" .. n1 .. ";zcg_out:" .. n1 .. ";]"
  250. formspec = formspec .. "item_image_button[7,1.5;1,1;" .. n2 .. ";zcg_out:" .. n2 .. ";]"
  251. end
  252. --minetest.chat_send_all(dump(c))
  253. end
  254. end
  255. end
  256. -- Node list
  257. local npp = 8*3 -- nodes per page
  258. local i = 0 -- for positionning buttons
  259. local s = 0 -- for skipping pages
  260. local whichlist = zcg.itemlist
  261. local listname = " searchable crafts."
  262. --minetest.chat_send_all(zcg.users[pn].searchtext)
  263. if zcg.users[pn].searchtext ~= "" then
  264. whichlist = zcg.users[pn].searchlist
  265. page = zcg.users[pn].spage
  266. listname = " result(s)."
  267. elseif current_group ~= "" then
  268. whichlist = zcg.items_in_group(current_group)
  269. page = zcg.users[pn].spage
  270. listname = " item(s) in group."
  271. end
  272. if #whichlist > 0 then
  273. formspec = formspec ..
  274. "label[0,4.0;" .. #whichlist .. " " .. listname .. "]"
  275. for _, name in ipairs(whichlist) do
  276. if s < page*npp then s = s+1 else
  277. if i >= npp then break end
  278. formspec = formspec .. "item_image_button["..(i%8)..","..(math_floor(i/8)+4.5)..";1,1;"..name..";zcg:"..name..";]"
  279. i = i+1
  280. end
  281. end
  282. else
  283. formspec = formspec ..
  284. "label[0,4.0;No results.]"
  285. end
  286. -- Page buttons.
  287. local maxpage = (math.ceil(#whichlist/npp))
  288. if maxpage < 1 then maxpage = 1 end -- In case no results, must have 1 page.
  289. local curpage = page+1
  290. if page > 0 then
  291. formspec = formspec .. "button[0,8;1,.5;zcg_page:"..(page-1)..";<<]"
  292. else
  293. formspec = formspec .. "button[0,8;1,.5;zcg_page:"..(maxpage-1)..";<<]"
  294. end
  295. if curpage < maxpage then
  296. formspec = formspec .. "button[1,8;1,.5;zcg_page:"..(page+1)..";>>]"
  297. elseif curpage >= maxpage then
  298. formspec = formspec .. "button[1,8;1,.5;zcg_page:".. 0 ..";>>]"
  299. end
  300. -- The Y is approximatively the good one to have it centered vertically...
  301. formspec = formspec .. "label[2,7.85;Page " .. curpage .."/".. maxpage .."]"
  302. -- Search field.
  303. formspec = formspec ..
  304. "button[6,8;1,0.5;zcg_search;?]" ..
  305. "button[7,8;1,0.5;zcg_clear;X]"
  306. local text = zcg.users[pn].searchtext or ""
  307. formspec = formspec ..
  308. "field[4,8.1;2.3,1;zcg_sbox;;" .. minetest.formspec_escape(text) .. "]" ..
  309. "field_close_on_enter[zcg_sbox;false]"
  310. return formspec
  311. end
  312. function zcg.update_search(pn, tsearch)
  313. minetest.log("action", "<" .. rename.gpn(pn) .. "> executes craftguide search for \"" .. tsearch .. "\".")
  314. zcg.users[pn].searchlist = {}
  315. if tsearch == "" or tsearch == "<INVAL>" then
  316. return
  317. end
  318. -- Let user search multiple tokens at once.
  319. local texts = string.split(tsearch)
  320. if not texts or #texts == 0 then
  321. return
  322. end
  323. local list = {}
  324. local find = string.find
  325. local ipairs = ipairs
  326. local pairs = pairs
  327. local type = type
  328. local items = minetest.registered_items
  329. -- Returns true only if all tokens in list are found in the search string.
  330. local function find_all(search, combined)
  331. local count = 0
  332. for i=1, #combined do
  333. if find(search, combined[i], 1, true) then
  334. count = count + 1
  335. else
  336. -- Early finish.
  337. return false
  338. end
  339. end
  340. return (count == #combined)
  341. end
  342. for i=1, #texts, 1 do
  343. local text = string.trim(texts[i]):lower()
  344. local combined = string.split(text, " ")
  345. for k=1, #combined do
  346. combined[k] = string.trim(combined[k]):lower()
  347. end
  348. for _, name in ipairs(zcg.itemlist) do
  349. if find_all(name:lower(), combined) then
  350. list[name] = true
  351. else
  352. local ndef = items[name]
  353. if ndef then
  354. -- Search description for a match.
  355. if ndef.description then
  356. if find_all(ndef.description:lower(), combined) then
  357. list[name] = true
  358. end
  359. end
  360. end -- if ndef.
  361. end
  362. end
  363. end -- for all texts.
  364. -- Duplicate results are removed.
  365. local flat = {}
  366. for k, v in pairs(list) do
  367. flat[#flat + 1] = k
  368. end
  369. zcg.users[pn].searchlist = flat
  370. end
  371. -- Sound volumes.
  372. local click_gain = 0.4
  373. local page_gain = 0.7
  374. local sound_range = 16
  375. -- It seems the player's main inventory formspec does not have a formname.
  376. local singleplayer = minetest.is_singleplayer()
  377. zcg.on_receive_fields = function(player, formname, fields)
  378. local played_sound = false
  379. local pn = player:get_player_name()
  380. afk.reset_timeout(pn)
  381. if zcg.users[pn] == nil then
  382. zcg.users[pn] = {
  383. current_item = "",
  384. current_group = "",
  385. alt = 1,
  386. page = 0,
  387. history = {index=0, list={}},
  388. searchlist = {},
  389. searchtext = "",
  390. spage = 0, -- Keep search page # seperate from main page #.
  391. }
  392. end
  393. if fields.zcg then
  394. inventory_plus.set_inventory_formspec(player, zcg.formspec(pn))
  395. if not played_sound and not fields.quit then
  396. ambiance.sound_play("button_click", player:get_pos(), click_gain, sound_range)
  397. end
  398. return
  399. elseif fields.zcg_previous then
  400. if zcg.users[pn].history.index > 1 then
  401. zcg.users[pn].history.index = zcg.users[pn].history.index - 1
  402. zcg.users[pn].current_item = zcg.users[pn].history.list[zcg.users[pn].history.index]
  403. zcg.users[pn].searchtext = fields.zcg_sbox or "<INVAL>"
  404. inventory_plus.set_inventory_formspec(player,zcg.formspec(pn))
  405. end
  406. elseif fields.zcg_next then
  407. if zcg.users[pn].history.index < #zcg.users[pn].history.list then
  408. zcg.users[pn].history.index = zcg.users[pn].history.index + 1
  409. zcg.users[pn].current_item = zcg.users[pn].history.list[zcg.users[pn].history.index]
  410. zcg.users[pn].searchtext = fields.zcg_sbox or "<INVAL>"
  411. inventory_plus.set_inventory_formspec(player,zcg.formspec(pn))
  412. end
  413. end
  414. for k, v in pairs(fields) do
  415. if (k:sub(0, 10) == "zcg:group:") then
  416. zcg.users[pn].searchtext = ""
  417. zcg.users[pn].current_group = k:sub(11):gsub(":", ",") -- comma cannot be used in formspecs!
  418. zcg.users[pn].spage = 0 -- Reset user's search page.
  419. --minetest.chat_send_all(zcg.users[pn].current_group)
  420. inventory_plus.set_inventory_formspec(player, zcg.formspec(pn))
  421. elseif (k:sub(0,4)=="zcg:") then
  422. local ni = k:sub(5)
  423. zcg.users[pn].current_group = ""
  424. zcg.users[pn].searchtext = fields.zcg_sbox or "<INVAL>"
  425. if zcg.crafts[ni] then
  426. local previtem = zcg.users[pn].current_item
  427. zcg.users[pn].current_item = ni
  428. table.insert(zcg.users[pn].history.list, ni)
  429. zcg.users[pn].history.index = #zcg.users[pn].history.list
  430. inventory_plus.set_inventory_formspec(player, zcg.formspec(pn))
  431. -- Add item to inventory if creative access is enabled.
  432. if gdac.player_is_admin(pn) or singleplayer then
  433. -- If player clicked twice.
  434. if previtem == ni then
  435. local inv = player:get_inventory()
  436. local stack = ItemStack(ni)
  437. stack:set_count(stack:get_stack_max())
  438. local leftover = inv:add_item("main", stack)
  439. -- Notify if a mapping kit was added.
  440. if map.is_mapping_kit(stack:get_name()) then
  441. map.update_inventory_info(pn)
  442. end
  443. if not leftover or leftover:get_count() == 0 then
  444. local desc = utility.get_short_desc(stack:get_definition().description or "Undescribed Item")
  445. minetest.chat_send_player(pn, "# Server: Added '" .. desc .. "' to inventory!")
  446. else
  447. minetest.chat_send_player(pn, "# Server: Not enough room in inventory!")
  448. end
  449. end
  450. end
  451. end
  452. elseif (k:sub(0,9)=="zcg_page:") then
  453. if zcg.users[pn].searchtext == "" then
  454. zcg.users[pn].page = tonumber(k:sub(10))
  455. else
  456. zcg.users[pn].spage = tonumber(k:sub(10))
  457. end
  458. ambiance.sound_play("pageflip", player:get_pos(), page_gain, sound_range)
  459. played_sound = true
  460. zcg.users[pn].searchtext = fields.zcg_sbox or "<INVAL>"
  461. inventory_plus.set_inventory_formspec(player,zcg.formspec(pn))
  462. elseif (k:sub(0,8)=="zcg_alt:") then
  463. zcg.users[pn].alt = tonumber(k:sub(9))
  464. zcg.users[pn].searchtext = fields.zcg_sbox or "<INVAL>"
  465. inventory_plus.set_inventory_formspec(player,zcg.formspec(pn))
  466. elseif (k == "zcg_clear") then
  467. zcg.users[pn].searchlist = {}
  468. zcg.users[pn].searchtext = ""
  469. zcg.users[pn].current_group = ""
  470. inventory_plus.set_inventory_formspec(player,zcg.formspec(pn))
  471. elseif (fields.key_enter_field == "zcg_sbox" or k == "zcg_search") then
  472. zcg.users[pn].current_group = ""
  473. local newtext = fields.zcg_sbox or "<INVAL>"
  474. if newtext ~= zcg.users[pn].searchtext then -- Don't update if same.
  475. zcg.users[pn].searchlist = {}
  476. zcg.users[pn].searchtext = newtext
  477. if string.len(zcg.users[pn].searchtext) > 128 then
  478. zcg.users[pn].searchtext = "<INVAL>"
  479. else
  480. local res, err = pcall(function() zcg.update_search(pn, zcg.users[pn].searchtext) end)
  481. if not res and type(err) == "string" then
  482. minetest.log("error", err)
  483. end
  484. zcg.users[pn].spage = 0 -- Reset user's search page.
  485. end
  486. inventory_plus.set_inventory_formspec(player,zcg.formspec(pn))
  487. ambiance.sound_play("pageflip", player:get_pos(), page_gain, sound_range)
  488. played_sound = true
  489. end
  490. end
  491. end
  492. if not played_sound and not fields.quit then
  493. ambiance.sound_play("button_click", player:get_pos(), click_gain, sound_range)
  494. end
  495. if not played_sound and (fields.close or fields.exit or fields.done) then
  496. ambiance.sound_play("button_click", player:get_pos(), click_gain, sound_range)
  497. end
  498. -- This works because the 'quit' field is sent whenever the player tabs or ESC's out of a formspec,
  499. -- but not when changing to show a different formspec.
  500. if fields.quit then
  501. if passport.open_keys[pn] then
  502. passport.open_keys[pn] = nil
  503. ambiance.sound_play("fancy_chime2", player:get_pos(), 1.0, 20, "", false)
  504. end
  505. end
  506. end
  507. -- Ha hah ha lol olo lol.
  508. if not zcg.registered then
  509. -- Load all crafts directly after server-init time.
  510. -- We can't do this at craft-register time because the logic needs access to
  511. -- the groups of the recipe output items, which may not be known by the engine
  512. -- until after recipes for the items are registered.
  513. --if not minetest.is_singleplayer() then
  514. minetest.register_on_mods_loaded(function()
  515. local t1 = os.clock()
  516. -- Must search through ALL registered items! Cannot use shortcut tables.
  517. for name, item in pairs(minetest.registered_items) do
  518. if name and name ~= "" then
  519. -- Ignore stairs nodes. They do have generic/standard recipes, but we
  520. -- wouldn't show them anway -- WAY too much CG spam.
  521. if not name:find("^%:?stairs:") and not name:find("^%:?walls:") then
  522. zcg.load_crafts(name)
  523. end
  524. end
  525. end
  526. table.sort(zcg.itemlist)
  527. local t2 = os.clock()
  528. minetest.log("action", "Loading craft recipes took " .. (t2 - t1) .. " seconds.")
  529. end)
  530. --end -- Speed up singleplayer game testing.
  531. -- Register button once.
  532. inventory_plus.register_button("zcg", "Craft Journal")
  533. -- Per Lua docs, newest functions are called first.
  534. -- Therefore resister inside minetest.after() to ensure this function is
  535. -- registered AFTER all other mods have registered theirs.
  536. minetest.after(0, function()
  537. minetest.register_on_player_receive_fields(function(...)
  538. return zcg.on_receive_fields(...)
  539. end)
  540. end)
  541. dofile(zcg.modpath .. "/groups.lua")
  542. local c = "zcg:core"
  543. local f = zcg.modpath .. "/init.lua"
  544. reload.register_file(c, f, false)
  545. zcg.registered = true
  546. end