Files
2026-11-03 12:41:09 -05:00

114 lines
5.3 KiB
Lua
Executable File

-- Original bounded seasonal appearance. No timers, world lists or clock replay.
return function(hearth, api)
local S = {COLUMN_HEIGHT = 12, MAX_COLUMN_SWAPS = 12}
local variants, canonical, availability = {}, {}, {}
local function variant(kind, season, texture, mesh)
variants[kind] = variants[kind] or {}
local short = kind .. "_" .. season:lower()
variants[kind][season] = {name="hearth:"..short, texture=texture, mesh=mesh}
canonical["hearth:"..short] = kind
end
for _, kind in ipairs({"tree_seedling", "young_tree", "mature_tree", "leaves"}) do
for _, season in ipairs({"Spring", "Autumn", "Winter"}) do
local suffix = season:lower()
local mesh = kind ~= "leaves" and ("hearth_"..kind
.. (season == "Winter" and kind ~= "tree_seedling" and "_winter" or "") .. ".obj") or nil
variant(kind, season, "hearth_" .. (kind == "leaves" and "leaves" or "tree_atlas") .. "_" .. suffix .. ".png", mesh)
end
end
for _, kind in ipairs({"meadow_grass", "fern", "wildflowers"}) do
variant(kind, "Winter", "hearth_"..kind.."_dormant.png")
if kind == "wildflowers" then variant(kind, "Autumn", "hearth_wildflowers_dormant.png") end
end
-- Patch nodes and picked food items have different names: never overwrite a
-- registered node with a craftitem, and never let picked food reset a patch.
variant("spring_greens_patch", "Spring", "hearth_spring_greens.png")
variant("autumn_mushrooms_patch", "Autumn", "hearth_autumn_mushrooms.png")
for kind in pairs(variants) do canonical["hearth:"..kind] = kind end
-- Extensions are installed by botany/diversity before node registration. Missing
-- bases in minimal mocks/optional content are skipped, never asserted.
function S.add_variants(kind, definitions, active_seasons)
assert(type(kind)=="string" and kind:match("^[a-z_]+$"), "invalid flora kind")
canonical["hearth:"..kind] = kind
for season, def in pairs(definitions or {}) do
assert(({Spring=true,Summer=true,Autumn=true,Winter=true})[season], "invalid season")
variant(kind, season, assert(def.texture), def.mesh)
end
availability[kind] = active_seasons
end
function S.kind(n)
local name = type(n) == "table" and n.name or n
return type(name) == "string" and (canonical[name] or name:match("^hearth:(.+)$")) or nil
end
function S.season(now)
local climate = hearth.climate_at and hearth.climate_at(now or os.time())
return climate and climate.season or "Summer"
end
function S.name_for(kind, now)
kind = S.kind(kind) or kind
local v = variants[kind] and variants[kind][S.season(now)]
return v and v.name or "hearth:"..kind
end
function S.available(kind, now)
kind = S.kind(kind) or kind
local season = S.season(now)
if availability[kind] then return availability[kind][season] == true end
if kind == "spring_greens_patch" then return season == "Spring" end
if kind == "autumn_mushrooms_patch" then return season == "Autumn" end
if kind == "wildflowers" then return season == "Spring" or season == "Summer" end
return not variants[kind] or season ~= "Winter"
end
function S.names()
local names = {}
for name in pairs(canonical) do names[#names+1] = name end
return names
end
function S.reconcile(p, n, now)
-- Appearance alone can change a claimed/hand-placed plant, but never its
-- provenance, age, harvest deadline, dig permission, drops or inventory.
n = n or api.get_node_or_nil(p)
if not n or not canonical[n.name] then return false end
local protection = hearth.external_protected or api.is_protected
if protection and protection(p, "") then return false end
local target = S.name_for(canonical[n.name], now)
if n.name == target then return false end
-- Recheck loaded identity before swapping; no set_node metadata erasure.
local current = api.get_node_or_nil(p)
if not current or current.name ~= n.name then return false end
api.swap_node(p, {name=target, param1=current.param1, param2=current.param2})
return true
end
function S.register()
local names = {}
for kind, seasons in pairs(variants) do
local base_name = "hearth:"..kind
local base = (api.registered_nodes or {})[base_name]
if base then
names[#names+1] = base_name
for _, v in pairs(seasons) do
local def = {}
for k, value in pairs(base) do def[k] = value end
def.groups = {}
for k, value in pairs(base.groups or {}) do def.groups[k] = value end
def.groups.not_in_creative_inventory = 1
-- A stage may now be a bark cube rather than the old sculpted
-- tree. Recolour foliage/sprites only; never flatten end-grain
-- and bark faces or reintroduce an obsolete mesh on a cube.
if base.drawtype ~= "normal" then
def.tiles = {v.texture}
if base.inventory_image then def.inventory_image = v.texture end
if base.wield_image then def.wield_image = v.texture end
end
if base.drawtype == "mesh" and v.mesh then def.mesh = v.mesh end
def.drop = base.drop == nil and base_name or base.drop
if not api.registered_nodes[v.name] then api.register_node(v.name, def) end
names[#names+1] = v.name
end
end
end
api.register_lbm({name="hearth:seasonal_flora", label="Reconcile seasonal flora appearance",
nodenames=names, run_at_every_load=true, action=function(p) S.reconcile(p) end})
end
return S
end