diff --git a/mods/ITEMS/REDSTONE/mesecons_mvps/init.lua b/mods/ITEMS/REDSTONE/mesecons_mvps/init.lua index 669edbcbba..8cd5ae872a 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/init.lua b/mods/ITEMS/mcl_ocean/init.lua index 2a103b8d05..f723a1f3ff 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 3c6e32422f..979d0098ae 100644 --- a/mods/ITEMS/mcl_ocean/kelp.lua +++ b/mods/ITEMS/mcl_ocean/kelp.lua @@ -1,71 +1,495 @@ +-- TODO: whenever it becomes possible to fully implement kelp without the +-- 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. + local S = minetest.get_translator("mcl_ocean") local mod_doc = minetest.get_modpath("doc") ~= nil --- 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-ify runtime functions +-------------------------------------------------------------------------------- +-- objects +local mt_registered_items = minetest.registered_items +local mt_registered_nodes = minetest.registered_nodes -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) +-- 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 +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_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_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 + +-------------------------------------------------------------------------------- +-- Kelp API +-------------------------------------------------------------------------------- + +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 + +-- 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 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() + +-- 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) + return age >= 0 and age < kelp.MAX_AGE 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 - return "source" - elseif def_water.liquidtype == "flowing" then - return "flowing" - end + +-- Is this water? +-- Returns the liquidtype, if indeed water. +function kelp.is_submerged(node) + if mt_get_item_group(node.name, "water") ~= 0 then + -- Expected only "source" and "flowing" from water liquids + return mt_registered_nodes[node.name].liquidtype end return false end -local function grow_param2_step(param2, snap_into_grid) - local old_param2 = param2 - param2 = param2 + 16 - if param2 > 240 then - param2 = 240 + +-- Is the water downward flowing? +-- (kelp can grow/be placed inside downward flowing water) +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, also check node above. + -- (this is needed due a weird quirk in the definition of "downwards flowing" + -- liquids in Minetest) + 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 - if snap_into_grid and (param2 % 16 ~= 0) then - param2 = param2 - (param2 % 16) - end - return param2, param2 ~= old_param2 + return result end -local function kelp_check_place(pos_above, node_above, def_above) - if minetest.get_item_group(node_above.name, "water") == 0 then + +-- 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, 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 accordingly. + local nodename = node.name + + if is_falling == false or + is_falling == nil and mt_get_item_group(nodename, "falling_node") == 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 + + 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 = node_bottom or mt_get_node_or_nil(pos_bottom) + local nodename_bottom = node_bottom.name + local def_bottom = def_bottom or node_bottom and mt_registered_nodes[nodename_bottom] + if not def_bottom then + return false end - return can_place + + 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 -local function kelp_on_place(itemstack, placer, pointed_thing) + +-- 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) +end + + +-- Obtain pos and node of the tip of kelp. +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, height) + -- Optional params: node, height + local node = node or mt_get_node(pos) + local height = height or kelp.get_height(node.param2) + + 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 not kelp.is_submerged(walk_node) then + return walk_pos, walk_node, height, i + end + end + return nil, nil, height, height +end + + +-- Obtain next param2. +function kelp.next_param2(param2) + return param2+16 - param2 % 16 +end + + +-- 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 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") + 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, set_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 + 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. + node.param2 = kelp.next_param2(node.param2) + 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 = 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 + + +-- 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 + + -- 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, 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) + -- Optional params: param2 + local height = kelp.get_height(param2 or mt_get_node(pos).param2) + 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 + return true +end + + +-- 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. +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. + if new_height <= 0 then + if drop then + 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. + else + if drop then + kelp.detach_drop(dig_pos, param2 - new_height) + end + mt_swap_node(pos, {name=node.name, param=node.param, param2=16*new_height}) + end +end + + +-------------------------------------------------------------------------------- +-- Kelp callback functions +-------------------------------------------------------------------------------- + +function kelp.surface_on_dig(pos, node, digger) + kelp.detach_dig(pos, pos, node, true) +end + + +function kelp.surface_after_dig_node(pos, node) + return mt_set_node(pos, {name=registred_nodes[node.name].node_dig_prediction}) +end + + +function kelp.surface_on_timer(pos) + local node = mt_get_node(pos) + local pos_hash + + -- Update detahed kelps + local dig_pos = kelp.find_unsubmerged(pos, node) + if dig_pos then + 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) + 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+1, 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, node.param2) + end + + -- Removes position from queue + kelp.age_queue_pos[pos_hash] = nil +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: Old ABM implementation. +-- 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 + + +function kelp.kelp_on_place(itemstack, placer, pointed_thing) if pointed_thing.type ~= "node" or not placer then return itemstack end @@ -73,183 +497,243 @@ 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_above = minetest.get_node(pos_above) - local def_under = minetest.registered_nodes[node_under.name] - local def_above = minetest.registered_nodes[node_above.name] + local node_under = mt_get_node(pos_under) + local nu_name = node_under.name + local def_under = mt_registered_nodes[nu_name] + -- 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 - if minetest.is_protected(pos_under, player_name) or - minetest.is_protected(pos_above, player_name) then - minetest.log("action", player_name + -- Protection + if mt_is_protected(pos_under, player_name) or + mt_is_protected(pos_above, player_name) then + 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 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 - return itemstack - end - else + + local pos_tip, node_tip, def_tip + -- 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 - 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 + + -- 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.param2) + def_tip = mt_registered_nodes[node_tip.name] + + -- When placed on surface. else - -- New kelp placed ... - if pos_under.y >= pos_above.y then - -- Placed on side or below node, abort + 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 = 0 + new_kelp = true + break + end + end + -- Surface must support kelp + if not new_kelp then 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 + + pos_tip = pos_above + node_tip = mt_get_node(pos_above) + def_tip = mt_registered_nodes[node_tip.name] end - -- Place or grow kelp - local def_node = minetest.registered_items[node_under.name] + + -- New kelp must also be submerged in water. + 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 + + -- 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 - 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 - minetest.set_node(pos_under, node_under) - if not minetest.is_creative_enabled(player_name) then + 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 return itemstack end -local get_kelp_height = function(param2) - return math.floor(param2 / 16) + +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 +-------------------------------------------------------------------------------- + +-- List of supported surfaces for seagrass and kelp. +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.registered_surfaces = {} + +-- Commented properties are the ones obtained using register_kelp_surface. +-- If you define your own properties, it overrides the default ones. +kelp.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, + 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 + --_mcl_falling_node_alternative = is_falling and nodename or nil, + _mcl_hardness = 0, + _mcl_blast_resistance = 0, +} + +-- Commented properties are the ones obtained using register_kelp_surface. +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", +} + +-- 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 = 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 doc_create = surface.doc_create or false + 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 + -- Sets the first surface as the docs' entry ID + 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 = 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" + 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.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) +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) +for i=2, #kelp.surfaces do + kelp.register_kelp_surface(kelp.surfaces[i], table_copy(kelp.surface_deftemplate), kelp.surface_docs) +end + +-- Kelp item ------------------------------------------------------------------- + minetest.register_craftitem("mcl_ocean:kelp", { description = S("Kelp"), _tt_help = S("Grows in water on dirt, sand, gravel"), _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 }, }) --- 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=surfaces[s][2]}) - end, - 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() - 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) - 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", kelp.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", { @@ -264,13 +748,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."), @@ -310,32 +794,35 @@ minetest.register_craft({ burntime = 200, }) --- Grow kelp -minetest.register_abm({ - label = "Kelp growth", +-- Global registration ------------------------------------------------------------------------ + +minetest.register_lbm({ + label = "Kelp initialise", + name = "mcl_ocean:kelp_init", nodenames = { "group:kelp" }, - interval = 45, - chance = 12, - 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 - if submerged == "source" then - -- Liquid source: Grow normally - minetest.set_node(pos, node) - elseif submerged == "flowing" 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 - if alt_liq then - minetest.set_node(top, {name=alt_liq}) - end - end - end - end, + 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: Old ABM implementation. +-- minetest.register_abm({ +-- label = "Kelp drops", +-- nodenames = { "group:kelp" }, +-- interval = 1.0, +-- chance = 1, +-- 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, +-- })