521 lines
21 KiB
Lua
Executable File
521 lines
21 KiB
Lua
Executable File
-- Original, asset-free content for The Last Hearth (Luanti 5.18).
|
|
-- Survival state is owned/persisted by the caller; numeric needs use 0..100.
|
|
local api = core
|
|
local MAX_REACH, MAX_FUEL = 6, 600
|
|
local FUEL = { ["hearth:sticks"] = 45, ["hearth:log"] = 180 }
|
|
local COOK_COST, BOIL_COST = 20, 10
|
|
local directions = {
|
|
{x = 1, y = 0, z = 0}, {x = -1, y = 0, z = 0},
|
|
{x = 0, y = 0, z = 1}, {x = 0, y = 0, z = -1},
|
|
}
|
|
|
|
local function texture(color)
|
|
return hearth.texture(color)
|
|
end
|
|
|
|
local function lowpart(percent, color)
|
|
-- Texture-name arguments must escape modifiers/colons, including [fill.
|
|
local overlay = texture(color):gsub("[\\^:]", function(c) return "\\" .. c end)
|
|
return "^[lowpart:" .. percent .. ":" .. overlay
|
|
end
|
|
|
|
local function near(player, pos)
|
|
if not player or not player:is_player() then return false end
|
|
local p = player:get_pos()
|
|
if not p then return false end
|
|
local dx, dy, dz = p.x - pos.x, p.y - pos.y, p.z - pos.z
|
|
return dx * dx + dy * dy + dz * dz <= MAX_REACH * MAX_REACH
|
|
end
|
|
|
|
local function allowed(pos, player)
|
|
if not near(player, pos) then return false end
|
|
local name = player:get_player_name()
|
|
if api.is_protected(pos, name) or not hearth.claim_allowed(pos, name) then
|
|
api.record_protection_violation(pos, name)
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function can_dig(pos, player)
|
|
return allowed(pos, player)
|
|
end
|
|
|
|
local function node_def(pos)
|
|
local node = api.get_node_or_nil(pos)
|
|
return node and api.registered_nodes[node.name], node
|
|
end
|
|
|
|
local function placement_pos(pointed)
|
|
if not pointed or pointed.type ~= "node" then return nil end
|
|
local under = node_def(pointed.under)
|
|
local above = node_def(pointed.above)
|
|
if not under or not above then return nil end
|
|
if under.buildable_to then return pointed.under end
|
|
if above.buildable_to then return pointed.above end
|
|
end
|
|
|
|
local function place(stack, player, pointed)
|
|
if not pointed or pointed.type ~= "node" then return stack end
|
|
local def = node_def(pointed.under)
|
|
-- item_place dispatches right-click callbacks before attempting placement.
|
|
if def and def.on_rightclick and player and not player:get_player_control().sneak then
|
|
if not allowed(pointed.under, player) then return stack end
|
|
return api.item_place(stack, player, pointed)
|
|
end
|
|
local pos = placement_pos(pointed)
|
|
if not pos or not allowed(pos, player) then return stack end
|
|
return api.item_place(stack, player, pointed)
|
|
end
|
|
|
|
local function is_building(name)
|
|
return name == "hearth:wall" or name == "hearth:roof"
|
|
end
|
|
|
|
local function solid(def)
|
|
return def and def.walkable ~= false and not def.buildable_to
|
|
and (not def.liquidtype or def.liquidtype == "none")
|
|
and not (def.groups and def.groups.leaves)
|
|
end
|
|
|
|
local function supported(pos)
|
|
-- Search existing construction only: at most two horizontal links and
|
|
-- sixteen blocks down. The horizontal budget never resets at a new block,
|
|
-- so sequential placement cannot extend a cantilever indefinitely.
|
|
local queue = {{pos = pos, span = 0, depth = 0}}
|
|
local seen, index = {}, 1
|
|
while index <= #queue do
|
|
local entry = queue[index]
|
|
index = index + 1
|
|
local p = entry.pos
|
|
local key = p.x .. ":" .. p.y .. ":" .. p.z .. ":" .. entry.span
|
|
if not seen[key] then
|
|
seen[key] = true
|
|
local below = {x = p.x, y = p.y - 1, z = p.z}
|
|
local def, node = node_def(below)
|
|
if solid(def) then
|
|
if not is_building(node.name) then return true end
|
|
if entry.depth < 15 then
|
|
queue[#queue + 1] = {pos = below, span = entry.span,
|
|
depth = entry.depth + 1}
|
|
end
|
|
end
|
|
if entry.span < 2 then
|
|
for _, d in ipairs(directions) do
|
|
local adjacent = {x = p.x + d.x, y = p.y, z = p.z + d.z}
|
|
local _, n = node_def(adjacent)
|
|
if n and is_building(n.name) then
|
|
queue[#queue + 1] = {pos = adjacent, span = entry.span + 1,
|
|
depth = entry.depth}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function place_building(stack, player, pointed)
|
|
if not pointed or pointed.type ~= "node" then return stack end
|
|
local under = node_def(pointed.under)
|
|
if under and under.on_rightclick and player and not player:get_player_control().sneak then
|
|
if not allowed(pointed.under, player) then return stack end
|
|
return api.item_place(stack, player, pointed)
|
|
end
|
|
local pos = placement_pos(pointed)
|
|
if not pos or not allowed(pos, player) then return stack end
|
|
if not supported(pos) then
|
|
hearth.notify(player, "Needs solid support within two horizontal blocks (maximum height 16).")
|
|
return stack
|
|
end
|
|
return api.item_place(stack, player, pointed)
|
|
end
|
|
|
|
local function owner(pos, player)
|
|
if player and player:is_player() then
|
|
api.get_meta(pos):set_string("owner", player:get_player_name())
|
|
end
|
|
end
|
|
|
|
local function register_node(name, def)
|
|
def.on_place = def.on_place or place
|
|
def.can_dig = can_dig
|
|
api.register_node("hearth:" .. name, def)
|
|
end
|
|
|
|
register_node("stone", {
|
|
description = "Weathered stone", tiles = {texture("#79766b") .. "^[contrast:12"},
|
|
groups = {cracky = 3, stone = 1}, drop = "hearth:loose_stone 3",
|
|
})
|
|
register_node("soil", {
|
|
description = "Earth", tiles = {texture("#68513c")},
|
|
groups = {crumbly = 3, soil = 1},
|
|
})
|
|
register_node("grass", {
|
|
description = "Meadow turf", tiles = {texture("#66764a"), texture("#68513c"),
|
|
texture("#68513c") .. lowpart(25, "#66764a")},
|
|
groups = {crumbly = 3, soil = 1}, drop = "hearth:soil",
|
|
})
|
|
register_node("sand", {
|
|
description = "River sand", tiles = {texture("#b7a47a")},
|
|
groups = {crumbly = 3, falling_node = 1, sand = 1},
|
|
})
|
|
|
|
for _, flowing in ipairs({false, true}) do
|
|
local name = flowing and "water_flowing" or "water_source"
|
|
register_node(name, {
|
|
description = flowing and "Flowing river water" or "River water (unsafe to drink)",
|
|
drawtype = flowing and "flowingliquid" or "liquid",
|
|
tiles = {texture("#527e82") .. "^[opacity:185"},
|
|
special_tiles = {
|
|
{name = texture("#527e82") .. "^[opacity:185", backface_culling = false},
|
|
{name = texture("#527e82") .. "^[opacity:185", backface_culling = true},
|
|
},
|
|
paramtype = "light", paramtype2 = flowing and "flowingliquid" or nil,
|
|
use_texture_alpha = "blend", walkable = false, pointable = false,
|
|
diggable = false, buildable_to = true, drowning = 1,
|
|
liquidtype = flowing and "flowing" or "source",
|
|
liquid_alternative_flowing = "hearth:water_flowing",
|
|
liquid_alternative_source = "hearth:water_source",
|
|
liquid_viscosity = 1, liquid_renewable = false, liquid_range = 4,
|
|
post_effect_color = {a = 100, r = 48, g = 83, b = 88},
|
|
groups = {water = 3, liquid = 3, not_in_creative_inventory = 1}, drop = "",
|
|
})
|
|
end
|
|
|
|
register_node("log", {
|
|
description = "Timber log (requires an axe)",
|
|
tiles = {texture("#a18a62"), texture("#a18a62"), texture("#564534")},
|
|
paramtype2 = "facedir", groups = {tree = 1, choppy = 2, flammable = 2},
|
|
})
|
|
register_node("leaves", {
|
|
description = "Canopy leaves", drawtype = "allfaces_optional",
|
|
tiles = {texture("#536744")}, paramtype = "light", sunlight_propagates = true,
|
|
groups = {snappy = 3, leaves = 1, flammable = 2}, drop = "hearth:sticks",
|
|
})
|
|
|
|
register_node("loose_stone", {
|
|
description = "Loose flint stone", drawtype = "nodebox",
|
|
tiles = {texture("#96938b")}, inventory_image = texture("#96938b"),
|
|
wield_image = texture("#96938b"), paramtype = "light", walkable = false,
|
|
node_box = {type = "fixed", fixed = {-0.25, -0.5, -0.2, 0.25, -0.32, 0.2}},
|
|
selection_box = {type = "fixed", fixed = {-0.3, -0.5, -0.3, 0.3, -0.2, 0.3}},
|
|
groups = {dig_immediate = 3, flint = 1},
|
|
})
|
|
register_node("sticks", {
|
|
description = "Dry sticks", drawtype = "nodebox", tiles = {texture("#8e7451")},
|
|
inventory_image = texture("#8e7451"), wield_image = texture("#8e7451"),
|
|
paramtype = "light", walkable = false,
|
|
node_box = {type = "fixed", fixed = {-0.4, -0.48, -0.06, 0.4, -0.4, 0.06}},
|
|
groups = {dig_immediate = 3, flammable = 2},
|
|
})
|
|
|
|
local function eat(amounts, replacement)
|
|
return function(stack, player)
|
|
if not player or not player:is_player() then return stack end
|
|
local state = hearth.players[player:get_player_name()]
|
|
if not state then return stack end
|
|
local needed = false
|
|
for key, value in pairs(amounts) do
|
|
if type(state[key]) == "number" and state[key] < 100 and value > 0 then
|
|
needed = true
|
|
end
|
|
end
|
|
if not needed then return stack end
|
|
for key, value in pairs(amounts) do
|
|
if type(state[key]) == "number" then
|
|
state[key] = math.max(0, math.min(100, state[key] + value))
|
|
end
|
|
end
|
|
if replacement then stack:replace(replacement) else stack:take_item() end
|
|
return stack
|
|
end
|
|
end
|
|
|
|
api.register_craftitem("hearth:berries", {
|
|
description = "Wild berries", inventory_image = texture("#995a65"),
|
|
on_use = eat({food = 12, water = 4}),
|
|
})
|
|
api.register_craftitem("hearth:raw_food", {
|
|
description = "Foraged food (cook at a hearth)", inventory_image = texture("#ad8263"),
|
|
})
|
|
api.register_craftitem("hearth:cooked_food", {
|
|
description = "Hearth-cooked forage", inventory_image = texture("#bf985b"),
|
|
on_use = eat({food = 35, stamina = 10}),
|
|
})
|
|
|
|
register_node("berry_bush", {
|
|
description = "Wild berry bush", drawtype = "allfaces_optional",
|
|
tiles = {texture("#63734b") .. lowpart(20, "#995a65")},
|
|
paramtype = "light", walkable = false, sunlight_propagates = true,
|
|
groups = {snappy = 3, flora = 1, flammable = 2}, drop = "hearth:berries 3",
|
|
})
|
|
register_node("reeds", {
|
|
description = "Thatching rushes", drawtype = "plantlike", tiles = {texture("#a89965")},
|
|
inventory_image = texture("#a89965"), wield_image = texture("#a89965"),
|
|
paramtype = "light", walkable = false, sunlight_propagates = true,
|
|
groups = {snappy = 3, flora = 1, flammable = 2},
|
|
})
|
|
register_node("planks", {
|
|
description = "Split timber planks", tiles = {texture("#a48a61")},
|
|
groups = {choppy = 3, wood = 1, flammable = 2},
|
|
})
|
|
register_node("wall", {
|
|
description = "Timber wall", tiles = {texture("#947853")},
|
|
groups = {choppy = 3, wood = 1, flammable = 2},
|
|
on_place = place_building, after_place_node = owner,
|
|
})
|
|
register_node("roof", {
|
|
description = "Thatch roof", tiles = {texture("#b4a16b")},
|
|
groups = {snappy = 3, flammable = 3},
|
|
on_place = place_building, after_place_node = owner,
|
|
})
|
|
|
|
-- No hand choppy/cracky capability: timber needs an axe, stone a pick.
|
|
api.register_item(":", {
|
|
type = "none", wield_image = texture("#c7aa83"), wield_scale = {x = 1, y = 1, z = 1},
|
|
tool_capabilities = {
|
|
full_punch_interval = 1, max_drop_level = 0,
|
|
groupcaps = {
|
|
crumbly = {times = {[3] = 1.4}, uses = 0, maxlevel = 1},
|
|
snappy = {times = {[3] = 0.6}, uses = 0, maxlevel = 1},
|
|
}, damage_groups = {fleshy = 1},
|
|
},
|
|
})
|
|
|
|
local function tool(name, description, color, groupcaps, damage, interval)
|
|
api.register_tool("hearth:" .. name, {
|
|
description = description, inventory_image = "hearth_icon_" .. name .. ".png",
|
|
tool_capabilities = {full_punch_interval = interval, max_drop_level = 1,
|
|
groupcaps = groupcaps, damage_groups = {fleshy = damage}},
|
|
})
|
|
end
|
|
tool("knife", "Flint knife", "#b0aaa0", {
|
|
snappy = {times = {[3] = 0.2, [2] = 0.5, [1] = 1}, uses = 45, maxlevel = 1},
|
|
}, 3, 0.7)
|
|
tool("axe", "Flint axe", "#89877e", {
|
|
choppy = {times = {[3] = 0.8, [2] = 1.8, [1] = 3}, uses = 40, maxlevel = 1},
|
|
}, 4, 1.1)
|
|
tool("pick", "Flint pick", "#8e9292", {
|
|
cracky = {times = {[3] = 2.4}, uses = 35, maxlevel = 1},
|
|
}, 3, 1.2)
|
|
tool("spear", "Flint spear", "#aa9a7a", {}, 5, 1.2)
|
|
|
|
local function fuel_left(pos, now)
|
|
local deadline = tonumber(api.get_meta(pos):get_string("fuel_until")) or 0
|
|
-- Corrupt/future metadata cannot produce an unbounded fuel reserve.
|
|
if deadline ~= deadline then return 0 end
|
|
return math.max(0, math.min(MAX_FUEL, deadline - now))
|
|
end
|
|
|
|
local function refresh_fire(pos, remaining, now)
|
|
local meta = api.get_meta(pos)
|
|
meta:set_string("fuel_until", tostring(remaining > 0 and now + remaining or 0))
|
|
meta:set_string("infotext", remaining > 0
|
|
and ("Last Hearth: " .. math.ceil(remaining) .. "s fuel; feed sticks/logs, cook forage or boil water")
|
|
or "Cold hearth: feed dry sticks or a log")
|
|
local node = api.get_node(pos)
|
|
local name = remaining > 0 and "hearth:fire_lit" or "hearth:fire"
|
|
if node.name ~= name then
|
|
api.swap_node(pos, {name = name, param2 = node.param2})
|
|
end
|
|
if remaining > 0 then
|
|
api.get_node_timer(pos):start(math.min(5, remaining))
|
|
else
|
|
api.get_node_timer(pos):stop()
|
|
end
|
|
end
|
|
|
|
local function fire_tick(pos)
|
|
local now = os.time()
|
|
refresh_fire(pos, fuel_left(pos, now), now)
|
|
return false -- refresh_fire schedules the next absolute-deadline check.
|
|
end
|
|
|
|
local function use_fire(pos, _, player, stack)
|
|
if not allowed(pos, player) or stack:is_empty() then return stack end
|
|
local now = os.time()
|
|
local remaining = fuel_left(pos, now)
|
|
local item = stack:get_name()
|
|
local burn = FUEL[item]
|
|
if burn then
|
|
if remaining + burn <= MAX_FUEL then
|
|
stack:take_item(1)
|
|
remaining = remaining + burn
|
|
else
|
|
hearth.notify(player, "The hearth has no room for that much fuel yet.")
|
|
end
|
|
else
|
|
local cost, result
|
|
if item == "hearth:raw_food" then
|
|
cost, result = COOK_COST, "hearth:cooked_food"
|
|
elseif item == "hearth:water_raw" then
|
|
cost, result = BOIL_COST, "hearth:water_clean"
|
|
end
|
|
if cost then
|
|
local inv = player:get_inventory()
|
|
-- A multi-item cooking stack needs room before either input or fuel
|
|
-- is touched. Single-item inputs are replaced directly in the hand.
|
|
if remaining < cost then
|
|
hearth.notify(player, "Add fuel before cooking or boiling water.")
|
|
elseif stack:get_count() == 1 then
|
|
stack:replace(result)
|
|
remaining = remaining - cost
|
|
elseif inv and inv:room_for_item("main", result) then
|
|
local leftover = inv:add_item("main", result)
|
|
if leftover:is_empty() then
|
|
stack:take_item(1)
|
|
remaining = remaining - cost
|
|
end
|
|
else
|
|
hearth.notify(player, "Make inventory space for the cooked food.")
|
|
end
|
|
end
|
|
end
|
|
refresh_fire(pos, remaining, now)
|
|
return stack
|
|
end
|
|
|
|
for _, lit in ipairs({false, true}) do
|
|
register_node(lit and "fire_lit" or "fire", {
|
|
description = "Stone hearth", drawtype = "nodebox", paramtype = "light",
|
|
tiles = {texture(lit and "#dd9952" or "#6e695e")},
|
|
node_box = {type = "fixed", fixed = {-0.5, -0.5, -0.5, 0.5, -0.15, 0.5}},
|
|
light_source = lit and 10 or 0, drop = "hearth:fire",
|
|
groups = {cracky = 3, dig_immediate = 2, not_in_creative_inventory = lit and 1 or nil},
|
|
on_construct = function(pos) refresh_fire(pos, 0, os.time()) end,
|
|
after_place_node = owner, on_rightclick = use_fire, on_timer = fire_tick,
|
|
-- There is deliberately no hearth inventory/formspec. Deny injected
|
|
-- inventory actions as well, including actions against legacy metadata.
|
|
allow_metadata_inventory_put = function() return 0 end,
|
|
allow_metadata_inventory_take = function() return 0 end,
|
|
allow_metadata_inventory_move = function() return 0 end,
|
|
})
|
|
end
|
|
api.register_lbm({
|
|
name = "hearth:expire_fire", label = "Reconcile real-time hearth fuel",
|
|
nodenames = {"hearth:fire", "hearth:fire_lit"}, run_at_every_load = true,
|
|
action = fire_tick,
|
|
})
|
|
|
|
api.register_craftitem("hearth:vessel", {
|
|
description = "Carved stone cup (place on water to fill)",
|
|
inventory_image = texture("#a19a88"), stack_max = 1, liquids_pointable = true,
|
|
on_place = function(stack, player, pointed)
|
|
if not pointed or pointed.type ~= "node" then return stack end
|
|
local pos = pointed.under
|
|
if not allowed(pos, player) then return stack end
|
|
local node = api.get_node(pos)
|
|
if node.name == "hearth:water_source" or node.name == "hearth:water_flowing" then
|
|
stack:replace("hearth:water_raw")
|
|
end
|
|
return stack
|
|
end,
|
|
})
|
|
api.register_craftitem("hearth:water_raw", {
|
|
description = "Stone cup of untreated water (boil at a hearth)",
|
|
inventory_image = texture("#527e82"), stack_max = 1,
|
|
-- Primary use/placement on a hearth also works without targeting its top.
|
|
on_use = function(stack, player, pointed)
|
|
if pointed and pointed.type == "node" then
|
|
local node = api.get_node(pointed.under)
|
|
if node.name == "hearth:fire" or node.name == "hearth:fire_lit" then
|
|
return use_fire(pointed.under, node, player, stack)
|
|
end
|
|
end
|
|
return stack
|
|
end,
|
|
on_place = function(stack, player, pointed)
|
|
if pointed and pointed.type == "node" then
|
|
local node = api.get_node(pointed.under)
|
|
if node.name == "hearth:fire" or node.name == "hearth:fire_lit" then
|
|
return use_fire(pointed.under, node, player, stack)
|
|
end
|
|
end
|
|
return stack
|
|
end,
|
|
})
|
|
api.register_craftitem("hearth:water_clean", {
|
|
description = "Stone cup of boiled water", inventory_image = texture("#82adb0"),
|
|
stack_max = 1, on_use = eat({water = 40}, "hearth:vessel"),
|
|
})
|
|
|
|
local recipes = {
|
|
{"hearth:knife", {{"hearth:loose_stone", "hearth:loose_stone"}, {"", "hearth:sticks"}}},
|
|
{"hearth:axe", {{"hearth:loose_stone", "hearth:loose_stone"}, {"hearth:loose_stone", "hearth:sticks"}, {"", "hearth:sticks"}}},
|
|
{"hearth:pick", {{"hearth:loose_stone", "hearth:loose_stone", "hearth:loose_stone"}, {"", "hearth:sticks", ""}, {"", "hearth:sticks", ""}}},
|
|
{"hearth:spear", {{"hearth:loose_stone"}, {"hearth:sticks"}, {"hearth:sticks"}}},
|
|
{"hearth:vessel", {{"hearth:loose_stone", "", "hearth:loose_stone"}, {"", "hearth:loose_stone", ""}}},
|
|
{"hearth:fire", {{"hearth:loose_stone", "hearth:loose_stone", "hearth:loose_stone"}, {"hearth:loose_stone", "", "hearth:loose_stone"}, {"hearth:loose_stone", "hearth:loose_stone", "hearth:loose_stone"}}},
|
|
{"hearth:wall 4", {{"hearth:planks", "hearth:planks"}, {"hearth:planks", "hearth:planks"}}},
|
|
{"hearth:roof 4", {{"hearth:reeds", "hearth:reeds", "hearth:reeds"}, {"hearth:sticks", "", "hearth:sticks"}}},
|
|
}
|
|
for _, recipe in ipairs(recipes) do
|
|
api.register_craft({output = recipe[1], recipe = recipe[2]})
|
|
end
|
|
api.register_craft({type = "shapeless", output = "hearth:planks 4", recipe = {"hearth:log"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:sticks 4", recipe = {"hearth:planks"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:raw_food", recipe = {"hearth:berries", "hearth:berries"}})
|
|
|
|
-- Do not clear other mods' registrations or change global protection/mapgen.
|
|
api.register_alias("mapgen_stone", "hearth:stone")
|
|
api.register_alias("mapgen_water_source", "hearth:water_source")
|
|
api.register_alias("mapgen_river_water_source", "hearth:water_source")
|
|
api.register_biome({
|
|
name = "hearth_temperate", node_top = "hearth:grass", depth_top = 1,
|
|
node_filler = "hearth:soil", depth_filler = 3, node_stone = "hearth:stone",
|
|
node_water_top = "hearth:water_source", depth_water_top = 1,
|
|
node_water = "hearth:water_source", node_river_water = "hearth:water_source",
|
|
node_riverbed = "hearth:sand", depth_riverbed = 2,
|
|
y_min = 2, y_max = 31000, heat_point = 50, humidity_point = 50,
|
|
})
|
|
api.register_biome({
|
|
name = "hearth_riverbank", node_top = "hearth:sand", depth_top = 1,
|
|
node_filler = "hearth:sand", depth_filler = 2, node_stone = "hearth:stone",
|
|
node_water = "hearth:water_source", node_river_water = "hearth:water_source",
|
|
node_riverbed = "hearth:sand", depth_riverbed = 2,
|
|
y_min = -31000, y_max = 1, heat_point = 50, humidity_point = 50,
|
|
})
|
|
|
|
local function decoration(name, surfaces, ratio, ymin, ymax)
|
|
api.register_decoration({
|
|
name = "hearth:" .. name, deco_type = "simple", place_on = surfaces,
|
|
sidelen = 16, fill_ratio = ratio, y_min = ymin, y_max = ymax,
|
|
decoration = "hearth:" .. name,
|
|
})
|
|
end
|
|
decoration("loose_stone", {"hearth:grass", "hearth:sand"}, 0.035, 1, 200)
|
|
decoration("sticks", {"hearth:grass"}, 0.03, 2, 200)
|
|
decoration("berry_bush", {"hearth:grass"}, 0.018, 2, 120)
|
|
decoration("reeds", {"hearth:sand", "hearth:grass"}, 0.025, 1, 200)
|
|
|
|
-- Small seep springs make upland starts viable without handing out supplies.
|
|
-- Replace one surface block: surrounding earth contains water where level;
|
|
-- natural slopes can create a short flowing seep instead.
|
|
api.register_decoration({
|
|
name = "hearth:seep_springs", deco_type = "simple", place_on = {"hearth:grass"},
|
|
sidelen = 16, fill_ratio = 0.0015, y_min = 3, y_max = 150,
|
|
decoration = "hearth:water_source", place_offset_y = -1, flags = "force_placement",
|
|
})
|
|
|
|
-- Table schematics are x-fastest, then y, then z; no external assets.
|
|
local tree = {size = {x = 5, y = 6, z = 5}, data = {}}
|
|
for z = 0, 4 do
|
|
for y = 0, 5 do
|
|
for x = 0, 4 do
|
|
local trunk = x == 2 and z == 2 and y < 5
|
|
local canopy = y >= 3 and math.abs(x - 2) + math.abs(z - 2) <= (y == 5 and 1 or 3)
|
|
tree.data[#tree.data + 1] = {
|
|
name = trunk and "hearth:log" or (canopy and "hearth:leaves" or "air"),
|
|
prob = (trunk or canopy) and 255 or 0,
|
|
}
|
|
end
|
|
end
|
|
end
|
|
api.register_decoration({
|
|
name = "hearth:temperate_trees", deco_type = "schematic",
|
|
place_on = {"hearth:grass"}, sidelen = 16, fill_ratio = 0.006,
|
|
y_min = 3, y_max = 150, schematic = tree, rotation = "random",
|
|
flags = "place_center_x,place_center_z",
|
|
})
|