-- 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