807 lines
39 KiB
Lua
Executable File
807 lines
39 KiB
Lua
Executable File
-- Original, generated-art content for The Last Hearth (Luanti 5.18).
|
|
-- Survival state is owned/persisted by the caller; numeric needs use 0..100.
|
|
local function world_image(name) return "hearth_" .. name .. ".png" end
|
|
local function item_image(name) return "hearth_item_" .. name .. ".png" end
|
|
-- A private facade, not a mutation of core or its registration tables. Missing
|
|
-- optional registered_* fields in standalone mocks remain missing, as in core.
|
|
local api = setmetatable({
|
|
register_node = function(name, def)
|
|
-- Legacy seasonal definitions still mention mesh atlases. Normalize them
|
|
-- at registration without mutating core tables or the seasons owner.
|
|
local kind=name
|
|
for _,season in ipairs({"spring","autumn","winter"}) do kind=kind:gsub("_"..season.."$","") end
|
|
if kind=="hearth:young_tree" or kind=="hearth:mature_tree" then
|
|
def.mesh = nil
|
|
def.tiles = {world_image("log_top"), world_image("log_top"), world_image("log_side")}
|
|
elseif kind=="hearth:tree_seedling" then
|
|
def.mesh = nil
|
|
def.tiles = {world_image("sapling")}
|
|
end
|
|
return core.register_node(name, def)
|
|
end,
|
|
register_craftitem = function(name, def)
|
|
def.inventory_image = item_image(assert(name:match("^hearth:(.+)$")))
|
|
return core.register_craftitem(name, def)
|
|
end,
|
|
}, {__index = core})
|
|
local modpath = api.get_modpath("hearth")
|
|
-- Plain policy namespace, available before any factory captures calendar APIs.
|
|
hearth.lifecycle = dofile(modpath .. "/lifecycle.lua")
|
|
local construction = dofile(modpath .. "/construction.lua")(hearth, api)
|
|
local seasons = dofile(modpath .. "/seasons.lua")(hearth, api)
|
|
hearth.seasons = seasons
|
|
local botany = dofile(modpath .. "/botany.lua")(hearth, api)
|
|
hearth.botany = botany
|
|
hearth.wiki_articles = dofile(modpath .. "/wiki_articles.lua")
|
|
-- Succession captures the effect policy at construction time.
|
|
hearth.species_effects = dofile(modpath .. "/species_effects.lua")(hearth, api)
|
|
local diversity = dofile(modpath .. "/diversity.lua")(hearth, api)
|
|
hearth.diversity = diversity
|
|
local ecology = dofile(modpath .. "/ecology.lua")(hearth, api, construction)
|
|
local succession = dofile(modpath .. "/succession.lua")(hearth, api)
|
|
hearth.succession = succession
|
|
local fishery = dofile(modpath .. "/fishery.lua")(hearth, api, construction)
|
|
hearth.ecology = ecology
|
|
hearth.fishery = fishery
|
|
hearth.construction = construction
|
|
local interactions = dofile(modpath .. "/interactions.lua")(hearth)
|
|
local processes = dofile(modpath .. "/processes.lua")(hearth, api, construction)
|
|
local allowed, can_dig, place = construction.allowed, construction.can_dig, construction.place
|
|
local place_building, owner = construction.place_building, construction.owner
|
|
local eat = interactions.eat
|
|
local use_fire, fire_tick, refresh_fire = processes.use_fire, processes.fire_tick, processes.refresh_fire
|
|
|
|
-- Semantic exceptions to the default one-name/one-tile manifest contract.
|
|
-- Nodeboxes need opaque physical materials, not stretched inventory silhouettes.
|
|
local node_material = {
|
|
door_open = "door", loose_stone = "stone", sticks = "log_side",
|
|
fire = "stone", fire_lit = "stone", drying_rack = "planks", fish_trap = "planks",
|
|
}
|
|
local function node_art(name, def)
|
|
local image = world_image(name == "door_open" and "door" or name)
|
|
def.tiles = def.tiles or {world_image(node_material[name] or name)}
|
|
if name == "grass" then
|
|
def.tiles = {world_image("grass_top"), world_image("soil"), world_image("grass_side")}
|
|
elseif name == "log" then
|
|
def.tiles = {world_image("log_top"), world_image("log_top"), world_image("log_side")}
|
|
elseif name == "water_source" or name == "water_flowing" then
|
|
local water = world_image("water") .. "^[opacity:185"
|
|
def.tiles = {water}
|
|
def.special_tiles = {{name = water, backface_culling = false},
|
|
{name = water, backface_culling = true}}
|
|
elseif name == "bedroll" then
|
|
-- The opaque central cloth panel, excluding the silhouette's empty border.
|
|
def.tiles = {image .. "^[sheet:4x4:1,2"}
|
|
end
|
|
if name == "berry_bush" or name == "berry_bush_bare" then def.drawtype = "plantlike" end
|
|
if def.drawtype == "plantlike" or name == "leaves" then
|
|
def.use_texture_alpha, def.waving = "clip", 1
|
|
elseif name == "torch" then
|
|
def.use_texture_alpha = "clip"
|
|
end
|
|
if def.drawtype == "plantlike" or def.drawtype == "nodebox" or name == "torch" then
|
|
if def.drawtype == "plantlike" then image = def.tiles[1] end
|
|
def.inventory_image, def.wield_image = def.inventory_image or image, def.wield_image or image
|
|
end
|
|
end
|
|
|
|
local function register_node(name, def)
|
|
node_art(name, def)
|
|
def.on_place = def.on_place or place
|
|
def.can_dig = def.can_dig or can_dig
|
|
local g = def.groups or {}
|
|
local affects_room = g.hearth_building or g.hearth_vent
|
|
local function changed(callback)
|
|
return function(pos, ...)
|
|
local result
|
|
if callback then result = callback(pos, ...) end
|
|
if hearth.invalidate_environment then hearth.invalidate_environment(pos) end
|
|
return result
|
|
end
|
|
end
|
|
if affects_room then
|
|
def.on_construct = changed(def.on_construct)
|
|
def.after_place_node = changed(def.after_place_node)
|
|
def.after_destruct = changed(def.after_destruct)
|
|
end
|
|
-- Distinguish hand-placed natural-looking ground/flora from mapgen terrain.
|
|
local after_place = def.after_place_node
|
|
def.after_place_node = function(pos, ...)
|
|
api.get_meta(pos):set_string("player_placed", "1")
|
|
if after_place then return after_place(pos, ...) end
|
|
end
|
|
local material = (g.wood or g.tree or g.choppy) and "wood"
|
|
or ((g.stone or g.cracky) and "stone" or "soil")
|
|
def.sounds = def.sounds or {dug = {name = "hearth_dig_" .. material, gain = 0.35},
|
|
place = {name = "hearth_dig_" .. material, gain = 0.2}}
|
|
api.register_node("hearth:" .. name, def)
|
|
end
|
|
|
|
register_node("stone", {
|
|
description = "Weathered stone",
|
|
groups = {cracky = 3, stone = 1}, drop = "hearth:loose_stone 3",
|
|
})
|
|
register_node("soil", {
|
|
description = "Earth",
|
|
groups = {crumbly = 3, soil = 1},
|
|
})
|
|
register_node("grass", {
|
|
description = "Meadow turf",
|
|
groups = {crumbly = 3, soil = 1}, drop = "hearth:soil",
|
|
})
|
|
register_node("sand", {
|
|
description = "River sand",
|
|
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",
|
|
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)",
|
|
paramtype2 = "facedir", groups = {tree = 1, choppy = 2, flammable = 2},
|
|
})
|
|
register_node("leaves", {
|
|
description = "Canopy leaves", drawtype = "allfaces_optional",
|
|
paramtype = "light", sunlight_propagates = true,
|
|
groups = {snappy = 3, leaves = 1, flammable = 2},
|
|
drop = {items = {{items = {"hearth:sticks"}, rarity = 1},
|
|
{items = {"hearth:sapling"}, rarity = 8}}},
|
|
})
|
|
|
|
register_node("loose_stone", {
|
|
description = "Loose flint stone", drawtype = "nodebox",
|
|
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",
|
|
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},
|
|
})
|
|
register_node("torch", {
|
|
description = "Reed torch", drawtype = "torchlike",
|
|
paramtype = "light", paramtype2 = "wallmounted", sunlight_propagates = true, walkable = false,
|
|
light_source = 12, groups = {dig_immediate = 3}, drop = "hearth:torch",
|
|
})
|
|
|
|
register_node("berry_bush", {
|
|
description = "Wild berry bush", drawtype = "allfaces_optional",
|
|
paramtype = "light", walkable = false, sunlight_propagates = true,
|
|
groups = {snappy = 3, flora = 1, flammable = 2}, drop = "",
|
|
can_dig = function(pos, player) return construction.allowed(pos, player, "use") end,
|
|
on_dig = ecology.berry_dig,
|
|
on_rightclick = function(pos, _, player) ecology.harvest_berries(pos, player) end,
|
|
})
|
|
register_node("berry_bush_bare", {
|
|
description = "Berry bush (regrowing)", drawtype = "allfaces_optional",
|
|
paramtype = "light", walkable = false,
|
|
sunlight_propagates = true, drop = "",
|
|
groups = {snappy = 3, flora = 1, flammable = 2, not_in_creative_inventory = 1},
|
|
can_dig = function(pos, player) return construction.allowed(pos, player, "use") end,
|
|
on_dig = ecology.berry_dig,
|
|
on_rightclick = function(pos, _, player) ecology.harvest_berries(pos, player) end,
|
|
on_timer = function(pos) return ecology.refresh_bush(pos) end,
|
|
})
|
|
register_node("sapling", {
|
|
description = "Young tree sapling", drawtype = "plantlike",
|
|
paramtype = "light", sunlight_propagates = true, walkable = false,
|
|
groups = {snappy = 3, flora = 1, flammable = 2},
|
|
on_place = ecology.plant_sapling,
|
|
on_timer = ecology.grow_sapling,
|
|
})
|
|
-- Ecological sprites and ordinary log-cube roots; multi-node tree placement is
|
|
-- owned by ecology through diversity.tree_plan, not these node definitions.
|
|
-- Named textures are generated by the game's original asset pipeline.
|
|
for _, plant in ipairs({
|
|
{"meadow_grass", "Living meadow grass", 0.8},
|
|
{"wildflowers", "Meadow wildflowers", 1},
|
|
{"fern", "Shade fern", 1},
|
|
{"tree_seedling", "Naturally seeded tree", 0.65},
|
|
{"young_tree", "Young woodland tree", 1.5},
|
|
{"mature_tree", "Mature seed-bearing tree", 2.6},
|
|
{"fungi", "Decomposer fungi (not edible)", 0.5},
|
|
{"litter", "Decomposing leaf litter", 0.35},
|
|
{"deadwood", "Fallen log (axe; yields timber)", 1},
|
|
{"dung", "Wildlife dung (returns nutrients to the soil)", 0.25},
|
|
}) do
|
|
local name, description, scale = plant[1], plant[2], plant[3]
|
|
local woody = name == "young_tree" or name == "mature_tree" or name == "deadwood"
|
|
register_node(name, {
|
|
description = description, drawtype = woody and "normal" or "plantlike", visual_scale = woody and 1 or scale,
|
|
tiles = woody and {world_image("log_top"),world_image("log_top"),world_image("log_side")}
|
|
or {world_image(name == "tree_seedling" and "sapling" or name)},
|
|
paramtype = not woody and "light" or nil, paramtype2 = woody and "facedir" or nil,
|
|
sunlight_propagates = not woody, walkable = woody,
|
|
use_texture_alpha = not woody and "clip" or nil, buildable_to = not woody and name ~= "tree_seedling",
|
|
groups = woody and {tree=1, choppy=2, flammable=2, hearth_ecosystem=1, not_in_creative_inventory=1}
|
|
or {snappy=3, flora=1, flammable=2, hearth_ecosystem=1},
|
|
drop = woody and "hearth:log" or "",
|
|
})
|
|
end
|
|
-- Cosmetic migration of this one legacy node only; swap preserves provenance,
|
|
-- ecological age, inventories and timers. No terrain generation or conversion
|
|
-- to canonical timber (which would lose the decomposer's identity).
|
|
api.register_lbm({name="hearth:block_deadwood", label="Lay legacy fallen log cubes sideways",
|
|
nodenames={"hearth:deadwood"}, run_at_every_load=true, action=function(pos)
|
|
local current=api.get_node_or_nil(pos)
|
|
if not current or current.name~="hearth:deadwood" then return end
|
|
local protect=hearth.external_protected or api.is_protected
|
|
if protect and protect(pos, "") then return end
|
|
local meta=api.get_meta(pos)
|
|
if meta:get_string("succ_block_deadwood")=="1" then return end
|
|
local axis=math.floor((current.param2 or 0)/4)
|
|
if axis<1 or axis>4 then
|
|
api.swap_node(pos,{name=current.name,param1=current.param1,param2=12})
|
|
end
|
|
meta:set_string("succ_block_deadwood","1")
|
|
end})
|
|
for _, forage in ipairs({{"spring_greens", "Spring greens"}, {"autumn_mushrooms", "Autumn mushrooms"}}) do
|
|
local kind = forage[1]
|
|
register_node(kind .. "_patch", {
|
|
description = forage[2] .. " patch (pick in season)", drawtype = "plantlike",
|
|
tiles = {"hearth_" .. kind .. "_dormant.png"},
|
|
paramtype = "light", sunlight_propagates = true, walkable = false,
|
|
-- Occupied, even when dormant: neither placement nor succession overwrites it.
|
|
buildable_to = false, drop = "",
|
|
groups = {snappy=3, flora=1, flammable=2, hearth_ecosystem=1},
|
|
can_dig = function(pos, player) return construction.allowed(pos, player, "use") end,
|
|
on_dig = function(pos, _, player) ecology.harvest_seasonal(pos, player) end,
|
|
on_rightclick = function(pos, _, player) ecology.harvest_seasonal(pos, player) end,
|
|
})
|
|
end
|
|
for stage = 1, 4 do
|
|
register_node("crop_" .. stage, {
|
|
description = stage == 4 and "Ripe grain crop (harvest for food and seed)" or "Growing grain crop",
|
|
drawtype = "plantlike",
|
|
paramtype = "light", sunlight_propagates = true, walkable = false, drop = "",
|
|
groups = {snappy = 3, flora = 1, flammable = 2,
|
|
not_in_creative_inventory = stage > 1 and 1 or nil},
|
|
can_dig = function(pos, player) return construction.allowed(pos, player, "use") end,
|
|
on_dig = ecology.crop_dig,
|
|
on_place = ecology.plant_crop,
|
|
on_rightclick = function(pos, _, player) ecology.harvest_crop(pos, player) end,
|
|
on_construct = function(pos) api.get_meta(pos):set_int("planted_at", os.time()) end,
|
|
on_timer = function(pos) return ecology.update_crop(pos) end,
|
|
})
|
|
end
|
|
api.register_lbm({
|
|
name = "hearth:crop_growth", label = "Reconcile seasonal crop growth",
|
|
nodenames = {"hearth:crop_1", "hearth:crop_2", "hearth:crop_3", "hearth:crop_4"},
|
|
run_at_every_load = true, action = ecology.reconcile_crop,
|
|
})
|
|
api.register_lbm({
|
|
name = "hearth:berry_regrowth", label = "Reconcile berry-bush regrowth",
|
|
nodenames = {"hearth:berry_bush_bare"}, run_at_every_load = true,
|
|
action = ecology.reconcile_bush,
|
|
})
|
|
api.register_lbm({
|
|
name = "hearth:sapling_growth", label = "Reconcile bounded tree regrowth",
|
|
nodenames = {"hearth:sapling"}, run_at_every_load = true,
|
|
action = ecology.reconcile_sapling,
|
|
})
|
|
api.register_lbm({
|
|
name = "hearth:fish_trap_reconcile", label = "Reconcile baited river traps",
|
|
nodenames = {"hearth:fish_trap"}, run_at_every_load = true, action = fishery.reconcile,
|
|
})
|
|
api.register_craftitem("hearth:berries", {
|
|
description = "Wild berries",
|
|
on_use = eat({food = 12, water = 4}),
|
|
})
|
|
api.register_craftitem("hearth:spring_greens", {
|
|
description = "Picked spring greens", on_use = eat({food = 12}),
|
|
})
|
|
api.register_craftitem("hearth:autumn_mushrooms", {
|
|
description = "Picked autumn mushrooms (craft to forage, then cook at a hearth)",
|
|
})
|
|
api.register_craftitem("hearth:grain", {
|
|
description = "Harvested grain (reserve one to replant)",
|
|
})
|
|
api.register_craftitem("hearth:grain_seed", {
|
|
description = "Grain seed",
|
|
on_place = ecology.plant_crop,
|
|
})
|
|
api.register_craftitem("hearth:raw_food", {
|
|
description = "Foraged food (cook at a hearth)",
|
|
})
|
|
api.register_craftitem("hearth:cooked_food", {
|
|
description = "Hearth-cooked forage",
|
|
on_use = eat({food = 35, stamina = 10}),
|
|
})
|
|
api.register_craftitem("hearth:fish_bait", {
|
|
description = "Grain-and-berry fish bait",
|
|
})
|
|
api.register_craftitem("hearth:raw_fish", {
|
|
description = "Raw river fish (cook or dry before eating)",
|
|
})
|
|
api.register_craftitem("hearth:cooked_fish", {
|
|
description = "Hearth-roasted river fish",
|
|
on_use = eat({food = 32, stamina = 6}),
|
|
})
|
|
api.register_craftitem("hearth:fish_drying_charge", {
|
|
description = "Roasted fish and reeds ready for drying",
|
|
})
|
|
api.register_craftitem("hearth:dried_fish", {
|
|
description = "Dried river fish (shelf-stable)",
|
|
on_use = eat({food = 26, water = -1}),
|
|
})
|
|
api.register_craftitem("hearth:iron_ore", {description = "Iron ore"})
|
|
api.register_craftitem("hearth:iron_ingot", {description = "Hearth-smelted iron ingot"})
|
|
api.register_craftitem("hearth:copper_ingot", {description = "Hearth-smelted copper ingot"})
|
|
api.register_craftitem("hearth:copper_charge", {
|
|
description = "Copper ore and charcoal ready for hearth smelting",
|
|
})
|
|
api.register_craftitem("hearth:charcoal", {description = "Prepared charcoal fuel"})
|
|
api.register_craftitem("hearth:charcoal_charge", {
|
|
description = "Log and clay ready for a charcoal burn",
|
|
})
|
|
api.register_craftitem("hearth:smelt_charge", {
|
|
description = "Iron ore and charcoal ready for hearth smelting",
|
|
})
|
|
api.register_craftitem("hearth:clay", {description = "River clay"})
|
|
api.register_craftitem("hearth:clay_jar", {description = "Fired grain jar"})
|
|
api.register_craftitem("hearth:clay_jar_charge", {
|
|
description = "Unfired clay jar (fire at a hearth)",
|
|
})
|
|
api.register_craftitem("hearth:dried_food", {
|
|
description = "Dried preserved ration (shelf-stable)",
|
|
on_use = eat({food = 28, water = -2}),
|
|
})
|
|
api.register_craftitem("hearth:dried_food_charge", {
|
|
description = "Cooked forage ready for the drying rack process",
|
|
})
|
|
register_node("grain_patch", {
|
|
description = "Wild grain", drawtype = "plantlike",
|
|
paramtype = "light", sunlight_propagates = true, walkable = false,
|
|
groups = {snappy = 3, flora = 1, flammable = 2}, drop = "hearth:grain_seed",
|
|
})
|
|
register_node("clay_bank", {
|
|
description = "Exposed river clay",
|
|
groups = {crumbly = 2, clay = 1}, drop = "hearth:clay 2",
|
|
})
|
|
register_node("iron_ore_vein", {
|
|
description = "Iron-bearing rock (requires a pick)",
|
|
groups = {cracky = 3, stone = 1}, drop = "hearth:iron_ore 2",
|
|
})
|
|
register_node("weir_stone", {
|
|
description = "Old river-weir masonry",
|
|
groups = {cracky = 3, river_landmark = 1}, drop = "",
|
|
})
|
|
register_node("fish_trap", {
|
|
description = "Reed-and-stick river trap", drawtype = "nodebox",
|
|
paramtype = "light", sunlight_propagates = true, walkable = false,
|
|
node_box = {type = "fixed", fixed = {{-0.42,-0.5,-0.42,0.42,-0.34,0.42},
|
|
{-0.36,-0.34,-0.36,-0.28,0.18,-0.28},{0.28,-0.34,-0.36,0.36,0.18,-0.28},
|
|
{-0.36,-0.34,0.28,-0.28,0.18,0.36},{0.28,-0.34,0.28,0.36,0.18,0.36}}},
|
|
selection_box = {type = "fixed", fixed = {-0.45,-0.5,-0.45,0.45,0.2,0.45}},
|
|
groups = {choppy = 3, wood = 1, flammable = 2, hearth_non_support = 1}, drop = "hearth:fish_trap",
|
|
on_place = fishery.plant_trap,
|
|
on_construct = function(pos)
|
|
local meta = api.get_meta(pos)
|
|
meta:set_string("state", "idle"); meta:set_string("ready_at", "0")
|
|
meta:set_int("catch_count", 0); meta:set_string("infotext", "River trap: ready for bait beside water.")
|
|
end,
|
|
after_place_node = owner, can_dig = fishery.can_dig,
|
|
on_rightclick = fishery.rightclick, on_timer = fishery.on_timer,
|
|
})
|
|
register_node("reeds", {
|
|
description = "Thatching rushes", drawtype = "plantlike",
|
|
paramtype = "light", walkable = false, sunlight_propagates = true,
|
|
groups = {snappy = 3, flora = 1, flammable = 2},
|
|
})
|
|
register_node("planks", {
|
|
description = "Split timber planks",
|
|
groups = {choppy = 3, wood = 1, flammable = 2},
|
|
})
|
|
register_node("wall", {
|
|
description = "Timber wall",
|
|
groups = {choppy = 3, wood = 1, flammable = 2, hearth_building = 1},
|
|
on_place = place_building, after_place_node = owner,
|
|
})
|
|
register_node("roof", {
|
|
description = "Thatch roof",
|
|
groups = {snappy = 3, flammable = 3, hearth_building = 1},
|
|
on_place = place_building, after_place_node = owner,
|
|
})
|
|
register_node("foundation", {
|
|
description = "Stone foundation",
|
|
groups = {cracky = 3, stone = 1, hearth_building = 1},
|
|
on_place = place_building, after_place_node = owner,
|
|
})
|
|
register_node("frame", {
|
|
description = "Timber frame",
|
|
groups = {choppy = 3, wood = 1, flammable = 2, hearth_building = 1},
|
|
on_place = place_building, after_place_node = owner,
|
|
})
|
|
register_node("stairs", {
|
|
description = "Timber stairs", drawtype = "nodebox",
|
|
paramtype = "light", paramtype2 = "facedir",
|
|
node_box = {type = "fixed", fixed = {
|
|
{-0.5,-0.5,-0.5, 0.5,0,0.5}, {-0.5,0,-0.5, 0.5,0.5,0},
|
|
}},
|
|
groups = {choppy = 3, wood = 1, flammable = 2, hearth_building = 1},
|
|
on_place = place_building, after_place_node = owner,
|
|
})
|
|
local function toggle_door(pos, node, player)
|
|
if not construction.allowed(pos, player, "use") then return end
|
|
local next_name = node.name == "hearth:door_open" and "hearth:door" or "hearth:door_open"
|
|
if next_name == "hearth:door" and api.get_objects_inside_radius then
|
|
for _, object in ipairs(api.get_objects_inside_radius(pos, 0.8)) do
|
|
if object:is_player() then hearth.notify(player, "Stand clear before closing the door."); return end
|
|
end
|
|
end
|
|
api.swap_node(pos, {name = next_name, param2 = node.param2})
|
|
if hearth.invalidate_environment then hearth.invalidate_environment(pos) end
|
|
end
|
|
register_node("door", {
|
|
description = "Timber door (right-click to open)", drawtype = "nodebox",
|
|
paramtype = "light", paramtype2 = "4dir", node_box = {type = "fixed", fixed = {-0.5,-0.5,-0.08,0.5,0.5,0.08}},
|
|
groups = {choppy = 3, wood = 1, flammable = 2, hearth_building = 1},
|
|
on_place = place_building, after_place_node = owner, on_rightclick = toggle_door,
|
|
})
|
|
register_node("door_open", {
|
|
description = "Open timber door", drawtype = "nodebox",
|
|
paramtype = "light", paramtype2 = "4dir", walkable = false,
|
|
node_box = {type = "fixed", fixed = {-0.5,-0.5,-0.08,-0.42,0.5,0.08}},
|
|
groups = {choppy = 3, wood = 1, flammable = 2, hearth_building = 1, not_in_creative_inventory = 1},
|
|
drop = "hearth:door", on_place = place_building, can_dig = can_dig, on_rightclick = toggle_door,
|
|
allow_metadata_inventory_put = function() return 0 end,
|
|
allow_metadata_inventory_take = function() return 0 end,
|
|
allow_metadata_inventory_move = function() return 0 end,
|
|
})
|
|
register_node("window", {
|
|
description = "Reed-lattice window", drawtype = "glasslike", paramtype = "light",
|
|
use_texture_alpha = "blend", sunlight_propagates = true,
|
|
groups = {cracky = 3, hearth_building = 1},
|
|
on_place = place_building, after_place_node = owner,
|
|
})
|
|
register_node("vent", {
|
|
description = "Roof vent (opens a room to the air)",
|
|
groups = {choppy = 3, wood = 1, flammable = 1, hearth_building = 1, hearth_vent = 1},
|
|
on_place = place_building, after_place_node = owner,
|
|
})
|
|
local function claimed_utility_place(stack, player, pointed)
|
|
if not pointed or pointed.type ~= "node" or not player or not player:is_player() then return stack end
|
|
local under = api.get_node_or_nil(pointed.under)
|
|
local above = api.get_node_or_nil(pointed.above)
|
|
local under_def = under and api.registered_nodes[under.name]
|
|
local above_def = above and api.registered_nodes[above.name]
|
|
local pos = under_def and under_def.buildable_to and pointed.under
|
|
or (above_def and above_def.buildable_to and pointed.above)
|
|
if not pos or not hearth.claim_owner_at(pos)
|
|
or not hearth.claim_allowed(pos, player:get_player_name(), "build") then
|
|
hearth.notify(player, "Place this utility inside a homestead you own or share.")
|
|
return stack
|
|
end
|
|
return place(stack, player, pointed)
|
|
end
|
|
local function storage_allowed(pos, player)
|
|
if not player or not player:is_player() then return false end
|
|
local name = player:get_player_name()
|
|
local stored_owner = api.get_meta(pos):get_string("owner")
|
|
local claim_owner = hearth.claim_owner_at(pos)
|
|
-- In a claimed household, current claim membership is authoritative so the
|
|
-- owner can still administer storage after a resident leaves. Unclaimed legacy
|
|
-- storage remains private to its recorded placer.
|
|
if not claim_owner and stored_owner ~= name then return false end
|
|
return construction.allowed(pos, player, "storage")
|
|
end
|
|
local function storage_can_dig(pos, player)
|
|
if not player or not player:is_player() then return false end
|
|
local name = player:get_player_name()
|
|
local stored_owner = api.get_meta(pos):get_string("owner")
|
|
local claim_owner = hearth.claim_owner_at(pos)
|
|
local role = claim_owner and type(hearth.claim_role_at) == "function"
|
|
and hearth.claim_role_at(pos, name) or nil
|
|
local may_remove = claim_owner and (role == "owner" or role == "resident")
|
|
or (not claim_owner and stored_owner == name)
|
|
return may_remove and can_dig(pos, player) and api.get_meta(pos):get_inventory():is_empty("main")
|
|
end
|
|
local function utility_formspec(pos, title, rows)
|
|
local id = pos.x .. "," .. pos.y .. "," .. pos.z
|
|
local player_y = 1.25 * rows + 1.25
|
|
return "formspec_version[6]size[11," .. (player_y + 6) .. "]bgcolor[#1e1e2e;true]"
|
|
.. "style_type[label;textcolor=#cdd6f4]"
|
|
.. "style_type[button;bgcolor=#313244;textcolor=#cdd6f4;border=false]"
|
|
.. "style[close;bgcolor=#cba6f7;textcolor=#1e1e2e]"
|
|
.. "listcolors[#313244;#45475a;#cba6f7;#181825;#cdd6f4]"
|
|
.. "box[0.25,0.2;10.5,0.55;#313244]label[0.5,0.45;" .. title .. "]"
|
|
.. "list[nodemeta;" .. id .. ";main;0.5,1;8," .. rows .. ";]"
|
|
.. "list[current_player;main;0.5," .. player_y .. ";8,4;]"
|
|
.. "button_exit[8," .. (player_y + 5.1) .. ";2.25,0.65;close;Close]"
|
|
.. "listring[nodemeta;" .. id .. ";main]listring[current_player;main]"
|
|
end
|
|
register_node("storage", {
|
|
description = "Homestead storage",
|
|
groups = {choppy = 3, wood = 1, flammable = 2, hearth_storage = 1, hearth_non_support = 1},
|
|
on_place = claimed_utility_place, after_place_node = owner,
|
|
on_construct = function(pos)
|
|
local meta = api.get_meta(pos)
|
|
meta:get_inventory():set_size("main", 24)
|
|
meta:set_string("infotext", "Homestead storage (24 slots)")
|
|
end,
|
|
can_dig = storage_can_dig,
|
|
on_rightclick = function(pos, _, player)
|
|
if not storage_allowed(pos, player) then return end
|
|
api.show_formspec(player:get_player_name(), "hearth:storage",
|
|
utility_formspec(pos, "HOMESTEAD STORAGE", 3))
|
|
end,
|
|
allow_metadata_inventory_put = function(pos, list, index, item, player)
|
|
return storage_allowed(pos, player) and item:get_count() or 0
|
|
end,
|
|
allow_metadata_inventory_take = function(pos, list, index, item, player)
|
|
return storage_allowed(pos, player) and item:get_count() or 0
|
|
end,
|
|
allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
|
|
return storage_allowed(pos, player) and count or 0
|
|
end,
|
|
})
|
|
register_node("pantry", {
|
|
description = "Fired-clay pantry (48 slots)",
|
|
groups = {cracky = 3, hearth_storage = 1, hearth_non_support = 1},
|
|
on_place = claimed_utility_place, after_place_node = owner,
|
|
on_construct = function(pos)
|
|
local meta = api.get_meta(pos)
|
|
meta:get_inventory():set_size("main", 48)
|
|
meta:set_string("infotext", "Fired-clay pantry (48 slots; store shelf-stable food here)")
|
|
end,
|
|
can_dig = storage_can_dig,
|
|
on_rightclick = function(pos, _, player)
|
|
if not storage_allowed(pos, player) then return end
|
|
api.show_formspec(player:get_player_name(), "hearth:storage",
|
|
utility_formspec(pos, "FIRED-CLAY PANTRY", 6))
|
|
end,
|
|
allow_metadata_inventory_put = function(pos, list, index, item, player)
|
|
return storage_allowed(pos, player) and item:get_count() or 0
|
|
end,
|
|
allow_metadata_inventory_take = function(pos, list, index, item, player)
|
|
return storage_allowed(pos, player) and item:get_count() or 0
|
|
end,
|
|
allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
|
|
return storage_allowed(pos, player) and count or 0
|
|
end,
|
|
})
|
|
register_node("drying_rack", {
|
|
description = "Sheltered drying rack", drawtype = "nodebox",
|
|
paramtype = "light", walkable = false, sunlight_propagates = true,
|
|
node_box = {type = "fixed", fixed = {{-0.45,-0.5,-0.08,0.45,-0.38,0.08},{-0.4,-0.4,-0.06,0.4,0.35,0.06}}},
|
|
groups = {choppy = 3, wood = 1, flammable = 2}, on_place = claimed_utility_place, after_place_node = owner,
|
|
})
|
|
api.register_craftitem("hearth:cloak", {
|
|
description = "Woven rain cloak (click to wear or remove)", stack_max = 1,
|
|
on_use = function(stack, player)
|
|
local meta = player:get_meta()
|
|
local worn = meta:get_int("hearth:cloak") ~= 1
|
|
meta:set_int("hearth:cloak", worn and 1 or 0)
|
|
hearth.notify(player, worn and "You wear the rain cloak; exposure wets you more slowly." or "You remove the rain cloak.")
|
|
return stack
|
|
end,
|
|
})
|
|
register_node("bedroll", {
|
|
description = "Woven bedroll (rest under cover)", drawtype = "nodebox",
|
|
paramtype = "light", paramtype2 = "facedir", sunlight_propagates = true,
|
|
walkable = false, node_box = {type = "fixed", fixed = {-0.42, -0.5, -0.34, 0.42, -0.38, 0.34}},
|
|
selection_box = {type = "fixed", fixed = {-0.48, -0.5, -0.42, 0.48, -0.36, 0.42}},
|
|
groups = {snappy = 3, flammable = 2},
|
|
on_rightclick = function(pos, _, player)
|
|
if hearth.start_rest then hearth.start_rest(player, pos) end
|
|
end,
|
|
after_place_node = owner,
|
|
})
|
|
|
|
-- No hand choppy/cracky capability: timber needs an axe, stone a pick.
|
|
api.register_item(":", {
|
|
type = "none", wield_image = hearth.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)
|
|
api.register_tool("hearth:iron_axe", {
|
|
description = "Iron axe", inventory_image = item_image("iron_axe"),
|
|
tool_capabilities = {full_punch_interval = 0.9, max_drop_level = 2,
|
|
groupcaps = {choppy = {times = {[3] = 0.35, [2] = 0.8, [1] = 1.4}, uses = 120, maxlevel = 2}},
|
|
damage_groups = {fleshy = 6}},
|
|
})
|
|
api.register_tool("hearth:copper_pick", {
|
|
description = "Copper pick", inventory_image = item_image("copper_pick"),
|
|
tool_capabilities = {full_punch_interval = 1.2, max_drop_level = 1,
|
|
groupcaps = {cracky = {times = {[3] = 1.4}, uses = 70, maxlevel = 1}},
|
|
damage_groups = {fleshy = 3}},
|
|
})
|
|
api.register_tool("hearth:firestarter", {
|
|
description = "Flint firestarter (20 ignitions)", inventory_image = item_image("firestarter"),
|
|
tool_capabilities = {full_punch_interval = 1, max_drop_level = 0,
|
|
groupcaps = {}, damage_groups = {fleshy = 1}},
|
|
on_use = processes.use_firestarter,
|
|
on_place = processes.use_firestarter,
|
|
on_secondary_use = processes.use_firestarter,
|
|
})
|
|
|
|
for _, lit in ipairs({false, true}) do
|
|
register_node(lit and "fire_lit" or "fire", {
|
|
description = "Stone hearth", drawtype = "nodebox", paramtype = "light",
|
|
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, hearth_non_support = 1,
|
|
not_in_creative_inventory = lit and 1 or nil},
|
|
can_dig = function(pos, player)
|
|
if not can_dig(pos, player) then return false end
|
|
local meta = api.get_meta(pos)
|
|
return meta:get_string("job_kind") == ""
|
|
and meta:get_string("job_result") == ""
|
|
end,
|
|
on_construct = function(pos)
|
|
local meta = api.get_meta(pos)
|
|
meta:set_int("lit", 0)
|
|
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 hearth fuel and queued processes",
|
|
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)",
|
|
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)",
|
|
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",
|
|
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:copper_pick", {{"hearth:copper_ingot", "hearth:copper_ingot", "hearth:copper_ingot"}, {"", "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"}}},
|
|
{"hearth:bedroll", {{"hearth:planks", "hearth:planks", "hearth:planks"}, {"hearth:reeds", "hearth:reeds", "hearth:reeds"}}},
|
|
{"hearth:foundation 4", {{"hearth:stone", "hearth:stone"}, {"hearth:stone", "hearth:stone"}}},
|
|
{"hearth:frame 4", {{"hearth:planks", "hearth:sticks"}, {"hearth:sticks", "hearth:planks"}}},
|
|
{"hearth:stairs 4", {{"hearth:planks", "", ""}, {"hearth:planks", "hearth:planks", ""}, {"hearth:planks", "hearth:planks", "hearth:planks"}}},
|
|
{"hearth:door", {{"hearth:planks", "hearth:planks"}, {"hearth:planks", "hearth:planks"}, {"hearth:planks", "hearth:planks"}}},
|
|
{"hearth:window", {{"hearth:planks", "hearth:reeds", "hearth:planks"}, {"hearth:planks", "hearth:reeds", "hearth:planks"}}},
|
|
{"hearth:vent", {{"hearth:planks", "hearth:reeds", "hearth:planks"}, {"hearth:planks", "", "hearth:planks"}}},
|
|
{"hearth:storage", {{"hearth:planks", "hearth:planks", "hearth:planks"}, {"hearth:planks", "", "hearth:planks"}, {"hearth:planks", "hearth:planks", "hearth:planks"}}},
|
|
{"hearth:pantry", {{"hearth:planks", "hearth:clay_jar", "hearth:planks"}, {"hearth:planks", "", "hearth:planks"}, {"hearth:planks", "hearth:planks", "hearth:planks"}}},
|
|
{"hearth:drying_rack", {{"hearth:sticks", "hearth:sticks", "hearth:sticks"}, {"hearth:reeds", "", "hearth:reeds"}}},
|
|
{"hearth:iron_axe", {{"hearth:iron_ingot", "hearth:iron_ingot"}, {"hearth:iron_ingot", "hearth:sticks"}, {"", "hearth:sticks"}}},
|
|
{"hearth:fish_trap", {{"hearth:sticks", "hearth:reeds", "hearth:sticks"}, {"hearth:reeds", "", "hearth:reeds"}}},
|
|
}
|
|
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"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:raw_food", recipe = {"hearth:autumn_mushrooms"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:firestarter",
|
|
recipe = {"hearth:loose_stone", "hearth:sticks"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:torch 2",
|
|
recipe = {"hearth:sticks", "hearth:reeds"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:cloak",
|
|
recipe = {"hearth:reeds", "hearth:reeds", "hearth:reeds", "hearth:reeds", "hearth:planks"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:grain_seed",
|
|
recipe = {"hearth:grain"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:raw_food",
|
|
recipe = {"hearth:grain", "hearth:grain"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:charcoal_charge",
|
|
recipe = {"hearth:log", "hearth:clay"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:smelt_charge",
|
|
recipe = {"hearth:iron_ore", "hearth:charcoal"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:copper_charge",
|
|
recipe = {"hearth:copper_ore", "hearth:charcoal"}})
|
|
-- A mineral route to the same window, not a second node or free reeds.
|
|
api.register_craft({type = "shapeless", output = "hearth:window",
|
|
recipe = {"hearth:quartz", "hearth:quartz", "hearth:quartz"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:dried_food_charge",
|
|
recipe = {"hearth:cooked_food", "hearth:reeds"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:fish_bait 2",
|
|
recipe = {"hearth:grain", "hearth:berries"}})
|
|
api.register_craft({type = "shapeless", output = "hearth:fish_drying_charge",
|
|
recipe = {"hearth:cooked_fish", "hearth:reeds"}})
|
|
api.register_craft({output = "hearth:clay_jar_charge", recipe = {
|
|
{"hearth:clay", "hearth:clay", "hearth:clay"}, {"hearth:clay", "", "hearth:clay"},
|
|
{"", "hearth:clay", ""}}})
|
|
|
|
-- One owner for aliases, terrain nodes, biomes, ores and decorations. Succession
|
|
-- reads this shared habitat API lazily; do not retain the old registrations.
|
|
local biomes = dofile(modpath .. "/biomes.lua")(hearth, api, register_node, construction)
|
|
hearth.biomes = biomes.register()
|
|
diversity.register_nodes(register_node)
|
|
botany.register_nodes(register_node)
|
|
-- Species leaves and canonical ecological cubes/sprites must precede variants.
|
|
seasons.register()
|
|
|
|
-- Creatura's MIT-licensed boid and ambient-spawn APIs are optional at runtime;
|
|
-- the managed package pins and bundles the ContentDB 0.2.10 release.
|
|
if type(creatura) == "table" then hearth.creatura = creatura end
|
|
hearth.wildlife = dofile(modpath .. "/wildlife.lua")(hearth, api)
|
|
-- Wildlife owns factory loading; publish that exact catalogue, not a second one.
|
|
hearth.fauna = hearth.wildlife.fauna
|