Ruined
This commit is contained in:
+77
-37
@@ -1,7 +1,8 @@
|
||||
-- Bounded, restart-safe ecology for perennial forage, saplings and seasonal crops.
|
||||
return function(hearth, api, construction)
|
||||
local DAY, MAX_CATCHUP = 86400, 30 * 86400
|
||||
local CROP_STEP, CROP_MATURE_HOURS = 21600, 60
|
||||
local DAY, YEAR = 86400, 365.2425*86400
|
||||
-- Unidentified game grain, not a researched named cereal cultivar.
|
||||
local CROP_STEP, CROP_MATURE_HOURS, CROP_SAMPLES = 21600, 120*24, 366
|
||||
local function finite(value)
|
||||
value = tonumber(value)
|
||||
if not value or value ~= value or value == math.huge or value == -math.huge then return nil end
|
||||
@@ -28,25 +29,26 @@ return function(hearth, api, construction)
|
||||
planted, now = finite(planted), finite(now)
|
||||
if not planted or not now or planted < 0 or now < 0 then return 0 end
|
||||
planted, now = math.floor(planted), math.floor(now)
|
||||
now = math.min(math.floor(now), planted + MAX_CATCHUP)
|
||||
if planted <= 0 or now <= planted then return 0 end
|
||||
local progress, cursor = 0, planted
|
||||
while cursor < now and progress < CROP_MATURE_HOURS do
|
||||
local finish = math.min(now, cursor + CROP_STEP)
|
||||
local sample = cursor + math.floor((finish - cursor) / 2)
|
||||
-- Bounded midpoint quadrature covers the ENTIRE calendar interval. Long
|
||||
-- gaps coarsen samples, never silently discard all age after day thirty.
|
||||
local duration=now-planted
|
||||
local samples=math.min(CROP_SAMPLES,math.max(1,math.ceil(duration/DAY)))
|
||||
local progress=0
|
||||
for i=1,samples do
|
||||
local sample=planted+math.floor(duration*(i-.5)/samples)
|
||||
local climate = hearth.climate_at and hearth.climate_at(sample)
|
||||
local temperature = finite(climate and climate.temperature) or 12
|
||||
local rate = temperature < 3 and 0 or (temperature < 10 and 0.5 or 1)
|
||||
progress = progress + (finish - cursor) / 3600 * rate
|
||||
cursor = finish
|
||||
progress = progress + duration/samples/3600*rate
|
||||
end
|
||||
return math.min(CROP_MATURE_HOURS, progress)
|
||||
return math.min(CROP_MATURE_HOURS,progress,duration/3600)
|
||||
end
|
||||
local function crop_stage(planted, now)
|
||||
local growth = climate_progress(planted, now)
|
||||
if growth >= CROP_MATURE_HOURS then return 4 end
|
||||
if growth >= 36 then return 3 end
|
||||
if growth >= 12 then return 2 end
|
||||
if growth >= 72*24 then return 3 end
|
||||
if growth >= 24*24 then return 2 end
|
||||
return 1
|
||||
end
|
||||
local function update_crop(pos)
|
||||
@@ -54,8 +56,14 @@ return function(hearth, api, construction)
|
||||
if not current or not current.name:match("^hearth:crop_[1234]$") then return false end
|
||||
local meta, now = api.get_meta(pos), safe_now()
|
||||
local planted = tonumber(meta:get_string("planted_at")) or 0
|
||||
if planted <= 0 or planted > now then meta:set_string("planted_at", tostring(now)); planted = now end
|
||||
if planted <= 0 or planted > now then return true end
|
||||
local observed=finite(meta:get_string("crop_last_observed_at")) or planted
|
||||
if observed>now then return true end
|
||||
meta:set_string("crop_last_observed_at",tostring(now))
|
||||
local stage = crop_stage(planted, now)
|
||||
-- Quadrature refinement is approximate, so an established stage never
|
||||
-- regresses when a different sample partition straddles a cold front.
|
||||
stage=math.max(stage,tonumber(current.name:match("(%d)$")))
|
||||
local target = "hearth:crop_" .. stage
|
||||
if current.name ~= target then api.swap_node(pos, {name = target, param2 = current.param2}) end
|
||||
return stage < 4
|
||||
@@ -98,28 +106,37 @@ return function(hearth, api, construction)
|
||||
api.get_meta(pos):set_string("player_placed", "1")
|
||||
end
|
||||
local function harvest_berries(pos, player)
|
||||
if not construction.allowed(pos, player, "use") then return end
|
||||
if not construction.allowed(pos, player, "use") then return false end
|
||||
local n = node(pos)
|
||||
if not n then return end
|
||||
if not n or (n.name~="hearth:berry_bush" and n.name~="hearth:berry_bush_bare") then return false end
|
||||
if n.name == "hearth:berry_bush_bare" then
|
||||
hearth.notify(player, "The berry bush is regrowing; return after one real day.")
|
||||
return
|
||||
hearth.notify(player, "The berry bush has spent this crop; check a future Summer after its annual rest.")
|
||||
return false
|
||||
end
|
||||
local meta,now=api.get_meta(pos),safe_now()
|
||||
local previous=meta:get_string("ready_at")
|
||||
local due=previous=="" and 0 or finite(previous)
|
||||
if not due or due<0 or due>now or now>9007199254740991-YEAR then return false end
|
||||
local inv = player:get_inventory()
|
||||
local output = ItemStack("hearth:berries 3")
|
||||
if not inv or not inv:room_for_item("main", output) then
|
||||
hearth.notify(player, "Make inventory space before picking these berries.")
|
||||
return
|
||||
return false
|
||||
end
|
||||
local requested = output:get_count()
|
||||
-- Shared durable reservation before inventory callbacks, including
|
||||
-- reentrant picking. A rejected transfer restores exactly the old field.
|
||||
local ready = now + YEAR
|
||||
meta:set_string("ready_at",tostring(ready))
|
||||
local leftover = inv:add_item("main", output)
|
||||
if not leftover or leftover:get_count() >= requested then return end
|
||||
api.set_node(pos, {name = "hearth:berry_bush_bare", param2 = n.param2})
|
||||
local ready = safe_now() + DAY
|
||||
api.get_meta(pos):set_string("ready_at", tostring(ready))
|
||||
api.get_meta(pos):set_string("infotext", "Berry bush: regrowing (one real day)")
|
||||
local count=leftover and finite(leftover:get_count())
|
||||
if not count or count<0 or count>requested then return false end -- uncertain delivery stays locked
|
||||
if count==requested then meta:set_string("ready_at",previous); return false end
|
||||
api.swap_node(pos, {name = "hearth:berry_bush_bare", param2 = n.param2})
|
||||
meta:set_string("infotext", "Berry bush: annual rest; next eligible Summer")
|
||||
api.get_node_timer(pos):start(DAY)
|
||||
if hearth.invalidate_environment then hearth.invalidate_environment(pos) end
|
||||
return true
|
||||
end
|
||||
local function harvest_seasonal(pos, player)
|
||||
if not construction.allowed(pos, player, "use") then return false end
|
||||
@@ -129,25 +146,30 @@ return function(hearth, api, construction)
|
||||
local item = ({spring_greens_patch="hearth:spring_greens", autumn_mushrooms_patch="hearth:autumn_mushrooms"})[kind]
|
||||
if not item then return false end
|
||||
local now, meta = safe_now(), api.get_meta(pos)
|
||||
local ready = finite(meta:get_string("succ_harvest_ready_at")) or 0
|
||||
if not seasons.available(kind, now) or ready > now then
|
||||
local previous=meta:get_string("succ_harvest_ready_at")
|
||||
local ready = previous=="" and 0 or finite(previous)
|
||||
if not ready or ready<0 or not seasons.available(kind, now) or ready > now then
|
||||
hearth.notify(player, "This patch rests outside its season or until one real day after picking.")
|
||||
return false
|
||||
end
|
||||
local inv, output = player:get_inventory(), ItemStack(item)
|
||||
if not inv or not inv:room_for_item("main", output) then return false end
|
||||
-- One item only: no partial multi-item harvest can lose or duplicate food.
|
||||
local leftover = inv:add_item("main", output)
|
||||
if not leftover or leftover:get_count() ~= 0 then return false end
|
||||
meta:set_string("succ_harvest_ready_at", tostring(now + DAY))
|
||||
local leftover = inv:add_item("main", output)
|
||||
local count=leftover and finite(leftover:get_count())
|
||||
if count==1 then meta:set_string("succ_harvest_ready_at",previous); return false end
|
||||
if count~=0 then return false end
|
||||
return true
|
||||
end
|
||||
local function refresh_bush(pos)
|
||||
local n = node(pos)
|
||||
if not n or n.name ~= "hearth:berry_bush_bare" then return false end
|
||||
local meta, now = api.get_meta(pos), safe_now()
|
||||
local ready = tonumber(meta:get_string("ready_at")) or 0
|
||||
if ready <= now then
|
||||
local ready = finite(meta:get_string("ready_at"))
|
||||
if not ready or ready<0 then return false end
|
||||
local climate=hearth.climate_at and hearth.climate_at(now)
|
||||
if ready <= now and climate and climate.season=="Summer" then
|
||||
api.swap_node(pos, {name = "hearth:berry_bush", param2 = n.param2})
|
||||
meta:set_string("ready_at", "0"); meta:set_string("infotext", "")
|
||||
api.get_node_timer(pos):stop()
|
||||
@@ -158,8 +180,7 @@ return function(hearth, api, construction)
|
||||
local grow_sapling
|
||||
local function reconcile_sapling(pos)
|
||||
if grow_sapling(pos) then
|
||||
local planted = tonumber(api.get_meta(pos):get_string("planted_at")) or safe_now()
|
||||
api.get_node_timer(pos):start(math.max(60, math.min(DAY, planted + 3 * DAY - safe_now())))
|
||||
api.get_node_timer(pos):start(DAY)
|
||||
end
|
||||
end
|
||||
local function reconcile_bush(pos)
|
||||
@@ -187,17 +208,36 @@ return function(hearth, api, construction)
|
||||
if not n or n.name ~= "hearth:sapling" then return false end
|
||||
local meta, now = api.get_meta(pos), safe_now()
|
||||
local planted = tonumber(meta:get_string("planted_at")) or 0
|
||||
if planted <= 0 or planted > now then meta:set_string("planted_at", tostring(now)); planted = now end
|
||||
if planted <= 0 or planted > now then return true end
|
||||
local previous=finite(meta:get_string("succ_last_observed_at")) or planted
|
||||
if previous>now then return true end
|
||||
meta:set_string("succ_birth_at",tostring(planted))
|
||||
meta:set_string("succ_last_observed_at",tostring(now))
|
||||
meta:set_string("succ_lifecycle_version","1")
|
||||
meta:set_string("succ_lifecycle_origin","player_planted")
|
||||
meta:set_string("succ_lifecycle_profile","generic_tree")
|
||||
meta:set_string("succ_lifecycle_policy","generic_1y_young_10y_mature_unknown_lifespan")
|
||||
local owner = meta:get_string("planter")
|
||||
if now - planted < 3 * DAY or not tree_clear(pos, owner) then return true end
|
||||
for y = 0, 3 do api.set_node({x=pos.x,y=pos.y+y,z=pos.z},{name="hearth:log"}) end
|
||||
if now - planted < YEAR or not tree_clear(pos, owner) then return true end
|
||||
local function plant_wood(q,name)
|
||||
api.set_node(q,{name=name}); api.get_meta(q):set_string("player_placed","1")
|
||||
end
|
||||
for y = 0, 3 do plant_wood({x=pos.x,y=pos.y+y,z=pos.z},"hearth:log") end
|
||||
for y = 3, 5 do for x = -2, 2 do for z = -2, 2 do
|
||||
local radius = y == 5 and 1 or 2
|
||||
if math.abs(x) + math.abs(z) <= radius and not (x == 0 and z == 0 and y < 5) then
|
||||
local p = {x=pos.x+x,y=pos.y+y,z=pos.z+z}
|
||||
if api.get_node(p).name == "air" then api.set_node(p, {name="hearth:leaves"}) end
|
||||
if api.get_node(p).name == "air" then plant_wood(p,"hearth:leaves") end
|
||||
end
|
||||
end end end
|
||||
-- These are player trees, not a newly generated natural forest. Mark
|
||||
-- every written cell so later worldgen adoption cannot erase provenance.
|
||||
local root=api.get_meta(pos)
|
||||
root:set_string("planted_at",tostring(planted)); root:set_string("planter",owner)
|
||||
root:set_string("succ_birth_at",tostring(planted)); root:set_string("succ_last_observed_at",tostring(now))
|
||||
root:set_string("succ_lifecycle_version","1"); root:set_string("succ_lifecycle_origin","player_planted")
|
||||
root:set_string("succ_lifecycle_profile","generic_tree")
|
||||
root:set_string("succ_lifecycle_policy","generic_1y_young_10y_mature_unknown_lifespan")
|
||||
return false
|
||||
end
|
||||
local function plant_sapling(stack, player, pointed)
|
||||
@@ -207,7 +247,7 @@ return function(hearth, api, construction)
|
||||
api.set_node(pos, {name = "hearth:sapling"})
|
||||
local meta = api.get_meta(pos)
|
||||
meta:set_string("planted_at", tostring(safe_now())); meta:set_string("planter", player:get_player_name())
|
||||
api.get_node_timer(pos):start(3 * DAY)
|
||||
api.get_node_timer(pos):start(DAY)
|
||||
stack:take_item(1)
|
||||
return stack
|
||||
end
|
||||
@@ -226,5 +266,5 @@ return function(hearth, api, construction)
|
||||
refresh_bush = refresh_bush, reconcile_bush = reconcile_bush,
|
||||
reconcile_crop = reconcile_crop, plant_sapling = plant_sapling,
|
||||
grow_sapling = grow_sapling, reconcile_sapling = reconcile_sapling,
|
||||
MAX_CATCHUP = MAX_CATCHUP, CROP_MATURE_HOURS = CROP_MATURE_HOURS}
|
||||
CROP_SAMPLE_LIMIT = CROP_SAMPLES, CROP_MATURE_DAYS = 120, CROP_MATURE_HOURS = CROP_MATURE_HOURS}
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user