1492 lines
51 KiB
Lua
1492 lines
51 KiB
Lua
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, don’t count it (cut it’s 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,
|
||
})
|
||
|
||
|
||
|