Repixture/mods/rp_default/functions.lua

1492 lines
51 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local water_level = tonumber(minetest.get_mapgen_setting("water_level"))
local S = minetest.get_translator("rp_default")
-- If a sapling fails to grow, check the sapling again after this many seconds
local SAPLING_RECHECK_TIME_MIN = 60
local SAPLING_RECHECK_TIME_MAX = 70
local AIRWEED_RECHARGE_TIME_DEFAULT = 10.0 -- how many seconds it takes for an airweed to become usable again by default
local GRAVITY = tonumber(minetest.settings:get("movement_gravity") or 9.81)
-- Maximum growth height of cactus on dry dirt, normally
local CACTUS_MAX_HEIGHT_DDIRT = 2
-- Maximum growth height of cactus on dry dirt, fertilized
local CACTUS_MAX_HEIGHT_DDIRT_PLUS = 3
-- Maximum growth height of cactus on sand, normally
local CACTUS_MAX_HEIGHT_SAND = 4
-- Maximum growth height of cactus on sand, fertilized
local CACTUS_MAX_HEIGHT_SAND_PLUS = 6
-- Maximum growth height of thistle, normally
local THISTLE_MAX_HEIGHT_NORMAL = 2
-- Maximum growth height of thistle, fertilized
local THISTLE_MAX_HEIGHT_PLUS = 3
-- Maximum growth height of papyrus, normally
local PAPYRUS_MAX_HEIGHT_NORMAL = 3
-- Maximum growth height of papyrus, fertilized
local PAPYRUS_MAX_HEIGHT_PLUS = 4
-- Bonus height for papyrus when growing on swamp dirt
local PAPYRUS_SWAMP_HEIGHT_BONUS = 1
-- Maximum possible cactus growth height
default.CACTUS_MAX_HEIGHT_TOTAL = CACTUS_MAX_HEIGHT_SAND_PLUS
-- Maximum possible thistle growth height
default.THISTLE_MAX_HEIGHT_TOTAL = THISTLE_MAX_HEIGHT_PLUS
-- Maximum possible papyrus growth height
default.PAPYRUS_MAX_HEIGHT_TOTAL = PAPYRUS_MAX_HEIGHT_PLUS + PAPYRUS_SWAMP_HEIGHT_BONUS
--
-- Functions/ABMs
--
-- Chest naming via signs
function default.write_name(pos, text)
-- TODO: Allow container naming later
--[[
-- Check above, if allowed
if minetest.settings:get_bool("signs_allow_name_above") then
local above = {x = pos.x, y = pos.y + 1, z = pos.z}
local abovedef = nil
if minetest.registered_nodes[minetest.get_node(above).name] then
abovedef = minetest.registered_nodes[minetest.get_node(above).name]
end
if abovedef and abovedef.write_name ~= nil then
abovedef.write_name(above, text)
end
end
-- Then below
local below = {x = pos.x, y = pos.y - 1, z = pos.z}
local belowdef = nil
if minetest.registered_nodes[minetest.get_node(below).name] then
belowdef = minetest.registered_nodes[minetest.get_node(below).name]
end
if belowdef and belowdef.write_name ~= nil then
belowdef.write_name(below, text)
end
]]
end
-- Saplings growing and placing
function default.place_sapling(itemstack, placer, pointed_thing)
-- Boilerplate to handle pointed node handlers
local handled, handled_itemstack = util.on_place_pointed_node_handler(itemstack, placer, pointed_thing)
if handled then
return handled_itemstack
end
local sapling_name = itemstack:get_name()
-- Find position to place sapling at
local place_in, place_floor = util.pointed_thing_to_place_pos(pointed_thing)
if place_in == nil then
rp_sounds.play_place_failed_sound(placer)
return itemstack
end
local floornode = minetest.get_node(place_floor)
-- Check protection
if minetest.is_protected(place_in, placer:get_player_name()) and
not minetest.check_player_privs(placer, "protection_bypass") then
minetest.record_protection_violation(pos, placer:get_player_name())
return itemstack
end
-- Floor must be soil
if minetest.get_item_group(floornode.name, "soil") == 0 then
rp_sounds.play_place_failed_sound(placer)
return itemstack
end
-- Place sapling
local newnode = {name = itemstack:get_name()}
minetest.set_node(place_in, newnode)
rp_sounds.play_node_sound(place_in, newnode, "place")
-- Reduce item count
if not minetest.is_creative_enabled(placer:get_player_name()) then
itemstack:take_item()
end
-- Update achievement
if placer and placer:is_player() then
achievements.trigger_subcondition(placer, "forester", sapling_name)
end
return itemstack
end
local sapling_data = {
["rp_default:sapling"] = {
grows_to = {
["dry"] = "normal_tiny",
["swamp"] = "apple_swamp",
["default"] = "apple",
},
grow_time_min = 300,
grow_time_max = 480,
},
["rp_default:sapling_oak"] = {
grows_to = {
["dry"] = "oak_tiny",
["swamp"] = "oak_swamp",
["normal"] = "oak_acorns",
["default"] = "oak",
},
grow_time_min = 700,
grow_time_max = 960,
},
["rp_default:sapling_birch"] = {
grows_to = {
["dry"] = "birch_tiny",
["swamp"] = "birch_swamp",
["default"] = "birch_cuboid",
},
grows_to = "birch",
grow_time_min = 480,
grow_time_max = 780,
},
["rp_default:sapling_dry_bush"] = {
grows_to = {
["dry"] = "dry_bush",
["swamp"] = "dry_bush_small",
["default"] = "dry_bush",
},
grows_to = "dry_bush",
grow_time_min = 180,
grow_time_max = 400
}
}
local tree_data = {
["apple"] = {
schem = "rp_default_apple_tree.mts",
offset = vector.new(-2, -1, -2),
space = {
{ vector.new(0,0,0), vector.new(0,2,0) },
{ vector.new(-2,3,-2), vector.new(2,5,2) },
},
},
["normal_tiny"] = {
schem = "rp_default_tiny_normal_tree.mts",
offset = vector.new(-1, -1, -1),
space = {
{ vector.new(0,0,0), vector.new(0,1,0) },
{ vector.new(-1,2,-1), vector.new(1,4,1) },
},
},
["apple_swamp"] = {
schem = "rp_default_swamp_apple_tree.mts",
offset = vector.new(-2, -1, -2),
space = {
{ vector.new(0,0,0), vector.new(0,2,0) },
{ vector.new(-2,3,-2), vector.new(2,4,2) },
},
},
["oak"] = {
schem = "rp_default_oak_tree.mts",
offset = vector.new(-2, -1, -2),
space = {
{ vector.new(0,0,0), vector.new(0,2,0) },
{ vector.new(-1,3,-1), vector.new(1,5,1) },
},
},
["oak_acorns"] = {
schem = "rp_default_oak_tree_acorns.mts",
offset = vector.new(-2, -1, -2),
space = {
{ vector.new(0,0,0), vector.new(0,2,0) },
{ vector.new(-1,3,-1), vector.new(1,5,1) },
},
},
["oak_swamp"] = {
schem = "rp_default_swamp_oak.mts",
offset = vector.new(-3, -1, -3),
space = {
{ vector.new(0,0,0), vector.new(0,2,0) },
{ vector.new(-2,3,-2), vector.new(2,5,2) },
},
},
["oak_tiny"] = {
schem = "rp_default_tiny_oak.mts",
offset = vector.new(-1, -1, -1),
space = {
{ vector.new(0,0,0), vector.new(0,1,0) },
{ vector.new(-1,2,-1), vector.new(1,4,1) },
},
},
["birch_cuboid"] = {
schem = "rp_default_birch_cuboid_3x3_short.mts",
offset = vector.new(-1, -1, -1),
space = {
{ vector.new(0,0,0), vector.new(0,1,0) },
{ vector.new(-1,2,-1), vector.new(1,4,1) },
},
},
["birch_swamp"] = {
schem = "rp_default_swamp_birch.mts",
offset = vector.new(-2, -1, -2),
space = {
{ vector.new(0,0,0), vector.new(0,3,0) },
{ vector.new(-1,4,-1), vector.new(1,6,1) },
},
},
["birch_tiny"] = {
schem = "rp_default_tiny_birch.mts",
offset = vector.new(-1, -1, -1),
space = {
{ vector.new(0,0,0), vector.new(0,1,0) },
{ vector.new(-1,2,-1), vector.new(1,4,1) },
},
},
["dry_bush"] = {
schem = "rp_default_dry_bush.mts",
offset = vector.new(-1, -1, -1),
space = {
{ vector.new(0,0,0), vector.new(0,1,0) },
},
},
["dry_bush_small"] = {
schem = "rp_default_dry_bush_small.mts",
offset = vector.new(-1, -1, -1),
space = {
{ vector.new(0,0,0), vector.new(0,1,0) },
},
},
}
function default.check_sapling_space(pos, variety)
local tdata = tree_data[variety]
if not tdata then
return false
end
local space = tdata.space
for i=1, #space do
local min, max = space[i][1], space[i][2]
min = vector.add(pos, min)
max = vector.add(pos, max)
-- Every node of the volume needs to be air,
-- so we calculate the volume first
local required_airs = (max.x - min.x + 1) * (max.y - min.y + 1) * (max.z - min.z + 1)
-- If pos is inside the volume, dont count it (cut its the sapling)
if pos.x >= min.x and pos.y >= min.y and pos.z >= min.z and pos.x <= max.x and pos.y <= max.y and pos.z <= max.z then
required_airs = required_airs - 1
end
local _, counts = minetest.find_nodes_in_area(min, max, {"air"}, false)
local counted_airs = counts.air
if counted_airs < required_airs then
return false
end
end
return true
end
-- Returns true if node at pos is a sapling and
-- the sapling growth timer is activated.
function default.is_sapling_growing(pos)
local node = minetest.get_node(pos)
if minetest.get_item_group(node.name, "sapling") == 0 then
return false
end
local timer = minetest.get_node_timer(pos)
return timer:is_started()
end
-- Start the sapling grow timer of the sapling at pos.
-- Returns true on success or false if it was not a sapling.
function default.begin_growing_sapling(pos)
local node = minetest.get_node(pos)
local sdata = sapling_data[node.name]
if not sdata then
return false
end
local min, max = sdata.grow_time_min, sdata.grow_time_max
local below = minetest.get_node(vector.add(pos, vector.new(0,-1,0)))
local fertilized = minetest.get_item_group(below.name, "plantable_fertilizer") > 0
-- Determine the full growth time of this sapling
local timeout = math.random(min, max)
-- Time bonus if node is fertilized
local timebonus = 0
if fertilized then
-- The time bonus is a fraction of the full groth time
-- If the soil is fertilized, this will start the node timer
-- with a few seconds counted as 'elapsed' already.
timebonus = timeout * default.SAPLING_FERTILIZER_TIME_BONUS_FACTOR
end
minetest.get_node_timer(pos):set(timeout, timebonus)
minetest.log("action", "[rp_default] Sapling timer of "..node.name.. " started at "..minetest.pos_to_string(pos,0).." with timeout="..timeout.."s, elapsed="..timebonus.." (fertilized="..tostring(fertilized)..")")
return true
end
-- Grow a sapling at pos
function default.grow_sapling(pos)
local function grow(variety)
local tdata = tree_data[variety]
if not tdata then
minetest.log("error", "[rp_default] Unknown sapling variety in default.grow_sapling!")
return
end
local opos = vector.add(pos, tdata.offset)
local replacements = tdata.replacements or {}
minetest.place_schematic(
opos,
minetest.get_modpath("rp_default") .. "/schematics/" .. tdata.schem,
"random", replacements, false)
end
local node = minetest.get_node(pos)
local sdata = sapling_data[node.name]
if not sdata then
return false
end
local grows_to = sdata.grows_to
local belownode = minetest.get_node(vector.add(pos, vector.new(0,-1,0)))
local dirttype
if minetest.get_item_group(belownode.name, "normal_dirt") == 1 then
dirttype = "normal"
elseif minetest.get_item_group(belownode.name, "swamp_dirt") == 1 then
dirttype = "swamp"
elseif minetest.get_item_group(belownode.name, "dry_dirt") == 1 then
dirttype = "dry"
end
local variety = grows_to[dirttype]
if not variety then
dirttype = "default"
variety = grows_to[dirttype]
end
local enough_space = default.check_sapling_space(pos, variety)
if not enough_space and dirttype ~= "default" then
variety = grows_to["default"]
enough_space = default.check_sapling_space(pos, variety)
end
if not enough_space then
minetest.get_node_timer(pos):start(math.random(SAPLING_RECHECK_TIME_MIN, SAPLING_RECHECK_TIME_MAX))
return false
end
minetest.remove_node(pos)
minetest.after(0, grow, variety)
minetest.log("action", "[rp_default] Sapling of type '" .. variety .. "' grows at " ..
minetest.pos_to_string(pos))
return true
end
-- Grows a plantlike_rooted plant that lives underwater by `add` node lengths
-- (default: 1).
-- Returns: <success>, <top_pos>
-- <success>: true if plant was grown, false otherwise
-- <top_pos>: position to which a new "plant segment" was added (nil if none)
function default.grow_underwater_leveled_plant(pos, node, add)
local def = minetest.registered_nodes[node.name]
if not def then
return false
end
if not add then
add = 1
end
local old_param2 = node.param2
local new_level = node.param2 + (16 * add)
if new_level % 16 > 0 then
new_level = new_level - new_level % 16
end
local max_level = def.leveled_max
if new_level > max_level then
new_level = max_level
end
if new_level < 0 then
new_level = 0
end
node.param2 = new_level
if node.param2 == old_param2 then
return false
end
local height = math.ceil(new_level / 16)
for i = 1, height do
local pos2 = vector.new(pos.x, pos.y + i, pos.z)
if not util.is_water_source_or_waterfall(pos2) then
return false
end
end
minetest.swap_node(pos, node)
local top = vector.new(pos.x, pos.y + height, pos.z)
return true, top
end
-- Starts the timer of an inert airweed at pos
-- (if not started already) so it will become
-- usable to get air bubbles soon.
-- Do not call this function on any other node type!
function default.start_inert_airweed_timer(pos)
local timer = minetest.get_node_timer(pos)
if timer:is_started() then
return
else
local node = minetest.get_node(pos)
local def = minetest.registered_nodes[node.name]
timer:start(def._airweed_recharge_time or AIRWEED_RECHARGE_TIME_DEFAULT)
end
end
-- Make preexisting sapling restart the growing process
minetest.register_lbm(
{
label = "Grow legacy trees",
name = "rp_default:grow_legacy_trees",
nodenames = {"rp_default:sapling", "rp_default:sapling_oak", "rp_default:sapling_birch"},
action = function(pos, node)
if not default.is_sapling_growing(pos) then
default.begin_growing_sapling(pos)
end
end
}
)
-- Make sure to restart the timer of inert airweeds
minetest.register_lbm(
{
label = "Restart inert airweed timers",
name = "rp_default:restart_inert_airweed_timers",
nodenames = {"group:airweed_inert"},
action = function(pos, node)
default.start_inert_airweed_timer(pos)
end
}
)
-- Update sign formspecs/infotexts
minetest.register_lbm(
{
label = "Update signs",
name = "rp_default:update_signs_3_7_0",
nodenames = {"group:sign"},
action = function(pos, node)
local meta = minetest.get_meta(pos)
default.refresh_sign(meta, node)
end
}
)
-- Force nodes to update infotext/formspec
minetest.register_lbm(
{
label = "Update bookshelves",
name = "rp_default:update_bookshelves_3_5_0",
nodenames = {"rp_default:bookshelf"},
action = function(pos, node)
local def = minetest.registered_nodes[node.name]
def.on_construct(pos)
end
}
)
minetest.register_lbm(
{
label = "Update chests",
name = "rp_default:update_chests_3_5_0",
nodenames = {"rp_default:chest"},
action = function(pos, node)
local def = minetest.registered_nodes[node.name]
def.on_construct(pos)
end
}
)
-- Vertical plants
-- Leaf decay
default.leafdecay_trunk_cache = {}
default.leafdecay_enable_cache = true
-- Spread the load of finding trunks
default.leafdecay_trunk_find_allow_accumulator = 0
minetest.register_globalstep(function(dtime)
local finds_per_second = 5000
default.leafdecay_trunk_find_allow_accumulator =
math.floor(dtime * finds_per_second)
end)
default.after_place_leaves = function(pos, placer, itemstack, pointed_thing)
local node = minetest.get_node(pos)
node.param2 = 1
minetest.set_node(pos, node)
end
minetest.register_abm( -- leaf decay
{
label = "Leaf decay",
nodenames = {"group:leafdecay"},
neighbors = {"air", "group:liquid"},
-- A low interval and a high inverse chance spreads the load
interval = 2,
chance = 3,
action = function(p0, node, _, _)
local do_preserve = false
local d = minetest.registered_nodes[node.name].groups.leafdecay
if not d or d == 0 then
return
end
local n0 = minetest.get_node(p0)
if n0.param2 ~= 0 then
return
end
local p0_hash = nil
if default.leafdecay_enable_cache then
p0_hash = minetest.hash_node_position(p0)
local trunkp = default.leafdecay_trunk_cache[p0_hash]
if trunkp then
local n = minetest.get_node(trunkp)
local reg = minetest.registered_nodes[n.name]
-- Assume ignore is a trunk, to make the thing work at the border of the active area
if n.name == "ignore" or (reg and reg.groups.tree and reg.groups.tree ~= 0) then
return
end
-- Cache is invalid
table.remove(default.leafdecay_trunk_cache, p0_hash)
end
end
if default.leafdecay_trunk_find_allow_accumulator <= 0 then
return
end
default.leafdecay_trunk_find_allow_accumulator =
default.leafdecay_trunk_find_allow_accumulator - 1
-- Assume ignore is a trunk, to make the thing work at the border of the active area
local p1 = minetest.find_node_near(p0, d, {"ignore", "group:tree"})
if p1 then
do_preserve = true
if default.leafdecay_enable_cache then
-- Cache the trunk
default.leafdecay_trunk_cache[p0_hash] = p1
end
end
if not do_preserve then
-- Drop stuff other than the node itself
local itemstacks = minetest.get_node_drops(n0.name)
local leafdecay_drop = minetest.get_item_group(n0.name, "leafdecay_drop") ~= 0
for _, itemname in ipairs(itemstacks) do
if leafdecay_drop or itemname ~= n0.name then
local p_drop = {
x = p0.x - 0.5 + math.random(),
y = p0.y - 0.5 + math.random(),
z = p0.z - 0.5 + math.random(),
}
minetest.add_item(p_drop, itemname)
end
end
-- Remove node
minetest.remove_node(p0)
-- Trigger fall
local above = {x=p0.x, y=p0.y+1, z=p0.z}
minetest.check_for_falling(above)
-- Particles
if not leafdecay_drop then
minetest.add_particlespawner({
amount = math.random(10, 20),
time = 0.1,
minpos = vector.add(p0, {x=-0.4, y=-0.4, z=-0.4}),
maxpos = vector.add(p0, {x=0.4, y=0.4, z=0.4}),
minvel = {x=-0.2, y=-0.2, z=-0.2},
maxvel = {x=0.2, y=0.1, z=0.2},
minacc = {x=0, y=-GRAVITY, z=0},
maxacc = {x=0, y=-GRAVITY, z=0},
minexptime = 0.1,
maxexptime = 0.5,
minsize = 0.5,
maxsize = 1.5,
collisiondetection = true,
vertical = false,
node = n0,
})
end
end
end
})
local biome_data = {}
-- Returns true if the given biome is considered to be
-- a 'dry' biome (e.g. for dry grass). Custom or unknown
-- biomes are never dry.
default.is_dry_biome = function(biomename)
if biome_data[biomename] then
return biome_data[biomename].is_dry
end
return false
end
-- Returns true if the given biome is considered to be
-- a swamp biome. Custom or unknown biomes are not
-- part of the swamp.
default.is_swamp_biome = function(biomename)
if biome_data[biomename] then
return biome_data[biomename].class == "swampy"
end
return false
end
-- List of biomes registered with default.set_biome_info
local core_biomes = {}
-- Same as above, but without special sub-biomes like beach and underwater variants
local main_biomes = {}
-- Returns a list of names with all biomes registered with
-- default.set_biome_info
default.get_core_biomes = function()
return core_biomes
end
-- Returns a list of names with all main layer biomes registered with
-- default.set_biome_info (no sub-biomes like underwater or beach)
default.get_main_biomes = function()
return main_biomes
end
-- Sets biome metadata for a built-in biome.
-- Must be called AFTER biome registration.
-- * biome_name: Name of the *main* biome (not Underwater or Beach variant!)
-- * biome_class: One of: savannic, drylandic, swampy, desertic, undergroundy
default.set_biome_info = function(biomename, biome_class)
local is_dry = false
local dirt_blob = "rp_default:dirt"
local sand_blob = "rp_default:sand"
local gravel_blob = "rp_default:gravel"
if biome_class == "savannic" then
is_dry = true
elseif biome_class == "drylandic" then
dirt_blob = "rp_default:dry_dirt"
is_dry = true
elseif biome_class == "desertic" then
dirt_blob = "rp_default:dry_dirt"
is_dry = true
elseif biome_class == "swampy" then
dirt_blob = "rp_default:swamp_dirt"
elseif biome_class == "undergroundy" then
dirt_blob = nil
end
local data = {
main_biome = biomename,
layer = "main",
class = biome_class,
is_dry = is_dry,
dirt_blob = dirt_blob,
sand_blob = sand_blob,
gravel_blob = gravel_blob,
}
biome_data[biomename] = data
table.insert(main_biomes, biomename)
table.insert(core_biomes, biomename)
local underwater = biomename .. " Underwater"
if minetest.registered_biomes[underwater] then
local odata = table.copy(data)
odata.layer = "underwater"
biome_data[underwater] = odata
table.insert(core_biomes, underwater)
end
local beach = biomename .. " Beach"
if minetest.registered_biomes[beach] then
local bdata = table.copy(data)
bdata.layer = "beach"
biome_data[beach] = bdata
table.insert(core_biomes, beach)
end
end
-- Returns metadata for a builtin biome. Returns a table with these fields:
-- * main_biome: Name of the main biome (useful if you have an underwater or beach biome variant)
-- * layer: "main" for the core biome, "underwater" and "beach" for the special Underwater and Beach variants
-- * class: Biome class that was assigned (see above)
-- * is_dry: True if biome is considered dry (e.g. for dry grass)
-- * dirt_blob: Name of dirt ore node or nil to suppress generation
-- * sand_blob: Name of sand ore node or nil to suppress generation
-- * gravel_blob: Name of gravel ore node or nil to suppress generation
--
-- Note: dirt_blob, sand_blob and gravel_blob are used to create ores after all builtin
-- biomes were created. These fields are useless for biomes from
-- external mods.
default.get_biome_info = function(biomename)
return biome_data[biomename]
end
minetest.register_abm( -- dirt and grass footsteps becomes dirt with grass if uncovered
{
label = "Grow grass on dirt",
nodenames = {"rp_default:dirt", "rp_default:dirt_with_grass_footsteps", "rp_default:swamp_dirt"},
interval = 2,
chance = 40,
action = function(pos, node)
local above = {x=pos.x, y=pos.y+1, z=pos.z}
local name = minetest.get_node(above).name
local partialblock = minetest.get_item_group(name, "path") ~= 0 or minetest.get_item_group(name, "slab") ~= 0 or minetest.get_item_group(name, "stair") ~= 0
local nodedef = minetest.registered_nodes[name]
if nodedef and (not partialblock) and (nodedef.sunlight_propagates or nodedef.paramtype == "light") and nodedef.liquidtype == "none" and nodedef.drawtype ~= "plantlike_rooted" and (minetest.get_node_light(above) or 0) >= 8 then
local biomedata = minetest.get_biome_data(pos)
local biomename = minetest.get_biome_name(biomedata.biome)
if node.name == "rp_default:swamp_dirt" then
if default.is_swamp_biome(biomename) then
minetest.set_node(pos, {name = "rp_default:dirt_with_swamp_grass"})
end
else
if default.is_dry_biome(biomename) then
minetest.set_node(pos, {name = "rp_default:dirt_with_dry_grass"})
else
minetest.set_node(pos, {name = "rp_default:dirt_with_grass"})
end
end
end
end
})
minetest.register_abm( -- dirt with grass becomes dirt if covered
{
label = "Remove grass on covered dirt",
nodenames = {"group:grass_cover"},
interval = 2,
chance = 10,
action = function(pos, node)
local above = {x=pos.x, y=pos.y+1, z=pos.z}
local name = minetest.get_node(above).name
local partialblock = minetest.get_item_group(name, "path") ~= 0 or minetest.get_item_group(name, "slab") ~= 0 or minetest.get_item_group(name, "stair") ~= 0
local nodedef = minetest.registered_nodes[name]
if name ~= "ignore" and nodedef and (partialblock or nodedef.paramtype ~= "light" or nodedef.liquidtype ~= "none" or nodedef.drawtype == "plantlike_rooted") then
if node.name == "rp_default:dirt_with_swamp_grass" then
minetest.set_node(pos, {name = "rp_default:swamp_dirt"})
else
minetest.set_node(pos, {name = "rp_default:dirt"})
end
end
end
})
minetest.register_abm( -- seagrass and airweed dies if not underwater
{
label = "Sea grass / airweed decay",
nodenames = {"group:seagrass", "group:airweed"},
interval = 10,
chance = 20,
action = function(pos, node)
local dir = vector.new(0,1,0)
local plantpos = vector.add(pos, dir)
local is_water = util.is_water_source_or_waterfall(plantpos)
if not is_water then
local def = minetest.registered_nodes[node.name]
if def._waterplant_base_node then
minetest.set_node(pos, {name = def._waterplant_base_node})
else
minetest.log("error", "[rp_default] Missing _waterplant_base_node for "..node.name)
minetest.remove_node(pos)
end
end
end
})
minetest.register_abm( -- algae die/become smaller if not fully underwater
-- also reset age to 0 (no growth) by implication
{
label = "Alga decay",
nodenames = {"group:alga"},
interval = 10,
chance = 20,
action = function(pos, node)
local height = math.ceil(node.param2 / 16)
local segmentpos = vector.new(pos.x,pos.y,pos.z)
local height_ok = 0
for h=1, height do
segmentpos.y = pos.y + h
local is_water = util.is_water_source_or_waterfall(segmentpos)
if not is_water then
break
end
height_ok = h
end
if height_ok == height then
return
end
if height_ok < 1 then
local def = minetest.registered_nodes[node.name]
if def and def._waterplant_base_node then
minetest.set_node(pos, {name = def._waterplant_base_node})
else
minetest.log("error", "[rp_default] Missing _waterplant_base_node for "..node.name)
minetest.remove_node(pos)
end
else
local param2 = height_ok * 16
minetest.set_node(pos, {name=node.name, param2=param2})
end
minetest.add_particlespawner({
amount = math.random(10*height, 20*height),
time = 0.1,
minpos = vector.add(pos, {x=-0.3, y=0.6, z=-0.3}),
maxpos = vector.add(pos, {x=0.3, y=0.4+height, z=0.3}),
minvel = {x=-0.2, y=-0.2, z=-0.2},
maxvel = {x=0.2, y=0.1, z=0.2},
minacc = {x=0, y=-GRAVITY, z=0},
maxacc = {x=0, y=-GRAVITY, z=0},
minexptime = 0.1,
maxexptime = 0.5,
minsize = 0.5,
maxsize = 0.75,
collisiondetection = true,
vertical = false,
node = {name="rp_default:alga_block"},
})
end,
})
-- Spread airweed that is 'filled' and on fertilized ground
minetest.register_abm({
label = "Airweed expansion",
nodenames = {"group:airweed_full"},
neighbors = {"group:water"},
interval = 300,
chance = 13,
action = function(pos, node)
if minetest.get_item_group(node.name, "plantable_fertilizer") == 0 then
return
end
local above = {x=pos.x, y=pos.y+1, z=pos.z}
local anode = minetest.get_node(above)
local adef = minetest.registered_nodes[anode.name]
if minetest.get_item_group(anode.name, "water") == 0 or not adef or adef.liquidtype ~= "source" then
return
end
-- Overcrowding: Stop spreading if too many in area
local offset = vector.new(1,1,1)
local pos0 = vector.subtract(pos, offset)
local pos1 = vector.add(pos, offset)
local same_plants = minetest.find_nodes_in_area(pos0, pos1, {"group:airweed"})
if #same_plants >= 3 then
return
end
-- Find a suitable ground
local grounds = minetest.find_nodes_in_area(pos0, pos1, {"rp_default:dirt", "rp_default:sand", "rp_default:gravel", "rp_default:swamp_dirt", "rp_default:dry_dirt", "rp_default:fertilized_dirt", "rp_default:fertilized_swamp_dirt", "rp_default:fertilized_dry_dirt", "rp_default:fertilized_sand"})
local candidates = {}
for g=1, #grounds do
local ground = grounds[g]
local gnode = minetest.get_node(ground)
local wnode = minetest.get_node({x=ground.x, y=ground.y+1, z=ground.z})
local wdef = minetest.registered_nodes[wnode.name]
if minetest.get_item_group(wnode.name, "water") ~= 0 and wdef and wdef.liquidtype == "source" then
local newnode
if gnode.name == "rp_default:dirt" then
newnode = "rp_default:airweed_inert_on_dirt"
elseif gnode.name == "rp_default:sand" then
newnode = "rp_default:airweed_inert_on_sand"
elseif gnode.name == "rp_default:gravel" then
newnode = "rp_default:airweed_inert_on_gravel"
elseif gnode.name == "rp_default:swamp_dirt" then
newnode = "rp_default:airweed_inert_on_swamp_dirt"
elseif gnode.name == "rp_default:dry_dirt" then
newnode = "rp_default:airweed_inert_on_dry_dirt"
elseif gnode.name == "rp_default:fertilized_sand" then
newnode = "rp_default:airweed_inert_on_fertilized_sand"
elseif gnode.name == "rp_default:fertilized_dirt" then
newnode = "rp_default:airweed_inert_on_fertilized_dirt"
elseif gnode.name == "rp_default:fertilized_swamp_dirt" then
newnode = "rp_default:airweed_inert_on_fertilized_swamp_dirt"
elseif gnode.name == "rp_default:fertilized_dry_dirt" then
newnode = "rp_default:airweed_inert_on_fertilized_dry_dirt"
end
if newnode then
table.insert(candidates, {pos=grounds[g], nodename=newnode})
end
end
end
if #candidates == 0 then
return
end
-- Place new airweed (inert)
local c = math.random(1, #candidates)
local choice = candidates[c]
minetest.set_node(choice.pos, {name=choice.nodename})
end,
})
minetest.register_abm({
label = "Flower/fern expansion",
nodenames = {"rp_default:flower", "rp_default:fern"},
neighbors = {"rp_default:dirt_with_grass", "rp_default:fertilized_dirt"},
interval = 13,
chance = 300,
action = function(pos, node)
-- Spread flowers and fern planted on fertilized dirt
-- to neighboring nodes.
-- Fern: Spreads minimally on dirt with grass. Spreads greatly on fertilized dirt fields
-- Flowers: Spreads a few on dirt with grass with high range. Spreads quickly when on fertilized dirt
-- to dirt with grass, but more concentrated with low range. Never spreads on fertilized dirt
-- on its own.
local upos = vector.add(pos, vector.new(0,-1,0))
local under = minetest.get_node(upos)
local fertilized = false
if under.name == "rp_default:fertilized_dirt" then
fertilized = true
end
if not fertilized and under.name ~= "rp_default:dirt_with_grass" then
return
end
local offset, maxplants, maxplants_few
if node.name == "rp_default:fern" then
maxplants = 2
offset = vector.new(3,2,3)
else
maxplants = 3
offset = vector.new(4,1,4)
end
maxplants_few = maxplants
-- Overcrowding: Stop spreading if too many in area
if fertilized then
-- Higher limit if fertilized
if node.name == "rp_default:fern" then
maxplants = 7
else
offset = vector.new(1,1,1)
maxplants = 6
end
end
local pos0 = vector.subtract(pos, offset)
local pos1 = vector.add(pos, offset)
local same_plants = minetest.find_nodes_in_area(pos0, pos1, {"rp_default:flower", "rp_default:fern"})
-- If on fertilized dirt, can to other fertilized dirt with
-- higher overcrowding limit. Spreading to dirt with grass
-- still needs to be below the low maxplants_few limit to be
local can_grow_to_unfertilized = true
-- Flowers can't spread TO fertilized dirt,
-- but they can spread to dirt with grass FROM fertilized dirt.
local can_grow_to_fertilized = node.name == "rp_default:fern"
if #same_plants >= maxplants_few then
if fertilized then
if node.name == "rp_default:fern" then
can_grow_to_unfertilized = false
end
if #same_plants >= maxplants then
return
end
else
return
end
end
local airs = minetest.find_nodes_in_area(pos0, pos1, "air")
local spread_candidates = {}
for a=1, #airs do
local ground = vector.add(airs[a], vector.new(0,-1,0))
local gnode = minetest.get_node(ground)
-- New flower/fern spawns on fertilized dirt or dirt with grass
if (can_grow_to_fertilized and gnode.name == "rp_default:fertilized_dirt") or (can_grow_to_unfertilized and gnode.name == "rp_default:dirt_with_grass") then
table.insert(spread_candidates, airs[a])
end
end
if #spread_candidates == 0 then
return
end
local s = math.random(1, #spread_candidates)
minetest.set_node(spread_candidates[s], node)
end,
})
minetest.register_abm({
label = "Grass clump growth",
nodenames = {"rp_default:grass"},
neighbors = {"group:plantable_fertilizer"},
interval = 20,
chance = 160,
action = function(pos, node)
local below = vector.add(pos, vector.new(0,-1,0))
local belownode = minetest.get_node(below)
local fert = belownode.name == "rp_default:fertilized_dirt"
if fert then
minetest.set_node(pos, {name="rp_default:tall_grass", param2=node.param2})
end
end,
})
minetest.register_abm({
label = "Sea grass clump growth",
nodenames = {"group:seagrass"},
neighbors = {"group:water"},
interval = 20,
chance = 160,
action = function(pos, node)
local fert = minetest.get_item_group(node.name, "plantable_fertilizer") > 0
local above = vector.add(pos, vector.new(0,1,0))
local abovenode = minetest.get_node(above)
local in_water = minetest.get_item_group(abovenode.name, "water") > 0
if in_water and fert then
if node.name == "rp_default:seagrass_on_fertilized_dirt" then
minetest.set_node(pos, {name="rp_default:tall_seagrass_on_fertilized_dirt", param2=node.param2})
elseif node.name == "rp_default:seagrass_on_fertilized_swamp_dirt" then
minetest.set_node(pos, {name="rp_default:tall_seagrass_on_fertilized_swamp_dirt", param2=node.param2})
elseif node.name == "rp_default:seagrass_on_fertilized_sand" then
minetest.set_node(pos, {name="rp_default:tall_seagrass_on_fertilized_sand", param2=node.param2})
end
end
end,
})
-- Grow vine
minetest.register_abm(
{
label = "Grow vines",
name = "rp_default:grow_vines",
nodenames = {"rp_default:vine"},
interval = 21,
chance = 120,
action = function(pos, node)
local meta = minetest.get_meta(pos)
local age = node.param2
if node.param2 == 0 or node.param2 >= default.VINE_MAX_AGE then
return
end
local below = {x=pos.x, y=pos.y-1, z=pos.z}
local nbelow = minetest.get_node(below)
if nbelow.name == "air" then
age = math.min(default.VINE_MAX_AGE, age + 1)
minetest.set_node(below, {name="rp_default:vine", param2 = age})
end
end,
}
)
-- Grow algae
minetest.register_abm(
{
label = "Grow algae",
name = "rp_default:grow_algae",
nodenames = {"group:alga"},
neighbors = {"group:water"},
interval = 20,
chance = 90,
action = function(pos, node)
local def = minetest.registered_nodes[node.name]
if not def or not def._waterplant_max_height then
return
end
local meta = minetest.get_meta(pos)
local age = meta:get_int("age")
if age == 0 then
return
end
local height = math.ceil(node.param2 / 16)
local new_height = height + 1
-- Stop growh at max height
if new_height > def._waterplant_max_height or age+1 > def._waterplant_max_height then
return
end
local grown = default.grow_underwater_leveled_plant(pos, node, 1)
if not grown then
-- Stop growth once blocked
meta:set_int("age", 0) -- age = 0 means no growth
return
else
-- Increase age by 1
age = math.min(age + 1, 255)
meta:set_int("age", age)
end
end,
}
)
minetest.register_abm({
label = "Grass clump expansion",
nodenames = {"group:grass"},
neighbors = {"group:grass_cover"},
interval = 20,
chance = 160,
action = function(pos, node)
pos.y = pos.y - 1
local under = minetest.get_node(pos)
pos.y = pos.y + 1
local required_under
if minetest.get_item_group(node.name, "normal_grass") ~= 0 then
required_under = "rp_default:dirt_with_grass"
elseif minetest.get_item_group(node.name, "dry_grass") ~= 0 then
required_under = "rp_default:dirt_with_dry_grass"
elseif minetest.get_item_group(node.name, "swamp_grass") ~= 0 then
required_under = "rp_default:dirt_with_swamp_grass"
else
return
end
if under.name ~= required_under then
return
end
-- Lower chance to spread dry grass
if node.name == "rp_default:dry_grass" and math.random(1,2) == 1 then
return
end
local pos0 = vector.subtract(pos, 4)
local pos1 = vector.add(pos, 4)
-- Testing shows that a threshold of 3 results in an appropriate maximum
-- density of approximately 7 nodes per 9x9 area.
if #minetest.find_nodes_in_area(pos0, pos1, {"group:grass", "rp_default:fern"}) > 3 then
return
end
local soils = minetest.find_nodes_in_area_under_air( pos0, pos1, "group:grass_cover")
local num_soils = #soils
if num_soils >= 1 then
for si = 1, math.min(3, num_soils) do
local soil = soils[math.random(num_soils)]
local soil_above = {x = soil.x, y = soil.y + 1, z = soil.z}
minetest.set_node(soil_above, {name = node.name})
end
end
end
})
minetest.register_abm({
label = "Sea grass clump expansion",
nodenames = {"group:seagrass"},
neighbors = {"group:water"},
interval = 20,
chance = 160,
action = function(pos, node)
local abovenode = minetest.get_node({x=pos.x, y=pos.y+1, z=pos.z})
local abovedef = minetest.registered_nodes[abovenode.name]
if minetest.get_item_group(abovenode.name, "water") == 0 or not abovedef or abovedef.liquidtype ~= "source" then
return
end
local pos0 = vector.subtract(pos, 4)
local pos1 = vector.add(pos, 4)
local soils = minetest.find_nodes_in_area(pos0, pos1, {"rp_default:dirt", "rp_default:swamp_dirt", "rp_default:sand"})
local fsoils = minetest.find_nodes_in_area(pos0, pos1, {"rp_default:fertilized_dirt", "rp_default:fertilized_swamp_dirt", "rp_default:fertilized_sand"})
local to_set = math.min(3, #soils + #fsoils)
local has_set = 0
local function replace(grounds, lhas_set, lto_set)
if lhas_set >= lto_set then
return
end
local num_nodes = #grounds
if num_nodes >= 1 then
while true do
local rnd = math.random(1, #grounds)
local ground = grounds[rnd]
local ground_above = {x = ground.x, y = ground.y + 1, z = ground.z}
local ground_above_node = minetest.get_node(ground_above)
local ground_above_def = minetest.registered_nodes[ground_above_node.name]
if minetest.get_item_group(ground_above_node.name, "water") ~= 0 and ground_above_def and ground_above_def.liquidtype == "source" then
local ground_node = minetest.get_node(ground)
local newnode
if ground_node.name == "rp_default:dirt" then
newnode = "rp_default:seagrass_on_dirt"
elseif ground_node.name == "rp_default:swamp_dirt" then
newnode = "rp_default:seagrass_on_swamp_dirt"
elseif ground_node.name == "rp_default:sand" then
newnode = "rp_default:seagrass_on_sand"
elseif ground_node.name == "rp_default:fertilized_dirt" then
newnode = "rp_default:seagrass_on_fertilized_dirt"
elseif ground_node.name == "rp_default:fertilized_swamp_dirt" then
newnode = "rp_default:seagrass_on_fertilized_swamp_dirt"
elseif ground_node.name == "rp_default:fertilized_sand" then
newnode = "rp_default:seagrass_on_fertilized_sand"
else
return
end
minetest.set_node(ground, {name=newnode})
lhas_set = lhas_set + 1
if lhas_set >= lto_set then
break
end
end
table.remove(grounds, rnd)
if #grounds == 0 then
break
end
end
end
end
-- Seagrass prefers to grow on fertilized soils first (no overcrowding limit)
replace(fsoils, has_set, to_set)
-- For remaining soils, check overcrowding. Stop growth if too much seagrass nearby
-- Testing shows that a threshold of 3 results in an appropriate maximum
-- density of approximately 7 nodes per 9x9 area.
if #minetest.find_nodes_in_area(pos0, pos1, {"group:seagrass"}) > 3 then
return
end
-- Grow on unfertilized soil after the overcrowding check was passed
replace(soils, has_set, to_set)
end
})
minetest.register_abm({
label = "Sand Grass clump expansion",
nodenames = {"group:sand_grass"},
neighbors = {"group:sand"},
interval = 21,
chance = 160,
action = function(pos, node)
pos.y = pos.y - 1
local under = minetest.get_node(pos)
pos.y = pos.y + 1
if minetest.get_item_group(under.name, "plantable_sandy") == 0 then
return
end
local pos0 = vector.add(pos, vector.new(-4, 0, -4))
local pos1 = vector.add(pos, vector.new(4, 1, 4))
-- Testing shows that a threshold of 3 results in an appropriate maximum
-- density of approximately 7 nodes per 9x9 area.
if #minetest.find_nodes_in_area(pos0, pos1, {"group:sand_grass"}) > 3 then
return
end
-- Sand grass can spread to sand on the same level
-- and on the level above, but it can't spread downwards
pos0 = vector.add(pos, vector.new(-4, -1, -4))
pos1 = vector.add(pos, vector.new(4, 0, 4))
local soils = minetest.find_nodes_in_area_under_air( pos0, pos1, "group:plantable_sandy")
local num_soils = #soils
if num_soils >= 1 then
for si = 1, math.min(3, num_soils) do
local soil = soils[math.random(num_soils)]
local soil_above = {x = soil.x, y = soil.y + 1, z = soil.z}
minetest.set_node(soil_above, {name = node.name})
end
end
end
})
-- Clams "washing up" at shallow sand and gravel beaches
minetest.register_abm({
label = "Growing clams",
nodenames = {"rp_default:sand", "rp_default:gravel"},
neighbors = {"rp_default:water_source"},
interval = 20,
chance = 160,
min_y = water_level,
max_y = water_level+2,
action = function(pos, node)
if pos.y < water_level or pos.y > water_level+2 then
return
end
-- Abort if there's a clam nearby
local pos0 = vector.add(pos, {x=-5, y=0, z=-5})
local pos1 = vector.add(pos, {x=5, y=2, z=5})
if #minetest.find_nodes_in_area(pos0, pos1, "group:clam") >= 1 then
return
end
-- Check the terrain around pos if it roughly resembles a shallow beach.
-- Done to prevent clam spawning in trivial cases like
-- 1 water source + 1 sand
-- Count water around pos 1 level below where the clam would be
pos0 = vector.add(pos, {x=-5, y=0, z=-5})
pos1 = vector.add(pos, {x=5, y=0, z=5})
local waternodes = #minetest.find_nodes_in_area(pos0, pos1, "rp_default:water_source")
-- Count sand and gravel around pos 2 levels below where the clam would be.
-- This is 1 level below the water.
pos0 = vector.add(pos, {x=-5, y=-1, z=-5})
pos1 = vector.add(pos, {x=5, y=-1, z=5})
-- Seagrass also counts as the node position is the solid sand-/dirt-like node, not the plant itself
local beachnodes = #minetest.find_nodes_in_area(pos0, pos1, {"rp_default:sand", "rp_default:gravel", "group:seagrass"})
-- Check if enough nodes were found. 30 is roughly 1/4 of an 11×11 area
if waternodes < 30 or beachnodes < 30 then
return
end
-- All checks passed! Clam spawning begins.
-- Check for places for 1 or multiple clams to spawn on.
pos0 = vector.add(pos, {x=-2, y=0, z=-2})
pos1 = vector.add(pos, {x=2, y=0, z=2})
local soils = minetest.find_nodes_in_area_under_air( pos0, pos1, {"rp_default:sand", "rp_default:gravel"})
local num_soils = #soils
if num_soils >= 1 then
for si = 1, math.min(3, num_soils) do
local soil = soils[math.random(num_soils)]
local soil_above = {x = soil.x, y = soil.y + 1, z = soil.z}
minetest.set_node(soil_above, {name = "rp_default:clam"})
end
end
end
})
minetest.register_abm( -- cactus grows
{
label = "Growing cacti",
nodenames = {"rp_default:cactus"},
neighbors = {"group:sand", "group:dry_dirt"},
interval = 20,
chance = 10,
action = function(pos, node)
pos.y = pos.y-1
local name = minetest.get_node(pos).name
local is_sand = minetest.get_item_group(name, "sand") ~= 0
local is_ddirt = minetest.get_item_group(name, "dry_dirt") ~= 0
if is_sand or is_ddirt then
local fertilized = minetest.get_item_group(name, "plantable_fertilizer") == 1
pos.y = pos.y+1
local height = 0
local maxh
-- Determine maximum height. Bonus height on fertilized node
if is_sand then
if fertilized then
maxh = CACTUS_MAX_HEIGHT_SAND_PLUS
else
maxh = CACTUS_MAX_HEIGHT_SAND
end
else
if fertilized then
maxh = CACTUS_MAX_HEIGHT_DDIRT_PLUS
else
maxh = CACTUS_MAX_HEIGHT_DDIRT
end
end
while minetest.get_node(pos).name == "rp_default:cactus" and height < maxh do
height = height+1
pos.y = pos.y+1
end
if height < maxh then
if minetest.get_node(pos).name == "air" then
minetest.set_node(pos, {name="rp_default:cactus"})
end
end
end
end,
})
minetest.register_abm( -- papyrus grows
{
label = "Growing papyrus",
nodenames = {"rp_default:papyrus"},
neighbors = {"group:plantable_sandy", "group:plantable_soil", "group:plantable_wet"},
interval = 20,
chance = 10,
action = function(pos, node)
-- Papyrus grows upwards, up to a limit.
-- The maximum height is on fertilized swamp dirt.
-- Check underground first
pos.y = pos.y-1
local name = minetest.get_node(pos).name
if minetest.get_item_group(name, "plantable_sandy") == 0 and minetest.get_item_group(name, "plantable_soil") == 0 and minetest.get_item_group(name, "plantable_wet") == 0 then
return 0
end
-- Needs water nearby
if minetest.find_node_near(pos, 3, {"group:water"}) == nil then
return
end
pos.y = pos.y+1
-- Determine growth height
local height = 0
-- Maximum height is higher on fertilized
local fertilized = minetest.get_item_group(name, "plantable_fertilizer") == 1
local maxh
if fertilized then
maxh = PAPYRUS_MAX_HEIGHT_PLUS
else
maxh = PAPYRUS_MAX_HEIGHT_NORMAL
end
-- Bonus max. height on swamp dirt
local is_swampy = minetest.get_item_group(name, "swamp_dirt") == 1
if is_swampy then
maxh = maxh + PAPYRUS_SWAMP_HEIGHT_BONUS
end
-- Find highest spot and grow
while minetest.get_node(pos).name == "rp_default:papyrus" and height < maxh do
height = height+1
pos.y = pos.y+1
end
if height < maxh then
if minetest.get_node(pos).name == "air" then
-- Set param2 to the height. This tells the game
-- this papyrus node was grown
local p2 = height + 1
minetest.set_node(pos, {name="rp_default:papyrus", param2=p2})
end
end
end,
})
minetest.register_abm( -- thistle grows (slowly)
{
label = "Growing thistle",
nodenames = {"rp_default:thistle"},
neighbors = {"group:normal_dirt"},
interval = 120,
chance = 20,
action = function(pos, node)
-- Thistle grows upwards, up to a limit.
-- Check ground first
pos.y = pos.y-1
local name = minetest.get_node(pos).name
if minetest.get_item_group(name, "normal_dirt") == 0 then
return
end
pos.y = pos.y+1
local height = 0
local fertilized = minetest.get_item_group(name, "plantable_fertilizer") == 1
local maxh
-- Maximum height is higher on fertilized
if fertilized then
maxh = THISTLE_MAX_HEIGHT_PLUS
else
maxh = THISTLE_MAX_HEIGHT_NORMAL
end
-- Get node above the highest node and grow, if possible
while minetest.get_node(pos).name == "rp_default:thistle" and height < maxh do
height = height+1
pos.y = pos.y+1
end
if height < maxh then
if minetest.get_node(pos).name == "air" then
minetest.set_node(pos, {name="rp_default:thistle"})
end
end
end,
})