From e76a0ba6e859a4aba665a849864f9799b87a8b87 Mon Sep 17 00:00:00 2001 From: iliekprogrammar Date: Sun, 28 Mar 2021 14:11:08 +0800 Subject: [PATCH] Added kelp API and additional refactorings. WIP: register nodetimers for kelp --- mods/ITEMS/mcl_ocean/kelp.lua | 499 ++++++++++++++++++++++------------ 1 file changed, 318 insertions(+), 181 deletions(-) diff --git a/mods/ITEMS/mcl_ocean/kelp.lua b/mods/ITEMS/mcl_ocean/kelp.lua index 73b2aaeb42..fe2266789c 100644 --- a/mods/ITEMS/mcl_ocean/kelp.lua +++ b/mods/ITEMS/mcl_ocean/kelp.lua @@ -1,94 +1,163 @@ -local S = minetest.get_translator("mcl_ocean") -local mod_doc = minetest.get_modpath("doc") ~= nil -- NOTE: whenever it becomes possible to fully implement kelp without the -- plantlike_rooted limitation, please adapt the code accordingly. -- TODO: In MC, you can't actually destroy kelp by bucket'ing water in the middle. -- However, because of the plantlike_rooted hack, we'll just allow it for now. --- List of supported surfaces for seagrass and kelp. -local surfaces = { - { "dirt", "mcl_core:dirt" }, - { "sand", "mcl_core:sand", 1 }, - { "redsand", "mcl_core:redsand", 1 }, - { "gravel", "mcl_core:gravel", 1 }, -} +local S = minetest.get_translator("mcl_ocean") +local mod_doc = minetest.get_modpath("doc") ~= nil + +-------------------------------------------------------------------------------- +-- local-ify runtime functions +-------------------------------------------------------------------------------- +-- objects +local registered_items = minetest.registered_items +local registered_nodes = minetest.registered_nodes + +-- functions +local mt_get_item_group = minetest.get_item_group +local mt_get_node = minetest.get_node +local mt_set_node = minetest.set_node +local mt_add_item = minetest.add_item +local mt_sound_play = minetest.sound_play +local mt_is_creative_enabled = minetest.is_creative_enabled +local mt_is_protected = minetest.is_protected +local mt_hash_node_position = minetest.hash_node_position +local mt_get_node_timer = minetest.get_node_timer + +-- DEBUG: functions +local chatlog = minetest.chat_send_all + +-------------------------------------------------------------------------------- +-- Kelp API +-------------------------------------------------------------------------------- + +kelp = {} -- Is this water? +-- Returns the liquidtype, if indeed water. local function is_submerged(node, nodedef) - if minetest.get_item_group(node.name, "water") ~= 0 then + if mt_get_item_group(node.name, "water") ~= 0 then return nodedef.liquidtype -- Expected only "source" and "flowing" from water liquids end return false end +kelp.is_submerged = is_submerged + -- Is the water downward flowing? -- (kelp can grow/be placed inside downward flowing water) local function is_downward_flowing(pos, node, nodedef, is_above) - - result = (math.floor(node.param2 / 8) % 2) == 1 + local result = (math.floor(node.param2 / 8) % 2) == 1 if not (result or is_above) then - -- If not, also check node above + -- If not, also check node above. -- (this is needed due a weird quirk in the definition of "downwards flowing" -- liquids in Minetest) - local node_above = minetest.get_node({x=pos.x,y=pos.y+1,z=pos.z}) - local nodedef_above = minetest.registered_nodes[node_above.name] + local node_above = mt_get_node({x=pos.x,y=pos.y+1,z=pos.z}) + local nodedef_above = registered_nodes[node_above.name] result = is_submerged(node_above, nodedef_above) or is_downward_flowing(pos, node_above, nodedef_above, true) end return result end +kelp.is_downward_flowing = is_downward_flowing + -- Converts param2 to kelp height. -local function get_kelp_height(param2) +local function get_height(param2) return math.floor(param2 / 16) end +kelp.get_height = get_height + -- Obtain pos and node of the top of kelp. -local function get_kelp_top(pos, node) +local function get_tip(pos, node) local size = math.ceil(node.param2 / 16) local pos_top = table.copy(pos) pos_top.y = pos_top.y + size - return pos_top, minetest.get_node(pos_top) + return pos_top, mt_get_node(pos_top) end +kelp.get_tip = get_tip + -- Obtain position of the first kelp unsubmerged. -local function get_kelp_unsubmerged(pos, node) +local function find_unsubmerged(pos, node) local x,y,z = pos.x, pos.y, pos.z - local height = get_kelp_height(node.param2) + local height = get_height(node.param2) for i=1,height do local walk_pos = {x=x, y=y + i, z=z} - if minetest.get_item_group(minetest.get_node(walk_pos).name, "water") == 0 then + if mt_get_item_group(mt_get_node(walk_pos).name, "water") == 0 then return walk_pos end end return nil end +kelp.find_unsubmerged = find_unsubmerged --- Obtain next param2 if grown -local function grow_param2_step(param2) - -- TODO: allow kelp to grow bypass this limit according to MC rules. - -- https://minecraft.gamepedia.com/Kelp +-- Obtain next param2. +local function next_param2(param2) local old_param2 = param2 param2 = param2+16 - param2 % 16 - if param2 > 240 then - param2 = 240 - end return param2, param2 ~= old_param2 end +kelp.next_param2 = next_param2 -local function kelp_place(pos, node, pos_top, def_top, is_downward_flowing) - -- Liquid source: Grow normally - minetest.set_node(pos, node) - -- Flowing liquid: Grow 1 step, but also turn the top node into a liquid source +-- Grow next kelp. +local function next_grow(pos, node, pos_top, def_top, is_downward_flowing) + -- Liquid source: Grow normally. + mt_set_node(pos, node) + + -- Flowing liquid: Grow 1 step, but also turn the top node into a liquid source. if is_downward_flowing then local alt_liq = def_top.liquid_alternative_source if alt_liq then - minetest.set_node(pos_top, {name=alt_liq}) + mt_set_node(pos_top, {name=alt_liq}) end end end +kelp.next_grow = next_grow + + +-- Drops the items for detached kelps. +local function detach_drop(pos, height) + local x,y,z = pos.x,pos.y,pos.z + for i=1,height do + mt_add_item({x=x, y=y+i, z=z}, "mcl_ocean:kelp") + end +end +kelp.detach_drop = detach_drop + + +-- Detach the kelp at dig_pos, and drop their items. +-- Synonyous to digging the kelp +local function detach_dig(dig_pos, pos, node, is_drop) + local param2 = node.param2 + -- pos.y points to the surface, offset needed to point to the first kelp. + local new_height = dig_pos.y - (pos.y+1) + + -- Digs the entire kelp: invoke after_dig_node to mt_set_node. + if new_height <= 0 then + if is_drop then + detach_drop(dig_pos, get_height(param2)) + end + mt_set_node(pos, { + name=registered_nodes[node.name].node_dig_prediction, + param=node.param, param2=0 }) + + -- Digs the kelp beginning at a height + else + if is_drop then + detach_drop(dig_pos, get_height(param2) - new_height) + end + mt_set_node(pos, {name=node.name, param=node.param, param2=16*new_height}) + end +end +kelp.detach_dig = detach_dig + +-------------------------------------------------------------------------------- +-- Kelp callback functions +-------------------------------------------------------------------------------- local function kelp_on_place(itemstack, placer, pointed_thing) if pointed_thing.type ~= "node" or not placer then @@ -98,17 +167,17 @@ local function kelp_on_place(itemstack, placer, pointed_thing) local player_name = placer:get_player_name() local pos_under = pointed_thing.under local pos_above = pointed_thing.above - local node_under = minetest.get_node(pos_under) + local node_under = mt_get_node(pos_under) local nu_name = node_under.name - local def_under = minetest.registered_nodes[nu_name] + local def_under = registered_nodes[nu_name] if def_under and def_under.on_rightclick and not placer:get_player_control().sneak then return def_under.on_rightclick(pos_under, node_under, placer, itemstack, pointed_thing) or itemstack end - if minetest.is_protected(pos_under, player_name) or - minetest.is_protected(pos_above, player_name) then + if mt_is_protected(pos_under, player_name) or + mt_is_protected(pos_above, player_name) then minetest.log("action", player_name .. " tried to place " .. itemstack:get_name() .. " at protected position " @@ -122,23 +191,24 @@ local function kelp_on_place(itemstack, placer, pointed_thing) local pos_top, node_top, def_top -- When placed on kelp. - if minetest.get_item_group(nu_name, "kelp") == 1 then - node_under.param2, new_kelp = grow_param2_step(node_under.param2) + if mt_get_item_group(nu_name, "kelp") == 1 then + node_under.param2, new_kelp = next_param2(node_under.param2) -- Kelp must not reach the height limit. -- Kelp must also be placed on top of kelp to add kelp. if not new_kelp or pos_under.y >= pos_above.y then return itemstack end - pos_top, node_top = get_kelp_top(pos_under, node_under) - def_top = minetest.registered_nodes[node_top.name] + pos_top, node_top = get_tip(pos_under, node_under) + def_top = registered_nodes[node_top.name] -- When placed on surface. else + local surfaces = kelp.surfaces or surfaces for _,surface in pairs(surfaces) do -- Surface must support kelp - if nu_name == surface[2] then - node_under.name = "mcl_ocean:kelp_" ..surface[1] - node_under.param2 = minetest.registered_items[nu_name].place_param2 or 16 + if nu_name == surface.nodename then + node_under.name = "mcl_ocean:kelp_" ..surface.name + node_under.param2 = registered_items[nu_name].place_param2 or 16 new_kelp = true break end @@ -150,8 +220,8 @@ local function kelp_on_place(itemstack, placer, pointed_thing) end pos_top = pos_above - node_top = minetest.get_node(pos_above) - def_top = minetest.registered_nodes[node_top.name] + node_top = mt_get_node(pos_above) + def_top = registered_nodes[node_top.name] end -- New kelp must also be submerged in water. @@ -161,48 +231,193 @@ local function kelp_on_place(itemstack, placer, pointed_thing) end -- Play sound, place surface/kelp and take away an item - local def_node = minetest.registered_items[nu_name] + local def_node = registered_items[nu_name] if def_node.sounds then - minetest.sound_play(def_node.sounds.place, { gain = 0.5, pos = pos_under }, true) + mt_sound_play(def_node.sounds.place, { gain = 0.5, pos = pos_under }, true) end - kelp_place(pos_under, node_under, pos_top, def_top, downward_flowing) - if not minetest.is_creative_enabled(player_name) then + next_grow(pos_under, node_under, pos_top, def_top, downward_flowing) + if not mt_is_creative_enabled(player_name) then itemstack:take_item() end return itemstack end +kelp.kelp_on_place = kelp_on_place --- From kelp at pos, drop kelp until reaching its height. -local function kelp_drop(pos, height) - local x,y,z = pos.x,pos.y,pos.z - for i=1,height do - minetest.add_item({x=x, y=y+i, z=z}, "mcl_ocean:kelp") + +local function surface_on_dig(pos, node, digger) + -- TODO: poll on whether if players like dropping kelp in creative + -- detach_dig(pos, pos, node, + -- not (digger and mt_is_creative_enabled(digger:get_player_name()))) + detach_dig(pos, pos, node, true) +end +kelp.surface_on_dig = surface_on_dig + + +local function surface_after_dig_node(pos, node) + return mt_set_node(pos, {name=registred_nodes[node.name].node_dig_prediction}) +end +kelp.surface_after_dig_node = surface_after_dig_node + + +local function grow_kelp(pos, node) + local grow + -- Grow kelp by 1 node length if it would grow inside water + node.param2, grow = next_param2(node.param2) + local pos_top, node_top = get_tip(pos, node) + local def_top = registered_nodes[node_top.name] + if grow and is_submerged(node_top, def_top) then + next_grow(pos, node, pos_top, def_top, + is_downward_flowing(pos_top, node_top, def_top)) end end +kelp.grow_kelp = grow_kelp -local function kelp_dig(dig_pos, pos, node, is_drop) - local param2 = node.param2 - -- pos.y points to the surface, offset needed to point to the first kelp - local new_height = dig_pos.y - (pos.y+1) - -- Digs the entire kelp: invoke after_dig_node to set_node - if new_height <= 0 then - if is_drop then - kelp_drop(dig_pos, get_kelp_height(param2)) - end - minetest.set_node(pos, { - name=minetest.registered_nodes[node.name].node_dig_prediction, - param=node.param, param2=0 }) - - -- Digs the kelp beginning at a height - else - if is_drop then - kelp_drop(dig_pos, get_kelp_height(param2) - new_height) - end - minetest.set_node(pos, {name=node.name, param=node.param, param2=16*new_height}) +local kelp_timers_idx = {} +local kelp_timers = {} +local function surface_register_nodetimer(pos, node) + local pos_hash = mt_hash_node_position(pos) + if kelp_timers_idx[pos_hash] then + return end + local timer = mt_get_node_timer(pos) + table.insert(kelp_timers, timer) + kelp_timers_idx[pos_hash] = #kelp_timers + chatlog("added a timer. Currently " ..tostring(#kelp_timers) .." timers") end +kelp.surface_register_nodetiemr = surface_register_nodetimer + +-------------------------------------------------------------------------------- +-- Kelp registration API +-------------------------------------------------------------------------------- + +-- List of supported surfaces for seagrass and kelp. +local surfaces = { + { name="dirt", nodename="mcl_core:dirt", }, + { name="sand", nodename="mcl_core:sand", }, + { name="redsand", nodename="mcl_core:redsand", }, + { name="gravel", nodename="mcl_core:gravel", }, +} +kelp.surfaces = surfaces +local registered_surfaces = {} +kelp.registered_surfaces = registered_surfaces + +-- Commented keys are the ones obtained using register_kelp_surface. +-- If you define your own keys, that keys will be used instead. +local surface_deftemplate = { + drawtype = "plantlike_rooted", + paramtype = "light", + paramtype2 = "leveled", + place_param2 = 16, + --tiles = def.tiles, + special_tiles = { + { + image = "mcl_ocean_kelp_plant.png", + animation = {type="vertical_frames", aspect_w=16, aspect_h=16, length=2.0}, + tileable_vertical = true, + } + }, + --inventory_image = "("..def.tiles[1]..")^mcl_ocean_kelp_item.png", + wield_image = "mcl_ocean_kelp_item.png", + selection_box = { + type = "fixed", + fixed = { + { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }, + { -0.5, 0.5, -0.5, 0.5, 1.5, 0.5 }, + }, + }, + -- groups.falling_node = is_falling, + groups = { dig_immediate = 3, deco_block = 1, plant = 1, kelp = 1, }, + --sounds = sounds, + --node_dig_prediction = nodename, + after_dig_node = surface_after_dig_node, + on_dig = surface_on_dig, + drop = "", -- drops are handled in on_dig + --_mcl_falling_node_alternative = is_falling and nodename or nil, + _mcl_hardness = 0, + _mcl_blast_resistance = 0, +} +kelp.surface_deftemplate = surface_deftemplate + +-- Commented keys are the ones obtained using register_kelp_surface. +local surface_docs = { + -- entry_id_orig = nodename, + _doc_items_entry_name = S("Kelp"), + _doc_items_longdesc = S("Kelp grows inside water on top of dirt, sand or gravel."), + --_doc_items_create_entry = doc_create, + _doc_items_image = "mcl_ocean_kelp_item.png", +} +kelp.surface_docs = surface_docs + +--[==[-- +register_kelp_surface(surface[, surface_deftemplate[, surface_docs]]) + +surface: table with the specifications below. See also kelp.surface. + { + name="dirt", + The name of the surface. This will appended to the surface's nodedef name + + nodename="mcl_core:dirt" + The nodename of the surface kelp can be planted on. + } + +surface_deftemplate: modifiable nodedef template. See also kelp.surface_deftempate. +DO NOT RE-USE THE SAME DEFTEMPLATE. create copies. + +surface_docs: table with keys related to docs. See also kelp.surface_docs. +--]==]-- +local leaf_sounds = mcl_sounds.node_sound_leaves_defaults() +local function register_kelp_surface(surface, surface_deftemplate, surface_docs) + local name = surface.name + local nodename = surface.nodename + local def = registered_nodes[nodename] + local def_tiles = def.tiles + + local surfacename = "mcl_ocean:kelp_"..name + local surface_deftemplate = surface_deftemplate or kelp.surface_deftemplate -- optional param + + local doc_create = surface.doc_create or false + local surface_docs = surface_docs or kelp.surface_docs + + if doc_create then + surface_deftemplate._doc_items_entry_name = surface_docs._doc_items_entry_name + surface_deftemplate._doc_items_longdesc = surface_docs._doc_items_longdesc + surface_deftemplate._doc_items_create_entry = true + surface_deftemplate._doc_items_image = surface_docs._doc_items_image + -- Takes the first surface with docs + if not surface_docs.entry_id_orig then + surface_docs.entry_id_orig = nodename + end + elseif mod_doc then + doc.add_entry_alias("nodes", surface_docs.entry_id_orig, "nodes", surfacename) + end + + local sounds = table.copy(def.sounds) + sounds.dig = leaf_sounds.dig + sounds.dug = leaf_sounds.dug + sounds.place = leaf_sounds.place + + surface_deftemplate.tiles = surface_deftemplate.tiles or def_tiles + surface_deftemplate.inventory_image = surface_deftemplate.inventory_image or "("..def_tiles[1]..")^mcl_ocean_kelp_item.png" + surface_deftemplate.sounds = surface_deftemplate.sound or sounds + local falling_node = mt_get_item_group(nodename, "falling_node") + surface_deftemplate.node_dig_prediction = surface_deftemplate.node_dig_prediction or nodename + surface_deftemplate.groups.faling_node = surface_deftemplate.groups.faling_node or falling_node + surface_deftemplate._mcl_falling_node_alternative = surface_deftemplate._mcl_falling_node_alternative or (falling_node and nodename or nil) + + minetest.register_node(surfacename, surface_deftemplate) +end + +-- Kelp surfaces nodes --------------------------------------------------------- + +-- Dirt must be registered first, for the docs +register_kelp_surface(surfaces[1], table.copy(surface_deftemplate), surface_docs) +for i=2, #surfaces do + register_kelp_surface(surfaces[i], table.copy(surface_deftemplate), surface_docs) +end + +-- Kelp item ------------------------------------------------------------------- minetest.register_craftitem("mcl_ocean:kelp", { description = S("Kelp"), @@ -214,86 +429,11 @@ minetest.register_craftitem("mcl_ocean:kelp", { groups = { deco_block = 1 }, }) --- Kelp nodes: kelp on a surface node - -for s=1, #surfaces do - local def = minetest.registered_nodes[surfaces[s][2]] - local alt - if surfaces[s][3] == 1 then - alt = surfaces[s][2] - end - local sounds = table.copy(def.sounds) - local leaf_sounds = mcl_sounds.node_sound_leaves_defaults() - sounds.dig = leaf_sounds.dig - sounds.dug = leaf_sounds.dug - sounds.place = leaf_sounds.place - local tt_help, doc_longdesc, doc_img, desc - if surfaces[s][1] == "dirt" then - doc_longdesc = S("Kelp grows inside water on top of dirt, sand or gravel.") - desc = S("Kelp") - doc_create = true - doc_img = "mcl_ocean_kelp_item.png" - else - doc_create = false - end - minetest.register_node("mcl_ocean:kelp_"..surfaces[s][1], { - _doc_items_entry_name = desc, - _doc_items_longdesc = doc_longdesc, - _doc_items_create_entry = doc_create, - _doc_items_image = doc_img, - drawtype = "plantlike_rooted", - paramtype = "light", - paramtype2 = "leveled", - place_param2 = 16, - tiles = def.tiles, - special_tiles = { - { - image = "mcl_ocean_kelp_plant.png", - animation = {type="vertical_frames", aspect_w=16, aspect_h=16, length=2.0}, - tileable_vertical = true, - } - }, - inventory_image = "("..def.tiles[1]..")^mcl_ocean_kelp_item.png", - wield_image = "mcl_ocean_kelp_item.png", - selection_box = { - type = "fixed", - fixed = { - { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }, - { -0.5, 0.5, -0.5, 0.5, 1.5, 0.5 }, - }, - }, - groups = { dig_immediate = 3, deco_block = 1, plant = 1, kelp = 1, falling_node = surfaces[s][3] }, - sounds = sounds, - node_dig_prediction = surfaces[s][2], - after_dig_node = function(pos) - minetest.set_node(pos, {name=surface[s][2]}) - end, - -- TODO: add ability to detect whether the kelp or the surface is dug. - -- Currently, digging the surface gives sand, which isn't ideal. - on_dig = function(pos, node, digger) - minetest.chat_send_all("mo2") - local is_drop = true - if digger and minetest.is_creative_enabled(digger:get_player_name()) then - is_drop = false - end - kelp_dig(pos, pos, node, is_drop) - end, - drop = "", -- drops are handled in on_dig - _mcl_falling_node_alternative = alt, - _mcl_hardness = 0, - _mcl_blast_resistance = 0, - }) - - if mod_doc and surfaces[s][1] ~= "dirt" then - doc.add_entry_alias("nodes", "mcl_ocean:kelp_dirt", "nodes", "mcl_ocean:kelp_"..surfaces[s][1]) - end -end - if mod_doc then - doc.add_entry_alias("nodes", "mcl_ocean:kelp_dirt", "craftitems", "mcl_ocean:kelp") + doc.add_entry_alias("nodes", surface_docs.entry_id_orig, "craftitems", "mcl_ocean:kelp") end --- Dried kelp stuff +-- Dried kelp ------------------------------------------------------------------ -- TODO: This is supposed to be eaten very fast minetest.register_craftitem("mcl_ocean:dried_kelp", { @@ -308,13 +448,13 @@ minetest.register_craftitem("mcl_ocean:dried_kelp", { _mcl_saturation = 0.6, }) + local mod_screwdriver = minetest.get_modpath("screwdriver") ~= nil local on_rotate if mod_screwdriver then on_rotate = screwdriver.rotate_3way end - minetest.register_node("mcl_ocean:dried_kelp_block", { description = S("Dried Kelp Block"), _doc_items_longdesc = S("A decorative block that serves as a great furnace fuel."), @@ -354,39 +494,36 @@ minetest.register_craft({ burntime = 200, }) --- Grow kelp +-- ABMs ------------------------------------------------------------------------ minetest.register_abm({ - label = "Kelp growth", + label = "mcl_ocean:Kelp growth", nodenames = { "group:kelp" }, - -- interval = 45, - -- chance = 12, - interval = 1, - chance = 1, + interval = 45, + chance = 12, catch_up = false, - action = function(pos, node, active_object_count, active_object_count_wider) - local grow - -- Grow kelp by 1 node length if it would grow inside water - node.param2, grow = grow_param2_step(node.param2) - local pos_top, node_top = get_kelp_top(pos, node) - local def_top = minetest.registered_nodes[node_top.name] - if grow and is_submerged(node_top, def_top) then - kelp_place(pos, node, pos_top, def_top, - is_downward_flowing(pos_top, node_top, def_top)) - end - end, + action = grow_kelp, }) --- Break kelp not underwater. -minetest.register_abm({ - label = "Kelp drops", +minetest.register_lbm({ + label = "Kelp timer registration", + name = "mcl_ocean:kelp_timer_registration", nodenames = { "group:kelp" }, - interval = 0.25, - chance = 1, - catch_up = false, - action = function(pos, node) - local dig_pos = get_kelp_unsubmerged(pos, node) - if dig_pos then - kelp_dig(dig_pos, pos, node, true) - end - end + run_at_every_load = true, + action = surface_register_nodetimer, }) + +-- TODO: test if nodetimers are more efficient than ABM +-- -- Break kelp not underwater. +-- minetest.register_abm({ +-- label = "Kelp drops", +-- nodenames = { "group:kelp" }, +-- interval = 0.5, +-- chance = 1, +-- catch_up = false, +-- action = function(pos, node) +-- local dig_pos = find_unsubmerged(pos, node) +-- if dig_pos then +-- detach_dig(dig_pos, pos, node, true) +-- end +-- end +-- })