From 23f69dfd1e8bac7469a00ecb6a1de5161f2475f8 Mon Sep 17 00:00:00 2001 From: iliekprogrammar Date: Sat, 20 Mar 2021 17:47:04 +0800 Subject: [PATCH 01/12] Core implementation of MC-like kelp drops. Highlights: * Kelp should no longer be able to survive without water (by decreasing its height) * When kelp is destroyed, each segment now drop a single kelp similar to MC. * Significantly refactor how kelp grows and dig. Possible optimizations might be included. --- mods/ITEMS/REDSTONE/mesecons_mvps/init.lua | 6 +- mods/ITEMS/mcl_ocean/kelp.lua | 270 +++++++++++++-------- 2 files changed, 169 insertions(+), 107 deletions(-) diff --git a/mods/ITEMS/REDSTONE/mesecons_mvps/init.lua b/mods/ITEMS/REDSTONE/mesecons_mvps/init.lua index 669edbcbb..8cd5ae872 100644 --- a/mods/ITEMS/REDSTONE/mesecons_mvps/init.lua +++ b/mods/ITEMS/REDSTONE/mesecons_mvps/init.lua @@ -74,6 +74,7 @@ function mesecon.is_mvps_unsticky(node, pulldir, stack, stackid) end -- Functions to be called on mvps movement +-- See also the callback function mesecon.register_on_mvps_move(callback) mesecon.on_mvps_move[#mesecon.on_mvps_move+1] = callback end @@ -405,17 +406,20 @@ mesecon.register_mvps_unsticky("mcl_colorblocks:glazed_terracotta_brown") mesecon.register_mvps_unsticky("mcl_colorblocks:glazed_terracotta_light_blue") mesecon.register_mvps_unsticky("mcl_colorblocks:glazed_terracotta_pink") +-- Includes node heat when moving them mesecon.register_on_mvps_move(mesecon.move_hot_nodes) --- Check for falling after moving node mesecon.register_on_mvps_move(function(moved_nodes) for i = 1, #moved_nodes do local moved_node = moved_nodes[i] + -- Check for falling after moving node mesecon.on_placenode(moved_node.pos, moved_node.node) minetest.after(0, function() minetest.check_for_falling(moved_node.oldpos) minetest.check_for_falling(moved_node.pos) end) + + -- Callback for on_mvps_move stored in nodedef local node_def = minetest.registered_nodes[moved_node.node.name] if node_def and node_def.mesecon and node_def.mesecon.on_mvps_move then node_def.mesecon.on_mvps_move(moved_node.pos, moved_node.node, diff --git a/mods/ITEMS/mcl_ocean/kelp.lua b/mods/ITEMS/mcl_ocean/kelp.lua index 2e0dfe1a5..9d6c09279 100644 --- a/mods/ITEMS/mcl_ocean/kelp.lua +++ b/mods/ITEMS/mcl_ocean/kelp.lua @@ -9,29 +9,67 @@ local surfaces = { { "gravel", "mcl_core:gravel", 1 }, } -local function get_kelp_top(pos, node) - local size = math.ceil(node.param2 / 16) - local pos_water = table.copy(pos) - pos_water.y = pos_water.y + size - return pos_water, minetest.get_node(pos_water) -end - -local function get_submerged(node_water) - local def_water = minetest.registered_nodes[node_water.name] - -- Submerged in water? - if minetest.get_item_group(node_water.name, "water") then - if def_water.liquidtype == "source" then +-- Is this water? +local function is_submerged(node, nodedef) + if minetest.get_item_group(node.name, "water") ~= 0 then + local liquidtype = nodedef.liquidtype + -- TODO: is it OK to optimize this to: + -- return nodedef.liquidtype + if liquidtype == "source" then return "source" - elseif def_water.liquidtype == "flowing" then + elseif liquidtype == "flowing" then return "flowing" + else + minetest.chat_send_all("its NOT OK to optimize into return nodedef.liquidtype :(") end end return false end +-- Is the water downward flowing? +-- (kelp can grow inside downward flowing water) +local function is_downward_flowing(pos, node, nodedef, is_above) + + result = (math.floor(node.param2 / 8) % 2) == 1 + if not (result or is_above) then + -- 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] + result = is_submerged(node_above, nodedef_above) or is_downward_flowing(pos, node_above, nodedef_above, true) + end + return result +end + +local function get_kelp_height(param2) + return math.floor(param2 / 16) +end + +local function get_kelp_top(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) +end + +-- Obtain position of the first kelp unsubmerged +local function get_kelp_unsubmerged(pos, node) + local x,y,z = pos.x, pos.y, pos.z + local height = get_kelp_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 + return walk_pos + end + end + return nil +end + local function grow_param2_step(param2, snap_into_grid) local old_param2 = param2 param2 = param2 + 16 + -- TODO: allow kelp to grow bypass this limit according to MC rules. + -- https://minecraft.gamepedia.com/Kelp if param2 > 240 then param2 = 240 end @@ -41,30 +79,6 @@ local function grow_param2_step(param2, snap_into_grid) return param2, param2 ~= old_param2 end -local function kelp_check_place(pos_above, node_above, def_above) - if minetest.get_item_group(node_above.name, "water") == 0 then - return false - end - local can_place = false - if (def_above.liquidtype == "source") then - can_place = true - elseif (def_above.liquidtype == "flowing") then - -- Check if bit 3 (downwards flowing) is set - can_place = (math.floor(node_above.param2 / 8) % 2) == 1 - if not can_place then - -- 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_above = minetest.get_node({x=pos_above.x,y=pos_above.y+1,z=pos_above.z}) - local naa_def = minetest.registered_nodes[node_above_above.name] - can_place = naa_def.liquidtype == "source" - if not can_place then - can_place = (naa_def.liquidtype == "flowing") and ((math.floor(node_above_above.param2 / 8) % 2) == 1) - end - end - end - return can_place -end - local function kelp_on_place(itemstack, placer, pointed_thing) if pointed_thing.type ~= "node" or not placer then return itemstack @@ -74,8 +88,9 @@ local function kelp_on_place(itemstack, placer, pointed_thing) local pos_under = pointed_thing.under local pos_above = pointed_thing.above local node_under = minetest.get_node(pos_under) + local nu_name = node_under.name local node_above = minetest.get_node(pos_above) - local def_under = minetest.registered_nodes[node_under.name] + local def_under = minetest.registered_nodes[nu_name] local def_above = minetest.registered_nodes[node_above.name] if def_under and def_under.on_rightclick and not placer:get_player_control().sneak then @@ -93,56 +108,56 @@ local function kelp_on_place(itemstack, placer, pointed_thing) return itemstack end - local grow_kelp = false - -- Select a kelp node when placed on surface node - if node_under.name == "mcl_core:dirt" then - node_under.name = "mcl_ocean:kelp_dirt" - elseif node_under.name == "mcl_core:sand" then - node_under.name = "mcl_ocean:kelp_sand" - elseif node_under.name == "mcl_core:redsand" then - node_under.name = "mcl_ocean:kelp_redsand" - elseif node_under.name == "mcl_core:gravel" then - node_under.name = "mcl_ocean:kelp_gravel" - elseif minetest.get_item_group(node_under.name, "kelp") == 1 then - -- Place kelp on kelp = grow kelp by 1 node length - node_under.param2, grow_kelp = grow_param2_step(node_under.param2) - if not grow_kelp then + local new_kelp = false + + -- 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) + + -- Kelp must not reach the height limit. + -- Kelp must be placed on top of kelp to add kelp. + if not new_kelp or + pos_under.y >= pos_above.y or pos_under.x ~= pos_above.x or pos_under.z ~= pos_above.z then return itemstack end + + -- TODO: is this even possible??? + if pos_under.y < pos_above.y and (pos_under.x ~= pos_above.x or pos_under.z ~= pos_above.z) then + minetest.chat_send_all(dump2(pos_under, "pos_under")) --DEBUG + end + + -- New kelp top must also be submerged in water. + local pos_top, node_top = get_kelp_top(pos_under, node_under) + local def_top = minetest.registered_nodes[node_top.name] + if not (is_submerged(node_top, def_top) and is_downward_flowing(pos_top, node_top, def_top)) then + return itemstack + end + + -- When placed on surface. else - return itemstack + -- Surface must support kelp + for _,surface in pairs(surfaces) do + if nu_name == surface[2] then + node_under.name = "mcl_ocean:kelp_" ..surface[1] + new_kelp = true + end + end + + -- The surface must support kelp + -- Kelp must be placed on top of surface to add new kelp. + if not new_kelp or pos_under.y >= pos_above.y then + return itemstack + end + + -- New kelp must also be submerged in water. + if not (is_submerged(node_above, def_above) and is_downward_flowing(pos_above, node_above, def_above)) then + return itemstack + end + node_under.param2 = minetest.registered_items[nu_name].place_param2 or 16 end - local submerged = false - if grow_kelp then - -- Kelp placed on kelp ... - -- Kelp can be placed on top of another kelp to make it grow - if pos_under.y >= pos_above.y or pos_under.x ~= pos_above.x or pos_under.z ~= pos_above.z then - -- Placed on side or below node, abort - return itemstack - end - -- New kelp top must also be submerged in water - local top_pos, top_node = get_kelp_top(pos_under, node_under) - local top_def = minetest.registered_nodes[top_node.name] - submerged = kelp_check_place(top_pos, top_node, top_def) - if not submerged then - -- Not submerged in water, abort - return itemstack - end - else - -- New kelp placed ... - if pos_under.y >= pos_above.y then - -- Placed on side or below node, abort - return itemstack - end - -- Kelp can be placed inside a water source or water flowing downwards on top of a surface node - local can_place = kelp_check_place(pos_above, node_above, def_above) - if not can_place then - return itemstack - end - node_under.param2 = minetest.registered_items[node_under.name].place_param2 or 16 - end - -- Place or grow kelp - local def_node = minetest.registered_items[node_under.name] + + -- Play sound, set surface/kelp and take away an item + local def_node = minetest.registered_items[nu_name] if def_node.sounds then minetest.sound_play(def_node.sounds.place, { gain = 0.5, pos = pos_under }, true) end @@ -154,8 +169,36 @@ local function kelp_on_place(itemstack, placer, pointed_thing) return itemstack end -local get_kelp_height = function(param2) - return math.floor(param2 / 16) +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") + end +end + +-- Dig kelp: +-- Each kelp from broken stem until the top drop a single item +-- Kelp's height decreases to the height below dig_pos +local function kelp_dig(dig_pos, pos, node, is_drop) + local param2 = node.param2 + local height = get_kelp_height(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, height) + end + minetest.set_node(pos, {name=minetest.registered_nodes[node.name].node_dig_prediction}) + + -- Digs the kelp beginning at a height + else + if is_drop then + kelp_drop(dig_pos, height - new_height) + end + minetest.set_node(pos, {name=node.name, param=node.param, param2=16*new_height}) + end end minetest.register_craftitem("mcl_ocean:kelp", { @@ -220,19 +263,16 @@ for s=1, #surfaces do sounds = sounds, node_dig_prediction = surfaces[s][2], after_dig_node = function(pos) - minetest.set_node(pos, {name=surfaces[s][2]}) + minetest.set_node(pos, {name=surface[s][2]}) end, + -- NOTE: whenever it becomes possible to fully implement kelp without the + -- plantlike_rooted limitation, please adapt the code accordingly. on_dig = function(pos, node, digger) - -- Drop kelp as item; item count depends on height - local dname = "" - if digger then - dname = digger:get_player_name() + local is_drop = true + if digger and minetest.is_creative_enabled(digger:get_player_name()) then + is_drop = false end - local creative = minetest.is_creative_enabled(dname) - if not creative then - minetest.add_item({x=pos.x, y=pos.y+1, z=pos.z}, "mcl_ocean:kelp "..get_kelp_height(node.param2)) - end - minetest.node_dig(pos, node, digger) + kelp_dig(pos, pos, node, is_drop) end, drop = "", -- drops are handled in on_dig _mcl_falling_node_alternative = alt, @@ -314,28 +354,46 @@ minetest.register_craft({ minetest.register_abm({ label = "Kelp growth", nodenames = { "group:kelp" }, - interval = 45, - chance = 12, + -- interval = 45, + -- chance = 12, + interval = 1, + chance = 1, catch_up = false, action = function(pos, node, active_object_count, active_object_count_wider) local grown -- Grow kelp by 1 node length if it would grow inside water - node.param2, grown = grow_param2_step(node.param2, true) - local top, top_node = get_kelp_top(pos, node) - local submerged = get_submerged(top_node) - if grown then + node.param2, grow = grow_param2_step(node.param2, true) + local pos_top, node_top = get_kelp_top(pos, node) + local def_top = minetest.registered_nodes[node_top.name] + local submerged = is_submerged(node_top, def_top) + if grow then if submerged == "source" then -- Liquid source: Grow normally minetest.set_node(pos, node) - elseif submerged == "flowing" then + + elseif submerged == "flowing" and is_downward_flowing(pos_top, node_top, def_top) then -- Flowing liquid: Grow 1 step, but also turn the top node into a liquid source minetest.set_node(pos, node) - local def_liq = minetest.registered_nodes[top_node.name] - local alt_liq = def_liq and def_liq.liquid_alternative_source + local alt_liq = def_top.liquid_alternative_source if alt_liq then - minetest.set_node(top, {name=alt_liq}) + minetest.set_node(pos_top, {name=alt_liq}) end end end end, }) + +-- Break kelp not underwater. +minetest.register_abm({ + label = "Kelp drops", + 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 +}) From ebf9f8c918f74255ae0f4adfc6440f7cd81cd9a0 Mon Sep 17 00:00:00 2001 From: iliekprogrammar Date: Sun, 21 Mar 2021 12:16:56 +0800 Subject: [PATCH 02/12] placing kelp now creates water sources, small refactoring. --- mods/ITEMS/mcl_ocean/kelp.lua | 118 +++++++++++++++------------------- 1 file changed, 52 insertions(+), 66 deletions(-) diff --git a/mods/ITEMS/mcl_ocean/kelp.lua b/mods/ITEMS/mcl_ocean/kelp.lua index 9d6c09279..4d5bf094c 100644 --- a/mods/ITEMS/mcl_ocean/kelp.lua +++ b/mods/ITEMS/mcl_ocean/kelp.lua @@ -1,7 +1,9 @@ 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. --- List of supported surfaces for seagrass and kelp +-- List of supported surfaces for seagrass and kelp. local surfaces = { { "dirt", "mcl_core:dirt" }, { "sand", "mcl_core:sand", 1 }, @@ -12,22 +14,13 @@ local surfaces = { -- Is this water? local function is_submerged(node, nodedef) if minetest.get_item_group(node.name, "water") ~= 0 then - local liquidtype = nodedef.liquidtype - -- TODO: is it OK to optimize this to: - -- return nodedef.liquidtype - if liquidtype == "source" then - return "source" - elseif liquidtype == "flowing" then - return "flowing" - else - minetest.chat_send_all("its NOT OK to optimize into return nodedef.liquidtype :(") - end + return nodedef.liquidtype -- Expected only "source" and "flowing" from water liquids end return false end -- Is the water downward flowing? --- (kelp can grow inside downward flowing water) +-- (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 @@ -41,10 +34,12 @@ local function is_downward_flowing(pos, node, nodedef, is_above) return result end +-- Converts param2 to kelp height. local function get_kelp_height(param2) return math.floor(param2 / 16) end +-- Obtain pos and node of the top of kelp. local function get_kelp_top(pos, node) local size = math.ceil(node.param2 / 16) local pos_top = table.copy(pos) @@ -52,7 +47,7 @@ local function get_kelp_top(pos, node) return pos_top, minetest.get_node(pos_top) end --- Obtain position of the first kelp unsubmerged +-- Obtain position of the first kelp unsubmerged. local function get_kelp_unsubmerged(pos, node) local x,y,z = pos.x, pos.y, pos.z local height = get_kelp_height(node.param2) @@ -65,20 +60,32 @@ local function get_kelp_unsubmerged(pos, node) return nil end -local function grow_param2_step(param2, snap_into_grid) - local old_param2 = param2 - param2 = param2 + 16 +-- 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 + + local old_param2 = param2 + param2 = param2+16 - param2 % 16 if param2 > 240 then param2 = 240 end - if snap_into_grid and (param2 % 16 ~= 0) then - param2 = param2 - (param2 % 16) - end return param2, param2 ~= old_param2 end +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 + 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}) + end + end +end + local function kelp_on_place(itemstack, placer, pointed_thing) if pointed_thing.type ~= "node" or not placer then return itemstack @@ -89,9 +96,7 @@ local function kelp_on_place(itemstack, placer, pointed_thing) local pos_above = pointed_thing.above local node_under = minetest.get_node(pos_under) local nu_name = node_under.name - local node_above = minetest.get_node(pos_above) local def_under = minetest.registered_nodes[nu_name] - local def_above = minetest.registered_nodes[node_above.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, @@ -109,59 +114,52 @@ local function kelp_on_place(itemstack, placer, pointed_thing) end local new_kelp = false + local is_downward_flowing = false + 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) - -- Kelp must not reach the height limit. - -- Kelp must be placed on top of kelp to add kelp. - if not new_kelp or - pos_under.y >= pos_above.y or pos_under.x ~= pos_above.x or pos_under.z ~= pos_above.z then - return itemstack - end - - -- TODO: is this even possible??? - if pos_under.y < pos_above.y and (pos_under.x ~= pos_above.x or pos_under.z ~= pos_above.z) then - minetest.chat_send_all(dump2(pos_under, "pos_under")) --DEBUG - end - - -- New kelp top must also be submerged in water. - local pos_top, node_top = get_kelp_top(pos_under, node_under) - local def_top = minetest.registered_nodes[node_top.name] - if not (is_submerged(node_top, def_top) and is_downward_flowing(pos_top, node_top, def_top)) then + -- 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] -- When placed on surface. else - -- Surface must support kelp 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 new_kelp = true end end - -- The surface must support kelp - -- Kelp must be placed on top of surface to add new kelp. + -- Kelp must also be placed on top of surface to add new kelp. if not new_kelp or pos_under.y >= pos_above.y then return itemstack end - - -- New kelp must also be submerged in water. - if not (is_submerged(node_above, def_above) and is_downward_flowing(pos_above, node_above, def_above)) then - return itemstack - end - node_under.param2 = minetest.registered_items[nu_name].place_param2 or 16 + pos_top = pos_above + node_top = minetest.get_node(pos_above) + def_top = minetest.registered_nodes[node_above.name] end - -- Play sound, set surface/kelp and take away an item + -- New kelp must also be submerged in water. + is_downward_flowing = is_downward_flowing(pos_top, node_top, def_top) + if not (is_submerged(node_top, def_top) and is_downward_flowing) then + return itemstack + end + + -- Play sound, place surface/kelp and take away an item local def_node = minetest.registered_items[nu_name] if def_node.sounds then minetest.sound_play(def_node.sounds.place, { gain = 0.5, pos = pos_under }, true) end - minetest.set_node(pos_under, node_under) + kelp_place(pos_under, node_under, pos_top, def_top, is_downward_flowing) if not minetest.is_creative_enabled(player_name) then itemstack:take_item() end @@ -169,6 +167,7 @@ local function kelp_on_place(itemstack, placer, pointed_thing) return itemstack end +-- 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 @@ -265,8 +264,6 @@ for s=1, #surfaces do after_dig_node = function(pos) minetest.set_node(pos, {name=surface[s][2]}) end, - -- NOTE: whenever it becomes possible to fully implement kelp without the - -- plantlike_rooted limitation, please adapt the code accordingly. on_dig = function(pos, node, digger) local is_drop = true if digger and minetest.is_creative_enabled(digger:get_player_name()) then @@ -360,25 +357,14 @@ minetest.register_abm({ chance = 1, catch_up = false, action = function(pos, node, active_object_count, active_object_count_wider) - local grown + local grow -- Grow kelp by 1 node length if it would grow inside water - node.param2, grow = grow_param2_step(node.param2, true) + 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] - local submerged = is_submerged(node_top, def_top) - if grow then - if submerged == "source" then - -- Liquid source: Grow normally - minetest.set_node(pos, node) - - elseif submerged == "flowing" and is_downward_flowing(pos_top, node_top, def_top) then - -- Flowing liquid: Grow 1 step, but also turn the top node into a liquid source - minetest.set_node(pos, node) - local alt_liq = def_top.liquid_alternative_source - if alt_liq then - minetest.set_node(pos_top, {name=alt_liq}) - end - end + 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, }) From ca635b69be351a8cd8dfcaafa2536f659932e6c9 Mon Sep 17 00:00:00 2001 From: iliekprogrammar Date: Sun, 21 Mar 2021 14:06:54 +0800 Subject: [PATCH 03/12] Various fixes. --- mods/ITEMS/mcl_ocean/kelp.lua | 39 +++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/mods/ITEMS/mcl_ocean/kelp.lua b/mods/ITEMS/mcl_ocean/kelp.lua index 4d5bf094c..73b2aaeb4 100644 --- a/mods/ITEMS/mcl_ocean/kelp.lua +++ b/mods/ITEMS/mcl_ocean/kelp.lua @@ -2,6 +2,8 @@ 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 = { @@ -25,11 +27,13 @@ local function is_downward_flowing(pos, node, nodedef, is_above) result = (math.floor(node.param2 / 8) % 2) == 1 if not (result or is_above) then - -- If not, also check node above (this is needed due a weird quirk in the definition of - -- "downwards flowing" liquids in Minetest) + -- 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] - result = is_submerged(node_above, nodedef_above) or is_downward_flowing(pos, node_above, nodedef_above, true) + result = is_submerged(node_above, nodedef_above) + or is_downward_flowing(pos, node_above, nodedef_above, true) end return result end @@ -114,7 +118,7 @@ local function kelp_on_place(itemstack, placer, pointed_thing) end local new_kelp = false - local is_downward_flowing = false + local downward_flowing = false local pos_top, node_top, def_top -- When placed on kelp. @@ -136,6 +140,7 @@ local function kelp_on_place(itemstack, placer, pointed_thing) node_under.name = "mcl_ocean:kelp_" ..surface[1] node_under.param2 = minetest.registered_items[nu_name].place_param2 or 16 new_kelp = true + break end end @@ -143,14 +148,15 @@ local function kelp_on_place(itemstack, placer, pointed_thing) if not new_kelp or pos_under.y >= pos_above.y then return itemstack end + pos_top = pos_above node_top = minetest.get_node(pos_above) - def_top = minetest.registered_nodes[node_above.name] + def_top = minetest.registered_nodes[node_top.name] end -- New kelp must also be submerged in water. - is_downward_flowing = is_downward_flowing(pos_top, node_top, def_top) - if not (is_submerged(node_top, def_top) and is_downward_flowing) then + downward_flowing = is_downward_flowing(pos_top, node_top, def_top) + if not (is_submerged(node_top, def_top) or downward_flowing) then return itemstack end @@ -159,7 +165,7 @@ local function kelp_on_place(itemstack, placer, pointed_thing) if def_node.sounds then minetest.sound_play(def_node.sounds.place, { gain = 0.5, pos = pos_under }, true) end - kelp_place(pos_under, node_under, pos_top, def_top, is_downward_flowing) + kelp_place(pos_under, node_under, pos_top, def_top, downward_flowing) if not minetest.is_creative_enabled(player_name) then itemstack:take_item() end @@ -175,26 +181,24 @@ local function kelp_drop(pos, height) end end --- Dig kelp: --- Each kelp from broken stem until the top drop a single item --- Kelp's height decreases to the height below dig_pos local function kelp_dig(dig_pos, pos, node, is_drop) local param2 = node.param2 - local height = get_kelp_height(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 new_height <= 0 then if is_drop then - kelp_drop(dig_pos, height) + kelp_drop(dig_pos, get_kelp_height(param2)) end - minetest.set_node(pos, {name=minetest.registered_nodes[node.name].node_dig_prediction}) + 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, height - new_height) + 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}) end @@ -264,7 +268,10 @@ for s=1, #surfaces do 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 From e76a0ba6e859a4aba665a849864f9799b87a8b87 Mon Sep 17 00:00:00 2001 From: iliekprogrammar Date: Sun, 28 Mar 2021 14:11:08 +0800 Subject: [PATCH 04/12] 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 73b2aaeb4..fe2266789 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 +-- }) From 24da94ec3b413b1b41121e2003573cf45b810ed3 Mon Sep 17 00:00:00 2001 From: iliekprogrammar Date: Sun, 28 Mar 2021 20:29:29 +0800 Subject: [PATCH 05/12] Disable ABMs in favor of nodetimers to check if kelp is unsubmerged. --- mods/ITEMS/mcl_ocean/kelp.lua | 164 +++++++++++++++++++--------------- 1 file changed, 94 insertions(+), 70 deletions(-) diff --git a/mods/ITEMS/mcl_ocean/kelp.lua b/mods/ITEMS/mcl_ocean/kelp.lua index fe2266789..a4de943b7 100644 --- a/mods/ITEMS/mcl_ocean/kelp.lua +++ b/mods/ITEMS/mcl_ocean/kelp.lua @@ -1,7 +1,11 @@ --- NOTE: whenever it becomes possible to fully implement kelp without the +-- TODO: 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. +-- +-- TODO: Currently, you lose kelp if the kelp is placed on a block and then falls. +-- This is most relevant for (red)sand and gravel. local S = minetest.get_translator("mcl_ocean") local mod_doc = minetest.get_modpath("doc") ~= nil @@ -25,6 +29,7 @@ local mt_hash_node_position = minetest.hash_node_position local mt_get_node_timer = minetest.get_node_timer -- DEBUG: functions +local log = minetest.log local chatlog = minetest.chat_send_all -------------------------------------------------------------------------------- @@ -130,7 +135,9 @@ kelp.detach_drop = detach_drop -- Detach the kelp at dig_pos, and drop their items. --- Synonyous to digging the kelp +-- Synonymous to digging the kelp. +-- NOTE: this is intended for whenever kelp truly becomes segmented plants +-- instead of rooted to the floor. Don't try to remove dig_pos. 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. @@ -159,6 +166,72 @@ kelp.detach_dig = detach_dig -- Kelp callback functions -------------------------------------------------------------------------------- +local function surface_on_dig(pos, node, digger) + -- NOTE: if instead, kelp shouldn't drop in creative: use this instead + -- 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 kelp_timers = {} +local kelp_timers_counter = 0 +local function surface_on_timer(pos, elapsed) + local node = mt_get_node(pos) + local dig_pos = find_unsubmerged(pos, node) + if dig_pos then + detach_dig(dig_pos, pos, node, true) + end + return true +end + + +-- NOTE: Uncomment this to use ABMs +-- local function surface_unsubmerged_abm(pos, node) +-- local dig_pos = find_unsubmerged(pos, node) +-- if dig_pos then +-- detach_dig(dig_pos, pos, node, true) +-- end +-- return true +-- end + + +-- NOTE: Uncomment this to use nodetimers +local function surface_register_nodetimer(pos, node) + local pos_hash = mt_hash_node_position(pos) + if kelp_timers[pos_hash] then + return + end + local timer = mt_get_node_timer(pos) + kelp_timers[pos_hash] = timer + timer:start(0.5) + kelp_timers_counter = kelp_timers_counter + 1 + chatlog("added a timer. Currently " ..tostring(kelp_timers_counter) .." timers") +end +kelp.surface_register_nodetiemr = surface_register_nodetimer + + +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_on_place(itemstack, placer, pointed_thing) if pointed_thing.type ~= "node" or not placer then return itemstack @@ -171,6 +244,7 @@ local function kelp_on_place(itemstack, placer, pointed_thing) local nu_name = node_under.name local def_under = registered_nodes[nu_name] + -- Allow rightclick override. 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 @@ -222,6 +296,10 @@ local function kelp_on_place(itemstack, placer, pointed_thing) pos_top = pos_above node_top = mt_get_node(pos_above) def_top = registered_nodes[node_top.name] + + -- NOTE: Uncomment this to use nodetimers + -- Register nodetimer + surface_register_nodetimer(pos_under, node_under) end -- New kelp must also be submerged in water. @@ -244,50 +322,6 @@ local function kelp_on_place(itemstack, placer, pointed_thing) end kelp.kelp_on_place = kelp_on_place - -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 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 -------------------------------------------------------------------------------- @@ -303,8 +337,8 @@ 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. +-- Commented properties are the ones obtained using register_kelp_surface. +-- If you define your own properties, it overrides the default ones. local surface_deftemplate = { drawtype = "plantlike_rooted", paramtype = "light", @@ -333,6 +367,7 @@ local surface_deftemplate = { --node_dig_prediction = nodename, after_dig_node = surface_after_dig_node, on_dig = surface_on_dig, + on_timer = surface_on_timer, drop = "", -- drops are handled in on_dig --_mcl_falling_node_alternative = is_falling and nodename or nil, _mcl_hardness = 0, @@ -340,7 +375,7 @@ local surface_deftemplate = { } kelp.surface_deftemplate = surface_deftemplate --- Commented keys are the ones obtained using register_kelp_surface. +-- Commented properties are the ones obtained using register_kelp_surface. local surface_docs = { -- entry_id_orig = nodename, _doc_items_entry_name = S("Kelp"), @@ -353,19 +388,12 @@ 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: table with its specific properties. See also kelp.surface. 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. +surface_docs: table with properties 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) @@ -403,7 +431,7 @@ local function register_kelp_surface(surface, surface_deftemplate, surface_docs) 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.groups.falling_node = surface_deftemplate.groups.falling_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) @@ -496,7 +524,7 @@ minetest.register_craft({ -- ABMs ------------------------------------------------------------------------ minetest.register_abm({ - label = "mcl_ocean:Kelp growth", + label = "Kelp growth", nodenames = { "group:kelp" }, interval = 45, chance = 12, @@ -504,26 +532,22 @@ minetest.register_abm({ action = grow_kelp, }) +-- NOTE: Uncomment this to use nodetimers minetest.register_lbm({ label = "Kelp timer registration", name = "mcl_ocean:kelp_timer_registration", nodenames = { "group:kelp" }, - run_at_every_load = true, + run_at_every_load = false, action = surface_register_nodetimer, }) --- TODO: test if nodetimers are more efficient than ABM --- -- Break kelp not underwater. +-- NOTE: Uncomment this to use ABMs +-- Break kelp not underwater. -- minetest.register_abm({ -- label = "Kelp drops", -- nodenames = { "group:kelp" }, --- interval = 0.5, +-- interval = 1.0, -- 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 +-- action = surface_unsubmerged_abm, -- }) From ecdbc30b63998e4fdcb3004ca892707b54bd7d2f Mon Sep 17 00:00:00 2001 From: iliekprogrammar Date: Mon, 29 Mar 2021 14:09:09 +0800 Subject: [PATCH 06/12] Overhaul kelp interactions. Highlights: - Added locking system to drops. Will be removed after testing. - Expose more variables and functions. Will be finalized soon. - Implement MC-like age and natural growth mechanics. - Implement correct piston interactions with kelp and its surface. - Implement correct falling node interactions for its surface. - ABMs are now nonfunction. Will be fixed. - Various optimizations and fixes. --- mods/ITEMS/mcl_ocean/init.lua | 2 + mods/ITEMS/mcl_ocean/kelp.lua | 484 ++++++++++++++++++++++------------ 2 files changed, 320 insertions(+), 166 deletions(-) diff --git a/mods/ITEMS/mcl_ocean/init.lua b/mods/ITEMS/mcl_ocean/init.lua index 2a103b8d0..f723a1f3f 100644 --- a/mods/ITEMS/mcl_ocean/init.lua +++ b/mods/ITEMS/mcl_ocean/init.lua @@ -1,3 +1,5 @@ +mcl_ocean = {} + -- Prismarine (includes sea lantern) dofile(minetest.get_modpath(minetest.get_current_modname()).."/prismarine.lua") diff --git a/mods/ITEMS/mcl_ocean/kelp.lua b/mods/ITEMS/mcl_ocean/kelp.lua index a4de943b7..741d9e59a 100644 --- a/mods/ITEMS/mcl_ocean/kelp.lua +++ b/mods/ITEMS/mcl_ocean/kelp.lua @@ -3,9 +3,6 @@ -- -- 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. --- --- TODO: Currently, you lose kelp if the kelp is placed on a block and then falls. --- This is most relevant for (red)sand and gravel. local S = minetest.get_translator("mcl_ocean") local mod_doc = minetest.get_modpath("doc") ~= nil @@ -14,19 +11,28 @@ 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 +local mt_registered_items = minetest.registered_items +local mt_registered_nodes = minetest.registered_nodes -- functions +local mt_add_item = minetest.add_item 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_level = minetest.get_node_level +local mt_get_node_max_level = minetest.get_node_max_level +local mt_get_node_or_nil = minetest.get_node_or_nil local mt_get_node_timer = minetest.get_node_timer +local mt_hash_node_position = minetest.hash_node_position +local mt_is_protected = minetest.is_protected +local mt_set_node = minetest.set_node +local mt_get_meta = minetest.get_meta + +local mt_is_creative_enabled = minetest.is_creative_enabled +local mt_sound_play = minetest.sound_play + +local math_floor = math.floor +local math_random = math.random +local string_format = string.format -- DEBUG: functions local log = minetest.log @@ -36,109 +42,218 @@ local chatlog = minetest.chat_send_all -- Kelp API -------------------------------------------------------------------------------- -kelp = {} +local kelp = {} +mcl_ocean.kelp = kelp + +kelp.MAX_AGE = 25 +kelp.TIMER_INTERVAL = 0.2 + +-- The average amount of growth for kelp in a day is 2.16 (https://youtu.be/5Bp4lAjAk3I) +-- Normally, a day lasts 20 minutes, meaning this nodetimer is executed 24000 times. +-- Calculate probability via 2.16/24000 and we get the probability 9/100'000 or 9.0e-5 +-- NOTE: currently, we can't exactly use the same type of randomness MC does, +-- because it has multiple complicated sets of PRNGs. +-- kelp.RANDOM_NUMERATOR = 9 +kelp.RANDOM_NUMERATOR = 100 +kelp.RANDOM_DENOMINATOR = 100000 + +kelp.leaf_sounds = mcl_sounds.node_sound_leaves_defaults() + +-- TODO: is this really necessary +-- Lock drops to avoid duplicate drops, set after dropping detached kelp. +kelp.lock_drop = 0 + +-- Registrar of nodetimers, indexed by pos_hash. +kelp.registered_nodetimers = {} + +-- Pool storing age, indexed by pos_hash. +kelp.age_pool = {} + + +-- is age in the growable range? +function kelp.is_age_growable(age) + return age >= 0 and age < kelp.MAX_AGE +end + -- Is this water? -- Returns the liquidtype, if indeed water. -local function is_submerged(node, nodedef) +function kelp.is_submerged(node, def) if mt_get_item_group(node.name, "water") ~= 0 then - return nodedef.liquidtype -- Expected only "source" and "flowing" from water liquids + return def.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) - local result = (math.floor(node.param2 / 8) % 2) == 1 +function kelp.is_downward_flowing(pos, node, def, is_above) + local result = (math_floor(node.param2 / 8) % 2) == 1 if not (result or is_above) then -- 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 = 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) + local def_above = mt_registered_nodes[node_above.name] + result = kelp.is_submerged(node_above, def_above) + or kelp.is_downward_flowing(pos, node_above, def_above, true) end return result end -kelp.is_downward_flowing = is_downward_flowing + + +-- Will node fall at that position? +-- This only checks if a node would fall, meaning that node need not be at pos. +function kelp.is_falling(pos, node) + -- NOTE: Modified from check_single_for_falling in builtin. + -- Please update as necessary. + local nodename = node.name + + if mt_get_item_group(nodename, "falling_node") == 0 then + return false + end + + local pos_bottom = {x = pos.x, y = pos.y - 1, z = pos.z} + -- get_node_or_nil: Only fall if node below is loaded + local node_bottom = mt_get_node_or_nil(pos_bottom) + local nodename_bottom = node_bottom.name + local def_bottom = node_bottom and mt_registered_nodes[nodename_bottom] + if not def_bottom then + return false + end + + local same = nodename == nodename_bottom + -- Let leveled nodes fall if it can merge with the bottom node + if same and def_bottom.paramtype2 == "leveled" and + mt_get_node_level(pos_bottom) < + mt_get_node_max_level(pos_bottom) then + return true + end + + -- Otherwise only if the bottom node is considered "fall through" + if not same and + (not def_bottom.walkable or def_bottom.buildable_to) and + (mt_get_item_group(nodename, "float") == 0 or + def_bottom.liquidtype == "none") then + return true + end + + return false +end -- Converts param2 to kelp height. -local function get_height(param2) - return math.floor(param2 / 16) +function kelp.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_tip(pos, node) +-- Obtain pos and node of the tip of kelp. +function kelp.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, mt_get_node(pos_top) + local pos_tip = table.copy(pos) + pos_tip.y = pos_tip.y + size + return pos_tip, mt_get_node(pos_tip) end -kelp.get_tip = get_tip -- Obtain position of the first kelp unsubmerged. -local function find_unsubmerged(pos, node) +function kelp.find_unsubmerged(pos, node) + local x,y,z = pos.x, pos.y, pos.z - local height = get_height(node.param2) + local height = kelp.get_height(node.param2) + local walk_pos = {x=x, z=z} for i=1,height do - local walk_pos = {x=x, y=y + i, z=z} - if mt_get_item_group(mt_get_node(walk_pos).name, "water") == 0 then - return walk_pos + walk_pos.y = y + i + local walk_node = mt_get_node(walk_pos) + if mt_get_item_group(walk_node.name, "water") == 0 then + return walk_pos, walk_node end end return nil end -kelp.find_unsubmerged = find_unsubmerged -- Obtain next param2. -local function next_param2(param2) - local old_param2 = param2 - param2 = param2+16 - param2 % 16 - return param2, param2 ~= old_param2 +function kelp.next_param2(param2) + return param2+16 - param2 % 16 end -kelp.next_param2 = next_param2 -- Grow next kelp. -local function next_grow(pos, node, pos_top, def_top, is_downward_flowing) +function kelp.next_grow(pos, node, pos_tip, def_tip, downward_flowing) + -- Optional parameters + local pos_tip, def_tip = pos_tip, def_tip + local downward_flowing = downward_flowing + if pos_tip == nil and def_tip == nil then + local node_tip + pos_tip, node_tip = kelp.get_tip(pos, node) + def_tip = mt_registered_nodes[node_tip.name] + downward_flowing = kelp.is_submerged(pos_tip, node_tip) + and kelp.is_downward_flowing(pos_tip, node_tip, def_tip) + end + -- Liquid source: Grow normally. + local node = table.copy(node) + node.param2 = kelp.next_param2(node.param2) 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 + -- Flowing liquid: Grow 1 step, but also turn the tip node into a liquid source. + if downward_flowing then + local alt_liq = def_tip.liquid_alternative_source if alt_liq then - mt_set_node(pos_top, {name=alt_liq}) + mt_set_node(pos_tip, {name=alt_liq}) end end end -kelp.next_grow = next_grow + + +-- Naturally grow next kelp. +function kelp.natural_grow(pos, node, age, pos_hash, meta) + -- Must grow first, then get the new meta + kelp.next_grow(pos, node) + + -- Optional params + local meta = meta or mt_get_meta(pos) + local pos_hash = pos_hash or mt_hash_node_position(pos) + + local age = age + 1 + kelp.age_pool[pos_hash] = age + meta:set_int("mcl_ocean:kelp_age", age) + +end -- Drops the items for detached kelps. -local function detach_drop(pos, height) +function kelp.detach_drop(pos, height, pos_hash) + local pos_hash = pos_hash or mt_hash_node_position(pos) -- Optional params + + if kelp.lock_drop > 0 then + minetest.log("error", + string_format("Duplicate drop prevented at (%d, %d, %d) with lock level %d! Please report this.", + pos.x, pos.y, pos.z, kelp.lock_drop)) + return + end + 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 + + -- Locks drop. + kelp.lock_drop = kelp.lock_drop + 1 + return true end -kelp.detach_drop = detach_drop -- Detach the kelp at dig_pos, and drop their items. -- Synonymous to digging the kelp. -- NOTE: this is intended for whenever kelp truly becomes segmented plants -- instead of rooted to the floor. Don't try to remove dig_pos. -local function detach_dig(dig_pos, pos, node, is_drop) +function kelp.detach_dig(dig_pos, pos, node, is_drop, pos_hash) + local pos_hash = pos_hash or mt_hash_node_position(pos) + 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) @@ -146,52 +261,77 @@ local function detach_dig(dig_pos, pos, node, is_drop) -- 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)) + kelp.detach_drop(dig_pos, kelp.get_height(param2), pos_hash) end mt_set_node(pos, { - name=registered_nodes[node.name].node_dig_prediction, + name=mt_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) + kelp.detach_drop(dig_pos, kelp.get_height(param2) - new_height, pos_hash) 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 surface_on_dig(pos, node, digger) - -- NOTE: if instead, kelp shouldn't drop in creative: use this instead - -- detach_dig(pos, pos, node, - -- not (digger and mt_is_creative_enabled(digger:get_player_name()))) - detach_dig(pos, pos, node, true) +-- Set this to drop kelps when +function kelp.surface_on_dig(pos, node, digger) + kelp.detach_dig(pos, pos, node, true) end -kelp.surface_on_dig = surface_on_dig -local function surface_after_dig_node(pos, node) +function kelp.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 kelp_timers = {} -local kelp_timers_counter = 0 -local function surface_on_timer(pos, elapsed) +function kelp.surface_on_timer(pos, elapsed) local node = mt_get_node(pos) - local dig_pos = find_unsubmerged(pos, node) + local dig_pos = kelp.find_unsubmerged(pos, node) + local pos_hash = mt_hash_node_position(pos) if dig_pos then - detach_dig(dig_pos, pos, node, true) + kelp.detach_dig(dig_pos, pos, node, true, pos_hash) end + + -- Grow kelp on chance + if math_random(kelp.RANDOM_DENOMINATOR) - kelp.RANDOM_NUMERATOR < 0 then + local age = kelp.age_pool[pos_hash] + if kelp.is_age_growable(age) then + kelp.natural_grow(pos, node, age, pos_hash) + end + end + return true end +function kelp.surface_on_destruct(pos) + local node = mt_get_node(pos) + local pos_hash = mt_hash_node_position(pos) + + -- on_falling callback. Activated by pistons for falling nodes too. + if kelp.is_falling(pos, node) then + kelp.detach_drop(pos, kelp.get_height(node.param2), pos_hash) + end + + -- Unlocks drops. + if kelp.lock_drop > 0 then + kelp.lock_drop = kelp.lock_drop - 1 + end +end + + +function kelp.surface_on_mvps_move(pos, node, oldpos, nodemeta) + -- Pistons moving falling nodes will have already activated on_falling callback. + kelp.detach_dig(pos, pos, node, mt_get_item_group(node.name, "falling_node") ~= 1) +end + -- NOTE: Uncomment this to use ABMs -- local function surface_unsubmerged_abm(pos, node) @@ -204,35 +344,52 @@ end -- NOTE: Uncomment this to use nodetimers -local function surface_register_nodetimer(pos, node) - local pos_hash = mt_hash_node_position(pos) - if kelp_timers[pos_hash] then - return +function kelp.surface_register_nodetimer(pos, node, pos_hash, meta, age) + -- Optional params + local pos_hash,meta,age = pos_hash,meta,age + local timer = kelp.registered_nodetimers[pos_hash] + + if not timer then + pos_hash = pos_hash or mt_hash_node_position(pos) + + timer = mt_get_node_timer(pos) + kelp.registered_nodetimers[pos_hash] = timer + + -- Pool age to avoid meta:get* operations + meta = meta or mt_get_meta(pos) + if not age then + if not meta:contains("mcl_ocean:kelp_age") then + age = math_random(0, kelp.MAX_AGE-1) + else + age = meta:get_int("mcl_ocean:kelp_age") + end + end + kelp.age_pool[pos_hash] = age + meta:set_int("mcl_ocean:kelp_age", age) end - local timer = mt_get_node_timer(pos) - kelp_timers[pos_hash] = timer - timer:start(0.5) - kelp_timers_counter = kelp_timers_counter + 1 - chatlog("added a timer. Currently " ..tostring(kelp_timers_counter) .." timers") -end -kelp.surface_register_nodetiemr = surface_register_nodetimer - -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)) + if not timer:is_started() then + timer:start(kelp.TIMER_INTERVAL) end + + return pos_hash, meta, age end -kelp.grow_kelp = grow_kelp -local function kelp_on_place(itemstack, placer, pointed_thing) +-- NOTE: Uncomment this to use ABMs +-- function kelp.grow_abm(pos, node) +-- -- Grow kelp by 1 node length if it would grow inside water +-- node.param2 = next_param2(node.param2) +-- local pos_tip, node_tip = get_tip(pos, node) +-- local def_tip = mt_registered_nodes[node_tip.name] +-- if is_submerged(node_tip, def_tip) then +-- kelp.next_grow(pos, node, pos_tip, def_tip, +-- is_downward_flowing(pos_tip, node_tip, def_tip)) +-- end +-- end + + +function kelp.kelp_on_place(itemstack, placer, pointed_thing) if pointed_thing.type ~= "node" or not placer then return itemstack end @@ -242,14 +399,15 @@ local function kelp_on_place(itemstack, placer, pointed_thing) local pos_above = pointed_thing.above local node_under = mt_get_node(pos_under) local nu_name = node_under.name - local def_under = registered_nodes[nu_name] + local def_under = mt_registered_nodes[nu_name] - -- Allow rightclick override. + -- Allow rightclick to override place. 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 + -- Protection if mt_is_protected(pos_under, player_name) or mt_is_protected(pos_above, player_name) then minetest.log("action", player_name @@ -260,86 +418,89 @@ local function kelp_on_place(itemstack, placer, pointed_thing) return itemstack end - local new_kelp = false - local downward_flowing = false - local pos_top, node_top, def_top + + local pos_tip, node_tip, def_tip + local pos_hash, meta + + + -- Kelp must also be placed on the top/tip side of the surface/kelp + if pos_under.y >= pos_above.y then + return itemstack + end -- When placed on kelp. 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_tip(pos_under, node_under) - def_top = registered_nodes[node_top.name] + pos_tip, node_tip = kelp.get_tip(pos_under, node_under) + def_tip = mt_registered_nodes[node_tip.name] + + pos_hash = mt_hash_node_position(pos_under) + meta = mt_get_meta(pos_under) -- When placed on surface. else - local surfaces = kelp.surfaces or surfaces - for _,surface in pairs(surfaces) do - -- Surface must support kelp + local new_kelp = false + for _,surface in pairs(kelp.surfaces) do 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 + node_under.param2 = 0 new_kelp = true break end end - - -- Kelp must also be placed on top of surface to add new kelp. - if not new_kelp or pos_under.y >= pos_above.y then + -- Surface must support kelp + if not new_kelp then return itemstack end - pos_top = pos_above - node_top = mt_get_node(pos_above) - def_top = registered_nodes[node_top.name] - -- NOTE: Uncomment this to use nodetimers -- Register nodetimer - surface_register_nodetimer(pos_under, node_under) + pos_hash, meta = kelp.surface_register_nodetimer(pos_under) + + pos_tip = pos_above + node_tip = mt_get_node(pos_above) + def_tip = mt_registered_nodes[node_tip.name] end -- New kelp must also be submerged in water. - downward_flowing = is_downward_flowing(pos_top, node_top, def_top) - if not (is_submerged(node_top, def_top) or downward_flowing) then + local downward_flowing = kelp.is_downward_flowing(pos_tip, node_tip, def_tip) + if not (kelp.is_submerged(node_tip, def_tip) or downward_flowing) then return itemstack end -- Play sound, place surface/kelp and take away an item - local def_node = registered_items[nu_name] + local def_node = mt_registered_items[nu_name] if def_node.sounds then mt_sound_play(def_node.sounds.place, { gain = 0.5, pos = pos_under }, true) end - next_grow(pos_under, node_under, pos_top, def_top, downward_flowing) + kelp.next_grow(pos_under, node_under, pos_tip, def_tip, downward_flowing) if not mt_is_creative_enabled(player_name) then itemstack:take_item() end + -- Reroll age + local age = math_random(0, kelp.MAX_AGE-1) + meta:set_int("mcl_ocean:kelp_age", age) + kelp.age_pool[pos_hash] = age + return itemstack end -kelp.kelp_on_place = kelp_on_place -------------------------------------------------------------------------------- -- Kelp registration API -------------------------------------------------------------------------------- -- List of supported surfaces for seagrass and kelp. -local surfaces = { +kelp.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 +kelp.registered_surfaces = {} -- Commented properties are the ones obtained using register_kelp_surface. -- If you define your own properties, it overrides the default ones. -local surface_deftemplate = { +kelp.surface_deftemplate = { drawtype = "plantlike_rooted", paramtype = "light", paramtype2 = "leveled", @@ -365,55 +526,46 @@ local surface_deftemplate = { 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, - on_timer = surface_on_timer, + after_dig_node = kelp.surface_after_dig_node, + on_destruct = kelp.surface_on_destruct, + on_dig = kelp.surface_on_dig, + on_timer = kelp.surface_on_timer, + mesecon = { on_mvps_move = kelp.surface_on_mvps_move, }, 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 properties are the ones obtained using register_kelp_surface. -local surface_docs = { +kelp.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 its specific properties. See also kelp.surface. - -surface_deftemplate: modifiable nodedef template. See also kelp.surface_deftempate. -DO NOT RE-USE THE SAME DEFTEMPLATE. create copies. - -surface_docs: table with properties 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) +-- Creates new surfaces. +-- NOTE: surface_deftemplate will be modified in-place. +function kelp.register_kelp_surface(surface, surface_deftemplate, surface_docs) local name = surface.name local nodename = surface.nodename - local def = registered_nodes[nodename] + local def = mt_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 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 + local surface_docs = surface_docs or kelp.surface_docs -- Optional param 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 + -- Sets the first surface as the docs' entry ID if not surface_docs.entry_id_orig then surface_docs.entry_id_orig = nodename end @@ -422,9 +574,9 @@ local function register_kelp_surface(surface, surface_deftemplate, surface_docs) end local sounds = table.copy(def.sounds) - sounds.dig = leaf_sounds.dig - sounds.dug = leaf_sounds.dug - sounds.place = leaf_sounds.place + sounds.dig = kelp.leaf_sounds.dig + sounds.dug = kelp.leaf_sounds.dug + sounds.place = kelp.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" @@ -440,9 +592,9 @@ 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) +kelp.register_kelp_surface(kelp.surfaces[1], table.copy(kelp.surface_deftemplate), kelp.surface_docs) +for i=2, #kelp.surfaces do + kelp.register_kelp_surface(kelp.surfaces[i], table.copy(kelp.surface_deftemplate), kelp.surface_docs) end -- Kelp item ------------------------------------------------------------------- @@ -453,12 +605,12 @@ minetest.register_craftitem("mcl_ocean:kelp", { _doc_items_create_entry = false, inventory_image = "mcl_ocean_kelp_item.png", wield_image = "mcl_ocean_kelp_item.png", - on_place = kelp_on_place, + on_place = kelp.kelp_on_place, groups = { deco_block = 1 }, }) if mod_doc then - doc.add_entry_alias("nodes", surface_docs.entry_id_orig, "craftitems", "mcl_ocean:kelp") + doc.add_entry_alias("nodes", kelp.surface_docs.entry_id_orig, "craftitems", "mcl_ocean:kelp") end -- Dried kelp ------------------------------------------------------------------ @@ -523,26 +675,17 @@ minetest.register_craft({ }) -- ABMs ------------------------------------------------------------------------ -minetest.register_abm({ - label = "Kelp growth", - nodenames = { "group:kelp" }, - interval = 45, - chance = 12, - catch_up = false, - action = grow_kelp, -}) -- NOTE: Uncomment this to use nodetimers minetest.register_lbm({ label = "Kelp timer registration", name = "mcl_ocean:kelp_timer_registration", nodenames = { "group:kelp" }, - run_at_every_load = false, - action = surface_register_nodetimer, + run_at_every_load = true, -- so old kelps are also registered + action = kelp.surface_register_nodetimer, }) -- NOTE: Uncomment this to use ABMs --- Break kelp not underwater. -- minetest.register_abm({ -- label = "Kelp drops", -- nodenames = { "group:kelp" }, @@ -551,3 +694,12 @@ minetest.register_lbm({ -- catch_up = false, -- action = surface_unsubmerged_abm, -- }) +-- +-- minetest.register_abm({ +-- label = "Kelp growth", +-- nodenames = { "group:kelp" }, +-- interval = 45, +-- chance = 12, +-- catch_up = false, +-- action = grow_abm, +-- }) From 9518086b6b43891dc2059e0a36efc0b252a1d4ff Mon Sep 17 00:00:00 2001 From: iliekprogrammar Date: Tue, 30 Mar 2021 13:28:44 +0800 Subject: [PATCH 07/12] Kelp store age metadata by bulk on a queue, improve API, etc. Highlights: - Implement storing age metadata by bulk on a queue, with maximum interval and queue length. - "Flexible" initial growth probability. Will provide helper APIs. - Fix various bugs with unsubmerged kelp detection, submerged tip detection, age metadata. - Various optimizations. --- mods/ITEMS/mcl_ocean/kelp.lua | 456 ++++++++++++++++++++++------------ 1 file changed, 301 insertions(+), 155 deletions(-) diff --git a/mods/ITEMS/mcl_ocean/kelp.lua b/mods/ITEMS/mcl_ocean/kelp.lua index 741d9e59a..7a39bc850 100644 --- a/mods/ITEMS/mcl_ocean/kelp.lua +++ b/mods/ITEMS/mcl_ocean/kelp.lua @@ -1,8 +1,9 @@ -- TODO: whenever it becomes possible to fully implement kelp without the --- plantlike_rooted limitation, please adapt the code accordingly. +-- plantlike_rooted limitation, please update 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. +-- TODO: Integrate ABMs with the new kelp code, disabled. OR remove them entirely local S = minetest.get_translator("mcl_ocean") local mod_doc = minetest.get_modpath("doc") ~= nil @@ -15,6 +16,7 @@ local mt_registered_items = minetest.registered_items local mt_registered_nodes = minetest.registered_nodes -- functions +local mt_log = minetest.log local mt_add_item = minetest.add_item local mt_get_item_group = minetest.get_item_group local mt_get_node = minetest.get_node @@ -22,21 +24,31 @@ local mt_get_node_level = minetest.get_node_level local mt_get_node_max_level = minetest.get_node_max_level local mt_get_node_or_nil = minetest.get_node_or_nil local mt_get_node_timer = minetest.get_node_timer -local mt_hash_node_position = minetest.hash_node_position -local mt_is_protected = minetest.is_protected -local mt_set_node = minetest.set_node local mt_get_meta = minetest.get_meta +local mt_hash_node_position = minetest.hash_node_position +local mt_pos_to_string = minetest.pos_to_string +local mt_is_protected = minetest.is_protected +local mt_record_protection_violation = minetest.record_protection_violation +local mt_set_node = minetest.set_node local mt_is_creative_enabled = minetest.is_creative_enabled local mt_sound_play = minetest.sound_play +local math_min = math.min +local math_max = math.max +local math_ceil = math.ceil local math_floor = math.floor local math_random = math.random local string_format = string.format +local table_copy = table.copy +local table_insert = table.insert -- DEBUG: functions local log = minetest.log local chatlog = minetest.chat_send_all +il_chatlog = chatlog +il_get_meta = mt_get_meta +il_hash_pos = mt_hash_node_position -------------------------------------------------------------------------------- -- Kelp API @@ -45,30 +57,51 @@ local chatlog = minetest.chat_send_all local kelp = {} mcl_ocean.kelp = kelp +-- Kelp minimum and maximum age. Once reached the maximum, kelp no longer grows. +kelp.MIN_AGE = 0 kelp.MAX_AGE = 25 -kelp.TIMER_INTERVAL = 0.2 + +-- Tick interval (in seconds) for updating kelp. +kelp.TICK = 0.2 + +-- Tick interval (in seconds) to store kelp meta. +kelp.META_TICK = 2 + +-- Max age queue length +kelp.MAX_AGE_QUEUE = 20 -- The average amount of growth for kelp in a day is 2.16 (https://youtu.be/5Bp4lAjAk3I) --- Normally, a day lasts 20 minutes, meaning this nodetimer is executed 24000 times. --- Calculate probability via 2.16/24000 and we get the probability 9/100'000 or 9.0e-5 --- NOTE: currently, we can't exactly use the same type of randomness MC does, --- because it has multiple complicated sets of PRNGs. --- kelp.RANDOM_NUMERATOR = 9 -kelp.RANDOM_NUMERATOR = 100 -kelp.RANDOM_DENOMINATOR = 100000 +-- Normally, a day lasts 20 minutes, meaning kelp.next_grow() is executed +-- 1200 / TICK times. Per tick probability = (216/100) / (1200/TICK) +-- NOTE: currently, we can't exactly use the same type of randomness MC does, because +-- it has multiple complicated sets of PRNGs. +-- NOTE: Small loss of precision, should be 10 to preserve it. +-- kelp.ROLL_GROWTH_PRECISION = 10 +-- kelp.ROLL_GROWTH_NUMERATOR = 216 * kelp.TICK * kelp.ROLL_GROWTH_PRECISION +-- kelp.ROLL_GROWTH_DENOMINATOR = 100 * 1200 * kelp.ROLL_GROWTH_PRECISION +kelp.ROLL_GROWTH_PRECISION = 1 +kelp.ROLL_GROWTH_NUMERATOR = 216 * kelp.TICK +kelp.ROLL_GROWTH_DENOMINATOR = 100 * 1200 +-- Sounds used to dig and place kelp. kelp.leaf_sounds = mcl_sounds.node_sound_leaves_defaults() -- TODO: is this really necessary -- Lock drops to avoid duplicate drops, set after dropping detached kelp. kelp.lock_drop = 0 --- Registrar of nodetimers, indexed by pos_hash. -kelp.registered_nodetimers = {} +-- Pool storing nodetimers +kelp.timers_pool = {} -- Pool storing age, indexed by pos_hash. kelp.age_pool = {} +-- Queue(List) of hashed positions to save their ages. +-- Invalid ones may still persist in this queue. +kelp.age_queue = {} +-- Stores only valid positions of each hashed postiions. +kelp.age_queue_pos = {} + -- is age in the growable range? function kelp.is_age_growable(age) @@ -78,9 +111,10 @@ end -- Is this water? -- Returns the liquidtype, if indeed water. -function kelp.is_submerged(node, def) +function kelp.is_submerged(node) if mt_get_item_group(node.name, "water") ~= 0 then - return def.liquidtype -- Expected only "source" and "flowing" from water liquids + -- Expected only "source" and "flowing" from water liquids + return mt_registered_nodes[node.name].liquidtype end return false end @@ -88,16 +122,19 @@ end -- Is the water downward flowing? -- (kelp can grow/be placed inside downward flowing water) -function kelp.is_downward_flowing(pos, node, def, is_above) +function kelp.is_downward_flowing(pos, node, pos_above, node_above, __is_above__) + -- Function params: (pos[, node]) or (node, pos_above) or (node, node_above) + local node = node or mt_get_node(pos) + local result = (math_floor(node.param2 / 8) % 2) == 1 - if not (result or is_above) then + if not (result or __is_above__) then -- 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 = mt_get_node({x=pos.x,y=pos.y+1,z=pos.z}) - local def_above = mt_registered_nodes[node_above.name] - result = kelp.is_submerged(node_above, def_above) - or kelp.is_downward_flowing(pos, node_above, def_above, true) + local pos_above = pos_above or {x=pos.x,y=pos.y+1,z=pos.z} + local node_above = node_above or mt_get_node(pos_above) + result = kelp.is_submerged(node_above) + or kelp.is_downward_flowing(nil, node_above, nil, nil, true) end return result end @@ -105,20 +142,23 @@ end -- Will node fall at that position? -- This only checks if a node would fall, meaning that node need not be at pos. -function kelp.is_falling(pos, node) +function kelp.is_falling(pos, node, is_falling, pos_bottom, node_bottom, def_bottom) + -- Optional params: is_falling, pos_bottom, node_bottom, def_bottom + -- NOTE: Modified from check_single_for_falling in builtin. - -- Please update as necessary. + -- Please update accordingly. local nodename = node.name - if mt_get_item_group(nodename, "falling_node") == 0 then + if is_falling == false or + is_falling == nil and mt_get_item_group(nodename, "falling_node") == 0 then return false end - local pos_bottom = {x = pos.x, y = pos.y - 1, z = pos.z} + local pos_bottom = pos_bottom or {x = pos.x, y = pos.y - 1, z = pos.z} -- get_node_or_nil: Only fall if node below is loaded - local node_bottom = mt_get_node_or_nil(pos_bottom) + local node_bottom = node_bottom or mt_get_node_or_nil(pos_bottom) local nodename_bottom = node_bottom.name - local def_bottom = node_bottom and mt_registered_nodes[nodename_bottom] + local def_bottom = def_bottom or node_bottom and mt_registered_nodes[nodename_bottom] if not def_bottom then return false end @@ -143,6 +183,20 @@ function kelp.is_falling(pos, node) end +-- Roll whether to grow kelp or not. +function kelp.roll_growth(numerator, denominator) + -- Optional params: numerator, denominator + return math_random(denominator or kelp.ROLL_GROWTH_DENOMINATOR) <= (numerator or kelp.ROLL_GROWTH_NUMERATOR) +end + + +-- Roll initial age for kelp. +function kelp.roll_init_age(min, max) + -- Optional params + return math_random(min or kelp.MIN_AGE, (max or kelp.MAX_AGE)-1) +end + + -- Converts param2 to kelp height. function kelp.get_height(param2) return math_floor(param2 / 16) @@ -150,28 +204,31 @@ end -- Obtain pos and node of the tip of kelp. -function kelp.get_tip(pos, node) - local size = math.ceil(node.param2 / 16) - local pos_tip = table.copy(pos) - pos_tip.y = pos_tip.y + size - return pos_tip, mt_get_node(pos_tip) +function kelp.get_tip(pos, param2) + -- Optional params: param2 + local height = kelp.get_height(param2 or mt_get_node(pos).param2) + local pos_tip = {x=pos.x, y=pos.y, z=pos.z} + pos_tip.y = pos_tip.y + height + 1 + return pos_tip, mt_get_node(pos_tip), height end -- Obtain position of the first kelp unsubmerged. -function kelp.find_unsubmerged(pos, node) +function kelp.find_unsubmerged(pos, node, height) + -- Optional params: node, height + local node = node or mt_get_node(pos) + local height = height or kelp.get_height(node.param2) - local x,y,z = pos.x, pos.y, pos.z - local height = kelp.get_height(node.param2) - local walk_pos = {x=x, z=z} + local walk_pos = {x=pos.x, z=pos.z} + local y = pos.y for i=1,height do walk_pos.y = y + i local walk_node = mt_get_node(walk_pos) - if mt_get_item_group(walk_node.name, "water") == 0 then - return walk_pos, walk_node + if not kelp.is_submerged(walk_node) then + return walk_pos, walk_node, height, i end end - return nil + return nil, nil, height, height end @@ -181,53 +238,145 @@ function kelp.next_param2(param2) end --- Grow next kelp. -function kelp.next_grow(pos, node, pos_tip, def_tip, downward_flowing) - -- Optional parameters - local pos_tip, def_tip = pos_tip, def_tip - local downward_flowing = downward_flowing - if pos_tip == nil and def_tip == nil then - local node_tip - pos_tip, node_tip = kelp.get_tip(pos, node) - def_tip = mt_registered_nodes[node_tip.name] - downward_flowing = kelp.is_submerged(pos_tip, node_tip) - and kelp.is_downward_flowing(pos_tip, node_tip, def_tip) +-- Stores age from kelp.age_queue* into their respective meta +function kelp.store_meta() + local count = 0 + for _ in pairs(kelp.age_queue_pos) do + count = count + 1 + end + -- chatlog(string_format("Storing age metadata: %d in queue", #kelp.age_queue)) + -- chatlog(string_format("Storing age metadata: %d valid in queue", count)) + for i=1,#kelp.age_queue do + local pos_hash = kelp.age_queue[i] + local pos = kelp.age_queue_pos[pos_hash] + -- queued hashes may no longer point to a valid pos, e.g. kelp is destroyed. + if pos then + mt_get_meta(pos):set_int("mcl_ocean:kelp_age", kelp.age_pool[pos_hash]) + end + end + kelp.age_queue = {} + kelp.age_queue_pos = {} +end + + +-- Store and queue a kelp's age to be saved into meta later. +function kelp.store_age(age, pos, pos_hash) + -- Watched params: pos + -- Optional params: pos_hash + local pos_hash = pos_hash or mt_hash_node_position(pos) + + kelp.age_pool[pos_hash] = age + if not kelp.age_queue_pos[pos_hash] then + table_insert(kelp.age_queue, pos_hash) + kelp.age_queue_pos[pos_hash] = pos + return true, pos_hash end + return false, pos_hash +end + + +-- Initialise a kelp's age. +function kelp.init_age(pos, age, pos_hash, meta) + -- Watched params: pos + -- Optional params: age, pos_hash, meta + local pos_hash = pos_hash or mt_hash_node_position(pos) + local meta = meta or mt_get_meta(pos) + + local age = age + if not meta:contains("mcl_ocean:kelp_age") then + -- NOTE: age param can only replace initial age roll. + age = age or kelp.roll_init_age() + kelp.store_age(age, pos, pos_hash) + else + age = meta:get_int("mcl_ocean:kelp_age") + if not kelp.age_pool[pos_hash] then + kelp.age_pool[pos_hash] = age + end + end + + return age, pos_hash, meta +end + + +-- Initialise kelp nodetimer. +function kelp.init_timer(pos, pos_hash) + -- Optional params: pos_hash + local pos_hash = pos_hash or mt_hash_node_position(pos) + + local timer = kelp.timers_pool[pos_hash] + if not timer then + timer = mt_get_node_timer(pos) + kelp.timers_pool[pos_hash] = timer + end + if not timer:is_started() then + timer:start(kelp.TICK) + end + + return pos_hash +end + + +-- Apply next kelp height. +function kelp.next_height(pos, node, pos_tip, node_tip, submerged, downward_flowing) + -- Modified params: node + -- Optional params: node, pos_tip, node_tip, submerged, downward_flowing + local node = node or mt_get_node(pos) + local pos_tip = pos_tip + local node_tip = node_tip or (pos_tip and mt_get_node(pos_tip)) + if not pos_tip then + pos_tip,node_tip = kelp.get_tip(pos) + end + local downward_flowing = downward_flowing or + (submerged or kelp.is_submerged(node_tip) + and kelp.is_downward_flowing(pos_tip, node_tip)) + -- Liquid source: Grow normally. - local node = table.copy(node) node.param2 = kelp.next_param2(node.param2) mt_set_node(pos, node) -- Flowing liquid: Grow 1 step, but also turn the tip node into a liquid source. if downward_flowing then - local alt_liq = def_tip.liquid_alternative_source + local alt_liq = mt_registered_nodes[node_tip.name].liquid_alternative_source if alt_liq then mt_set_node(pos_tip, {name=alt_liq}) end end + + return node, pos_tip, node_tip, submerged, downward_flowing end --- Naturally grow next kelp. -function kelp.natural_grow(pos, node, age, pos_hash, meta) - -- Must grow first, then get the new meta - kelp.next_grow(pos, node) - - -- Optional params - local meta = meta or mt_get_meta(pos) +-- Grow next kelp. +function kelp.next_grow(age, pos, node, pos_hash, pos_tip, node_tip, submerged, downward_flowing) + -- Watched params: pos + -- Modified params: node + -- Optional params: node, pos_hash, pos_tip, node_tip, submerged, downward_flowing + local node = node or mt_get_node(pos) local pos_hash = pos_hash or mt_hash_node_position(pos) + local pos_tip = pos_tip + local node_tip = node_tip or (pos_tip and mt_get_node(pos_tip)) + if not pos_tip then + pos_tip,node_tip = kelp.get_tip(pos) + end - local age = age + 1 - kelp.age_pool[pos_hash] = age - meta:set_int("mcl_ocean:kelp_age", age) + -- New kelp must also be submerged in water. + local downward_flowing = downward_flowing or kelp.is_downward_flowing(pos_tip, node_tip) + if not (submerged or kelp.is_submerged(node_tip)) then + return + end + kelp.next_height(pos, node, pos_tip, node_tip, submerged, downward_flowing) + + return kelp.store_age(age, pos, pos_hash), node, pos_hash, pos_tip, node_tip, submerged, downward_flowing end -- Drops the items for detached kelps. -function kelp.detach_drop(pos, height, pos_hash) - local pos_hash = pos_hash or mt_hash_node_position(pos) -- Optional params +function kelp.detach_drop(pos, param2, pos_hash) + -- Optional params: param2, pos_hash + local height = kelp.get_height(param2 or mt_get_node(pos).param2) + local pos_hash = pos_hash or mt_hash_node_position(pos) if kelp.lock_drop > 0 then minetest.log("error", @@ -236,9 +385,11 @@ function kelp.detach_drop(pos, height, pos_hash) return end - local x,y,z = pos.x,pos.y,pos.z + local y = pos.y + local walk_pos = {x=pos.x, z=pos.z} for i=1,height do - mt_add_item({x=x, y=y+i, z=z}, "mcl_ocean:kelp") + walk_pos.y = y+i + mt_add_item(walk_pos, "mcl_ocean:kelp") end -- Locks drop. @@ -251,7 +402,8 @@ end -- Synonymous to digging the kelp. -- NOTE: this is intended for whenever kelp truly becomes segmented plants -- instead of rooted to the floor. Don't try to remove dig_pos. -function kelp.detach_dig(dig_pos, pos, node, is_drop, pos_hash) +function kelp.detach_dig(dig_pos, pos, node, drop, pos_hash) + -- Optional params: drop, pos_hash local pos_hash = pos_hash or mt_hash_node_position(pos) local param2 = node.param2 @@ -260,17 +412,18 @@ function kelp.detach_dig(dig_pos, pos, node, is_drop, pos_hash) -- Digs the entire kelp: invoke after_dig_node to mt_set_node. if new_height <= 0 then - if is_drop then - kelp.detach_drop(dig_pos, kelp.get_height(param2), pos_hash) + if drop then + kelp.detach_drop(dig_pos, param2, pos_hash) end mt_set_node(pos, { name=mt_registered_nodes[node.name].node_dig_prediction, - param=node.param, param2=0 }) + param=node.param, + param2=0 }) -- Digs the kelp beginning at a height else - if is_drop then - kelp.detach_drop(dig_pos, kelp.get_height(param2) - new_height, pos_hash) + if drop then + kelp.detach_drop(dig_pos, param2 - new_height, pos_hash) end mt_set_node(pos, {name=node.name, param=node.param, param2=16*new_height}) end @@ -281,7 +434,6 @@ end -- Kelp callback functions -------------------------------------------------------------------------------- --- Set this to drop kelps when function kelp.surface_on_dig(pos, node, digger) kelp.detach_dig(pos, pos, node, true) end @@ -292,34 +444,47 @@ function kelp.surface_after_dig_node(pos, node) end -function kelp.surface_on_timer(pos, elapsed) +function kelp.surface_on_timer(pos) local node = mt_get_node(pos) - local dig_pos = kelp.find_unsubmerged(pos, node) local pos_hash = mt_hash_node_position(pos) + + -- Update detahed kelps + local dig_pos = kelp.find_unsubmerged(pos, node) if dig_pos then + -- chatlog("detach_dig") kelp.detach_dig(dig_pos, pos, node, true, pos_hash) end -- Grow kelp on chance - if math_random(kelp.RANDOM_DENOMINATOR) - kelp.RANDOM_NUMERATOR < 0 then + if kelp.roll_growth() then local age = kelp.age_pool[pos_hash] if kelp.is_age_growable(age) then - kelp.natural_grow(pos, node, age, pos_hash) + kelp.next_grow(age, pos, node, pos_hash) end end return true end +function kelp.surface_on_construct(pos) + local pos_hash = mt_hash_node_position(pos) + kelp.init_age(pos, nil, pos_hash) + kelp.init_timer(pos, pos_hash) +end + + function kelp.surface_on_destruct(pos) local node = mt_get_node(pos) local pos_hash = mt_hash_node_position(pos) -- on_falling callback. Activated by pistons for falling nodes too. if kelp.is_falling(pos, node) then - kelp.detach_drop(pos, kelp.get_height(node.param2), pos_hash) + kelp.detach_drop(pos, node.param2, pos_hash) end + -- Removes position from queue + kelp.age_queue_pos[pos_hash] = nil + -- Unlocks drops. if kelp.lock_drop > 0 then kelp.lock_drop = kelp.lock_drop - 1 @@ -327,13 +492,14 @@ function kelp.surface_on_destruct(pos) end + function kelp.surface_on_mvps_move(pos, node, oldpos, nodemeta) -- Pistons moving falling nodes will have already activated on_falling callback. kelp.detach_dig(pos, pos, node, mt_get_item_group(node.name, "falling_node") ~= 1) end --- NOTE: Uncomment this to use ABMs +-- NOTE: Uncomment this to use ABMs. -- local function surface_unsubmerged_abm(pos, node) -- local dig_pos = find_unsubmerged(pos, node) -- if dig_pos then @@ -343,52 +509,6 @@ end -- end --- NOTE: Uncomment this to use nodetimers -function kelp.surface_register_nodetimer(pos, node, pos_hash, meta, age) - -- Optional params - local pos_hash,meta,age = pos_hash,meta,age - local timer = kelp.registered_nodetimers[pos_hash] - - if not timer then - pos_hash = pos_hash or mt_hash_node_position(pos) - - timer = mt_get_node_timer(pos) - kelp.registered_nodetimers[pos_hash] = timer - - -- Pool age to avoid meta:get* operations - meta = meta or mt_get_meta(pos) - if not age then - if not meta:contains("mcl_ocean:kelp_age") then - age = math_random(0, kelp.MAX_AGE-1) - else - age = meta:get_int("mcl_ocean:kelp_age") - end - end - kelp.age_pool[pos_hash] = age - meta:set_int("mcl_ocean:kelp_age", age) - end - - if not timer:is_started() then - timer:start(kelp.TIMER_INTERVAL) - end - - return pos_hash, meta, age -end - - --- NOTE: Uncomment this to use ABMs --- function kelp.grow_abm(pos, node) --- -- Grow kelp by 1 node length if it would grow inside water --- node.param2 = next_param2(node.param2) --- local pos_tip, node_tip = get_tip(pos, node) --- local def_tip = mt_registered_nodes[node_tip.name] --- if is_submerged(node_tip, def_tip) then --- kelp.next_grow(pos, node, pos_tip, def_tip, --- is_downward_flowing(pos_tip, node_tip, def_tip)) --- end --- end - - function kelp.kelp_on_place(itemstack, placer, pointed_thing) if pointed_thing.type ~= "node" or not placer then return itemstack @@ -410,17 +530,18 @@ function kelp.kelp_on_place(itemstack, placer, pointed_thing) -- Protection if mt_is_protected(pos_under, player_name) or mt_is_protected(pos_above, player_name) then - minetest.log("action", player_name + mt_log("action", player_name .. " tried to place " .. itemstack:get_name() .. " at protected position " - .. minetest.pos_to_string(pos_under)) - minetest.record_protection_violation(pos_under, player_name) + .. mt_pos_to_string(pos_under)) + mt_record_protection_violation(pos_under, player_name) return itemstack end local pos_tip, node_tip, def_tip - local pos_hash, meta + local pos_hash = mt_hash_node_position(pos_under) + local age = kelp.roll_init_age() -- Kelp must also be placed on the top/tip side of the surface/kelp @@ -430,12 +551,9 @@ function kelp.kelp_on_place(itemstack, placer, pointed_thing) -- When placed on kelp. if mt_get_item_group(nu_name, "kelp") == 1 then - pos_tip, node_tip = kelp.get_tip(pos_under, node_under) + pos_tip,node_tip = kelp.get_tip(pos_under, node_under.param2) def_tip = mt_registered_nodes[node_tip.name] - pos_hash = mt_hash_node_position(pos_under) - meta = mt_get_meta(pos_under) - -- When placed on surface. else local new_kelp = false @@ -452,18 +570,15 @@ function kelp.kelp_on_place(itemstack, placer, pointed_thing) return itemstack end - -- NOTE: Uncomment this to use nodetimers - -- Register nodetimer - pos_hash, meta = kelp.surface_register_nodetimer(pos_under) - pos_tip = pos_above node_tip = mt_get_node(pos_above) def_tip = mt_registered_nodes[node_tip.name] end -- New kelp must also be submerged in water. - local downward_flowing = kelp.is_downward_flowing(pos_tip, node_tip, def_tip) - if not (kelp.is_submerged(node_tip, def_tip) or downward_flowing) then + local downward_flowing = kelp.is_downward_flowing(pos_tip, node_tip) + local submerged = kelp.is_submerged(node_tip) + if not submerged then return itemstack end @@ -472,19 +587,46 @@ function kelp.kelp_on_place(itemstack, placer, pointed_thing) if def_node.sounds then mt_sound_play(def_node.sounds.place, { gain = 0.5, pos = pos_under }, true) end - kelp.next_grow(pos_under, node_under, pos_tip, def_tip, downward_flowing) + kelp.next_height(pos_under, node_under, pos_tip, node_tip, def_tip, submerged, downward_flowing) if not mt_is_creative_enabled(player_name) then itemstack:take_item() end - -- Reroll age - local age = math_random(0, kelp.MAX_AGE-1) - meta:set_int("mcl_ocean:kelp_age", age) - kelp.age_pool[pos_hash] = age - return itemstack end + +function kelp.lbm_register_nodetimer(pos, node) + local pos_hash = mt_hash_node_position(pos) + kelp.init_age(pos, nil, pos_hash) + kelp.init_timer(pos, pos_hash) +end + + +local gstep_time = 0 +function kelp.globalstep(dtime) + if #kelp.age_queue > kelp.MAX_AGE_QUEUE then + kelp.store_meta() + end + + gstep_time = gstep_time + dtime + if gstep_time < kelp.META_TICK then + return + end + gstep_time = 0 + + if #kelp.age_queue > 0 then + kelp.store_meta() + end +end + + +function kelp.on_shutdown() + if #kelp.age_queue > 0 then + kelp.store_meta() + end +end + -------------------------------------------------------------------------------- -- Kelp registration API -------------------------------------------------------------------------------- @@ -526,9 +668,10 @@ kelp.surface_deftemplate = { groups = { dig_immediate = 3, deco_block = 1, plant = 1, kelp = 1, }, --sounds = sounds, --node_dig_prediction = nodename, - after_dig_node = kelp.surface_after_dig_node, + on_construct = kelp.surface_on_construct, on_destruct = kelp.surface_on_destruct, on_dig = kelp.surface_on_dig, + after_dig_node = kelp.surface_after_dig_node, on_timer = kelp.surface_on_timer, mesecon = { on_mvps_move = kelp.surface_on_mvps_move, }, drop = "", -- drops are handled in on_dig @@ -573,7 +716,7 @@ function kelp.register_kelp_surface(surface, surface_deftemplate, surface_docs) doc.add_entry_alias("nodes", surface_docs.entry_id_orig, "nodes", surfacename) end - local sounds = table.copy(def.sounds) + local sounds = table_copy(def.sounds) sounds.dig = kelp.leaf_sounds.dig sounds.dug = kelp.leaf_sounds.dug sounds.place = kelp.leaf_sounds.place @@ -592,9 +735,9 @@ end -- Kelp surfaces nodes --------------------------------------------------------- -- Dirt must be registered first, for the docs -kelp.register_kelp_surface(kelp.surfaces[1], table.copy(kelp.surface_deftemplate), kelp.surface_docs) +kelp.register_kelp_surface(kelp.surfaces[1], table_copy(kelp.surface_deftemplate), kelp.surface_docs) for i=2, #kelp.surfaces do - kelp.register_kelp_surface(kelp.surfaces[i], table.copy(kelp.surface_deftemplate), kelp.surface_docs) + kelp.register_kelp_surface(kelp.surfaces[i], table_copy(kelp.surface_deftemplate), kelp.surface_docs) end -- Kelp item ------------------------------------------------------------------- @@ -676,15 +819,18 @@ minetest.register_craft({ -- ABMs ------------------------------------------------------------------------ --- NOTE: Uncomment this to use nodetimers minetest.register_lbm({ - label = "Kelp timer registration", - name = "mcl_ocean:kelp_timer_registration", + label = "Kelp initialise", + name = "mcl_ocean:kelp_init", nodenames = { "group:kelp" }, - run_at_every_load = true, -- so old kelps are also registered - action = kelp.surface_register_nodetimer, + run_at_every_load = true, -- so old kelps are also initialised + action = kelp.lbm_register_nodetimer, }) + +minetest.register_globalstep(kelp.globalstep) +minetest.register_on_shutdown(kelp.on_shutdown) + -- NOTE: Uncomment this to use ABMs -- minetest.register_abm({ -- label = "Kelp drops", From 089d6aa5c88a2d5f559c7aef01d1724f09f4a227 Mon Sep 17 00:00:00 2001 From: iliekprogrammar Date: Wed, 31 Mar 2021 13:55:07 +0800 Subject: [PATCH 08/12] Add dug sounds to kelp when unsubmerged --- mods/ITEMS/mcl_ocean/kelp.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/mods/ITEMS/mcl_ocean/kelp.lua b/mods/ITEMS/mcl_ocean/kelp.lua index 7a39bc850..291a69520 100644 --- a/mods/ITEMS/mcl_ocean/kelp.lua +++ b/mods/ITEMS/mcl_ocean/kelp.lua @@ -452,6 +452,7 @@ function kelp.surface_on_timer(pos) local dig_pos = kelp.find_unsubmerged(pos, node) if dig_pos then -- chatlog("detach_dig") + mt_sound_play(mt_registered_nodes[node.name].sounds.dug, { gain = 0.5, pos = dig_pos }, true) kelp.detach_dig(dig_pos, pos, node, true, pos_hash) end From 08e280d9b48574b5126f0ba5220d2e829b354824 Mon Sep 17 00:00:00 2001 From: iliekprogrammar Date: Wed, 31 Mar 2021 14:17:50 +0800 Subject: [PATCH 09/12] Comment out debug stuff --- mods/ITEMS/mcl_ocean/kelp.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mods/ITEMS/mcl_ocean/kelp.lua b/mods/ITEMS/mcl_ocean/kelp.lua index c33303977..1c7ced209 100644 --- a/mods/ITEMS/mcl_ocean/kelp.lua +++ b/mods/ITEMS/mcl_ocean/kelp.lua @@ -44,11 +44,11 @@ local table_copy = table.copy local table_insert = table.insert -- DEBUG: functions -local log = minetest.log -local chatlog = minetest.chat_send_all -il_chatlog = chatlog -il_get_meta = mt_get_meta -il_hash_pos = mt_hash_node_position +-- local log = minetest.log +-- local chatlog = minetest.chat_send_all +-- il_chatlog = chatlog +-- il_get_meta = mt_get_meta +-- il_hash_pos = mt_hash_node_position -------------------------------------------------------------------------------- -- Kelp API From ccea673dccfa19679c45e25a11b3960a9f9db0a1 Mon Sep 17 00:00:00 2001 From: iliekprogrammar Date: Wed, 31 Mar 2021 17:31:28 +0800 Subject: [PATCH 10/12] Fix updating age metadata. Remove kelp.lock_drop. --- mods/ITEMS/mcl_ocean/kelp.lua | 83 ++++++++++++----------------------- 1 file changed, 29 insertions(+), 54 deletions(-) diff --git a/mods/ITEMS/mcl_ocean/kelp.lua b/mods/ITEMS/mcl_ocean/kelp.lua index 1c7ced209..57c26358c 100644 --- a/mods/ITEMS/mcl_ocean/kelp.lua +++ b/mods/ITEMS/mcl_ocean/kelp.lua @@ -3,7 +3,6 @@ -- -- 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. --- TODO: Integrate ABMs with the new kelp code, disabled. OR remove them entirely local S = minetest.get_translator("mcl_ocean") local mod_doc = minetest.get_modpath("doc") ~= nil @@ -26,10 +25,11 @@ local mt_get_node_or_nil = minetest.get_node_or_nil local mt_get_node_timer = minetest.get_node_timer local mt_get_meta = minetest.get_meta local mt_hash_node_position = minetest.hash_node_position +local mt_set_node = minetest.set_node +local mt_swap_node = minetest.swap_node local mt_pos_to_string = minetest.pos_to_string local mt_is_protected = minetest.is_protected local mt_record_protection_violation = minetest.record_protection_violation -local mt_set_node = minetest.set_node local mt_is_creative_enabled = minetest.is_creative_enabled local mt_sound_play = minetest.sound_play @@ -46,9 +46,6 @@ local table_insert = table.insert -- DEBUG: functions -- local log = minetest.log -- local chatlog = minetest.chat_send_all --- il_chatlog = chatlog --- il_get_meta = mt_get_meta --- il_hash_pos = mt_hash_node_position -------------------------------------------------------------------------------- -- Kelp API @@ -86,10 +83,6 @@ kelp.ROLL_GROWTH_DENOMINATOR = 100 * 1200 -- Sounds used to dig and place kelp. kelp.leaf_sounds = mcl_sounds.node_sound_leaves_defaults() --- TODO: is this really necessary --- Lock drops to avoid duplicate drops, set after dropping detached kelp. -kelp.lock_drop = 0 - -- Pool storing nodetimers kelp.timers_pool = {} @@ -318,10 +311,11 @@ end -- Apply next kelp height. -function kelp.next_height(pos, node, pos_tip, node_tip, submerged, downward_flowing) +function kelp.next_height(pos, node, set_node, pos_tip, node_tip, submerged, downward_flowing) -- Modified params: node - -- Optional params: node, pos_tip, node_tip, submerged, downward_flowing + -- Optional params: node, set_node, pos_tip, node_tip, submerged, downward_flowing local node = node or mt_get_node(pos) + local set_node = set_node or mt_swap_node local pos_tip = pos_tip local node_tip = node_tip or (pos_tip and mt_get_node(pos_tip)) if not pos_tip then @@ -333,7 +327,7 @@ function kelp.next_height(pos, node, pos_tip, node_tip, submerged, downward_flow -- Liquid source: Grow normally. node.param2 = kelp.next_param2(node.param2) - mt_set_node(pos, node) + set_node(pos, node) -- Flowing liquid: Grow 1 step, but also turn the tip node into a liquid source. if downward_flowing then @@ -366,34 +360,22 @@ function kelp.next_grow(age, pos, node, pos_hash, pos_tip, node_tip, submerged, return end - kelp.next_height(pos, node, pos_tip, node_tip, submerged, downward_flowing) + kelp.next_height(pos, node, nil, pos_tip, node_tip, submerged, downward_flowing) return kelp.store_age(age, pos, pos_hash), node, pos_hash, pos_tip, node_tip, submerged, downward_flowing end -- Drops the items for detached kelps. -function kelp.detach_drop(pos, param2, pos_hash) - -- Optional params: param2, pos_hash +function kelp.detach_drop(pos, param2) + -- Optional params: param2 local height = kelp.get_height(param2 or mt_get_node(pos).param2) - local pos_hash = pos_hash or mt_hash_node_position(pos) - - if kelp.lock_drop > 0 then - minetest.log("error", - string_format("Duplicate drop prevented at (%d, %d, %d) with lock level %d! Please report this.", - pos.x, pos.y, pos.z, kelp.lock_drop)) - return - end - local y = pos.y local walk_pos = {x=pos.x, z=pos.z} for i=1,height do walk_pos.y = y+i mt_add_item(walk_pos, "mcl_ocean:kelp") end - - -- Locks drop. - kelp.lock_drop = kelp.lock_drop + 1 return true end @@ -402,30 +384,29 @@ end -- Synonymous to digging the kelp. -- NOTE: this is intended for whenever kelp truly becomes segmented plants -- instead of rooted to the floor. Don't try to remove dig_pos. -function kelp.detach_dig(dig_pos, pos, node, drop, pos_hash) - -- Optional params: drop, pos_hash - local pos_hash = pos_hash or mt_hash_node_position(pos) +function kelp.detach_dig(dig_pos, pos, node, drop) + -- Optional params: 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. + -- Digs the entire kelp. if new_height <= 0 then if drop then - kelp.detach_drop(dig_pos, param2, pos_hash) + kelp.detach_drop(dig_pos, param2) end mt_set_node(pos, { name=mt_registered_nodes[node.name].node_dig_prediction, param=node.param, param2=0 }) - -- Digs the kelp beginning at a height + -- Digs the kelp beginning at a height. else if drop then - kelp.detach_drop(dig_pos, param2 - new_height, pos_hash) + kelp.detach_drop(dig_pos, param2 - new_height) end - mt_set_node(pos, {name=node.name, param=node.param, param2=16*new_height}) + mt_swap_node(pos, {name=node.name, param=node.param, param2=16*new_height}) end end @@ -446,21 +427,23 @@ end function kelp.surface_on_timer(pos) local node = mt_get_node(pos) - local pos_hash = mt_hash_node_position(pos) + local pos_hash -- Update detahed kelps local dig_pos = kelp.find_unsubmerged(pos, node) if dig_pos then - -- chatlog("detach_dig") + pos_hash = mt_hash_node_position(pos) mt_sound_play(mt_registered_nodes[node.name].sounds.dug, { gain = 0.5, pos = dig_pos }, true) - kelp.detach_dig(dig_pos, pos, node, true, pos_hash) + kelp.detach_dig(dig_pos, pos, node, true) + kelp.store_age(kelp.roll_init_age(), pos, pos_hash) end -- Grow kelp on chance if kelp.roll_growth() then + pos_hash = pos_hash or mt_hash_node_position(pos) local age = kelp.age_pool[pos_hash] if kelp.is_age_growable(age) then - kelp.next_grow(age, pos, node, pos_hash) + kelp.next_grow(age+1, pos, node, pos_hash) end end @@ -480,16 +463,11 @@ function kelp.surface_on_destruct(pos) -- on_falling callback. Activated by pistons for falling nodes too. if kelp.is_falling(pos, node) then - kelp.detach_drop(pos, node.param2, pos_hash) + kelp.detach_drop(pos, node.param2) end -- Removes position from queue kelp.age_queue_pos[pos_hash] = nil - - -- Unlocks drops. - if kelp.lock_drop > 0 then - kelp.lock_drop = kelp.lock_drop - 1 - end end @@ -500,7 +478,7 @@ function kelp.surface_on_mvps_move(pos, node, oldpos, nodemeta) end --- NOTE: Uncomment this to use ABMs. +-- NOTE: Old ABM implementation. -- local function surface_unsubmerged_abm(pos, node) -- local dig_pos = find_unsubmerged(pos, node) -- if dig_pos then @@ -541,10 +519,6 @@ function kelp.kelp_on_place(itemstack, placer, pointed_thing) local pos_tip, node_tip, def_tip - local pos_hash = mt_hash_node_position(pos_under) - local age = kelp.roll_init_age() - - -- Kelp must also be placed on the top/tip side of the surface/kelp if pos_under.y >= pos_above.y then return itemstack @@ -583,12 +557,13 @@ function kelp.kelp_on_place(itemstack, placer, pointed_thing) return itemstack end - -- Play sound, place surface/kelp and take away an item + -- Play sound, place surface/kelp, store its new age and take away an item local def_node = mt_registered_items[nu_name] if def_node.sounds then mt_sound_play(def_node.sounds.place, { gain = 0.5, pos = pos_under }, true) end - kelp.next_height(pos_under, node_under, pos_tip, node_tip, def_tip, submerged, downward_flowing) + kelp.next_height(pos_under, node_under, nil, pos_tip, node_tip, def_tip, submerged, downward_flowing) + kelp.store_age(kelp.roll_init_age(), pos, mt_hash_node_position(pos_under)) if not mt_is_creative_enabled(player_name) then itemstack:take_item() end @@ -818,7 +793,7 @@ minetest.register_craft({ burntime = 200, }) --- ABMs ------------------------------------------------------------------------ +-- Global registration ------------------------------------------------------------------------ minetest.register_lbm({ label = "Kelp initialise", @@ -832,7 +807,7 @@ minetest.register_lbm({ minetest.register_globalstep(kelp.globalstep) minetest.register_on_shutdown(kelp.on_shutdown) --- NOTE: Uncomment this to use ABMs +-- NOTE: Old ABM implementation. -- minetest.register_abm({ -- label = "Kelp drops", -- nodenames = { "group:kelp" }, From bfff643ff42fddc680dbf4aeb3966080dcc34d00 Mon Sep 17 00:00:00 2001 From: iliekprogrammar Date: Wed, 31 Mar 2021 17:38:38 +0800 Subject: [PATCH 11/12] Allow age to override during initialization. --- mods/ITEMS/mcl_ocean/kelp.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mods/ITEMS/mcl_ocean/kelp.lua b/mods/ITEMS/mcl_ocean/kelp.lua index 57c26358c..979d0098a 100644 --- a/mods/ITEMS/mcl_ocean/kelp.lua +++ b/mods/ITEMS/mcl_ocean/kelp.lua @@ -277,9 +277,10 @@ function kelp.init_age(pos, age, pos_hash, meta) local meta = meta or mt_get_meta(pos) local age = age - if not meta:contains("mcl_ocean:kelp_age") then - -- NOTE: age param can only replace initial age roll. - age = age or kelp.roll_init_age() + if age then + kelp.store_age(age, pos, pos_hash) + elseif not meta:contains("mcl_ocean:kelp_age") then + age = kelp.roll_init_age() kelp.store_age(age, pos, pos_hash) else age = meta:get_int("mcl_ocean:kelp_age") From 9885f36c62ef7feb88149fa39c44383fa7810d49 Mon Sep 17 00:00:00 2001 From: iliekprogrammar Date: Wed, 31 Mar 2021 19:27:57 +0800 Subject: [PATCH 12/12] Fix missing age metadata and timer initialization in on_place. --- mods/ITEMS/mcl_ocean/kelp.lua | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/mods/ITEMS/mcl_ocean/kelp.lua b/mods/ITEMS/mcl_ocean/kelp.lua index 979d0098a..3aefe85f8 100644 --- a/mods/ITEMS/mcl_ocean/kelp.lua +++ b/mods/ITEMS/mcl_ocean/kelp.lua @@ -312,11 +312,10 @@ end -- Apply next kelp height. -function kelp.next_height(pos, node, set_node, pos_tip, node_tip, submerged, downward_flowing) +function kelp.next_height(pos, node, pos_tip, node_tip, submerged, downward_flowing) -- Modified params: node -- Optional params: node, set_node, pos_tip, node_tip, submerged, downward_flowing local node = node or mt_get_node(pos) - local set_node = set_node or mt_swap_node local pos_tip = pos_tip local node_tip = node_tip or (pos_tip and mt_get_node(pos_tip)) if not pos_tip then @@ -328,7 +327,7 @@ function kelp.next_height(pos, node, set_node, pos_tip, node_tip, submerged, dow -- Liquid source: Grow normally. node.param2 = kelp.next_param2(node.param2) - set_node(pos, node) + mt_swap_node(pos, node) -- Flowing liquid: Grow 1 step, but also turn the tip node into a liquid source. if downward_flowing then @@ -361,7 +360,7 @@ function kelp.next_grow(age, pos, node, pos_hash, pos_tip, node_tip, submerged, return end - kelp.next_height(pos, node, nil, pos_tip, node_tip, submerged, downward_flowing) + kelp.next_height(pos, node, pos_tip, node_tip, submerged, downward_flowing) return kelp.store_age(age, pos, pos_hash), node, pos_hash, pos_tip, node_tip, submerged, downward_flowing end @@ -519,7 +518,7 @@ function kelp.kelp_on_place(itemstack, placer, pointed_thing) end - local pos_tip, node_tip, def_tip + local pos_tip, node_tip, def_tip, new_kelp -- Kelp must also be placed on the top/tip side of the surface/kelp if pos_under.y >= pos_above.y then return itemstack @@ -532,7 +531,7 @@ function kelp.kelp_on_place(itemstack, placer, pointed_thing) -- When placed on surface. else - local new_kelp = false + new_kelp = false for _,surface in pairs(kelp.surfaces) do if nu_name == surface.nodename then node_under.name = "mcl_ocean:kelp_" ..surface.name @@ -558,17 +557,25 @@ function kelp.kelp_on_place(itemstack, placer, pointed_thing) return itemstack end - -- Play sound, place surface/kelp, store its new age and take away an item + -- Play sound, place surface/kelp and take away an item local def_node = mt_registered_items[nu_name] if def_node.sounds then mt_sound_play(def_node.sounds.place, { gain = 0.5, pos = pos_under }, true) end - kelp.next_height(pos_under, node_under, nil, pos_tip, node_tip, def_tip, submerged, downward_flowing) - kelp.store_age(kelp.roll_init_age(), pos, mt_hash_node_position(pos_under)) + kelp.next_height(pos_under, node_under, pos_tip, node_tip, def_tip, submerged, downward_flowing) if not mt_is_creative_enabled(player_name) then itemstack:take_item() end + -- Initialize age and timer when it's a new kelp + local pos_hash = mt_hash_node_position(pos_under) + if new_kelp then + kelp.init_age(pos_under, nil, pos_hash) + kelp.init_timer(pos_under, pos_hash) + else + kelp.store_age(kelp.roll_init_age(), pos_under, pos_hash) + end + return itemstack end