259 lines
9.7 KiB
Lua
Executable File
259 lines
9.7 KiB
Lua
Executable File
-- Real-time hearth processes; construction supplies the shared access check.
|
|
return function(hearth, api, construction)
|
|
local allowed = construction.allowed
|
|
local MAX_FUEL = 600
|
|
-- Exact names only: building timber, charges and arbitrary tree groups are not fuel.
|
|
local FUEL = { ["hearth:sticks"] = 45, ["hearth:log"] = 180, ["hearth:charcoal"] = 90,
|
|
["hearth:coal"] = 240, ["hearth:oak_log"] = 180, ["hearth:birch_log"] = 180,
|
|
["hearth:pine_log"] = 180, ["hearth:willow_log"] = 180, ["hearth:alder_log"] = 180,
|
|
["hearth:beech_log"] = 180, ["hearth:aspen_log"] = 180, ["hearth:rowan_log"] = 180,
|
|
["hearth:hazel_log"] = 180, ["hearth:spruce_log"] = 180 }
|
|
local JOBS = {
|
|
["hearth:raw_food"] = {kind = "cook", duration = 6, cost = 20,
|
|
result = "hearth:cooked_food"},
|
|
["hearth:raw_fish"] = {kind = "roast", duration = 8, cost = 20,
|
|
result = "hearth:cooked_fish"},
|
|
["hearth:water_raw"] = {kind = "boil", duration = 4, cost = 10,
|
|
result = "hearth:water_clean"},
|
|
["hearth:smelt_charge"] = {kind = "smelt", duration = 60, cost = 30,
|
|
result = "hearth:iron_ingot"},
|
|
["hearth:copper_charge"] = {kind = "smelt_copper", duration = 45, cost = 30,
|
|
result = "hearth:copper_ingot"},
|
|
["hearth:dried_food_charge"] = {kind = "dry", duration = 300, cost = 30,
|
|
result = "hearth:dried_food"},
|
|
["hearth:clay_jar_charge"] = {kind = "fire", duration = 120, cost = 30,
|
|
result = "hearth:clay_jar"},
|
|
["hearth:charcoal_charge"] = {kind = "char", duration = 60, cost = 30,
|
|
result = "hearth:charcoal"},
|
|
["hearth:fish_drying_charge"] = {kind = "dry_fish", duration = 300, cost = 30,
|
|
result = "hearth:dried_fish"},
|
|
}
|
|
local RESULTS = { ["hearth:cooked_food"] = true, ["hearth:water_clean"] = true,
|
|
["hearth:iron_ingot"] = true, ["hearth:copper_ingot"] = true,
|
|
["hearth:dried_food"] = true, ["hearth:clay_jar"] = true,
|
|
["hearth:charcoal"] = true, ["hearth:cooked_fish"] = true, ["hearth:dried_fish"] = true }
|
|
local function is_fire(pos)
|
|
local name = api.get_node(pos).name
|
|
return name == "hearth:fire" or name == "hearth:fire_lit"
|
|
end
|
|
local function fuel_left(pos, now)
|
|
local deadline = tonumber(api.get_meta(pos):get_string("fuel_until")) or 0
|
|
if deadline ~= deadline or deadline == math.huge or deadline == -math.huge then return 0 end
|
|
return math.max(0, math.min(MAX_FUEL, deadline - now))
|
|
end
|
|
local function job_deadline(meta)
|
|
local raw = meta:get_string("job_until")
|
|
if raw == "" then return 0 end
|
|
local value = tonumber(raw)
|
|
if not value or value ~= value or value == math.huge or value == -math.huge then return nil end
|
|
return math.floor(value)
|
|
end
|
|
|
|
local function valid_job(meta)
|
|
local kind = meta:get_string("job_kind")
|
|
if kind == "" then return nil end
|
|
local until_time, cost = job_deadline(meta), meta:get_int("job_cost")
|
|
local result = meta:get_string("job_result")
|
|
if not until_time or until_time <= 0 then return nil end
|
|
for _, job in pairs(JOBS) do
|
|
if kind == job.kind and cost == job.cost and result == job.result then
|
|
return {kind = kind, until_time = until_time, cost = cost, result = result}
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function clear_job(meta)
|
|
meta:set_string("job_kind", "")
|
|
meta:set_string("job_until", "0")
|
|
meta:set_int("job_cost", 0)
|
|
end
|
|
|
|
local function pending_output(meta)
|
|
local result = meta:get_string("job_result")
|
|
if meta:get_string("job_kind") == "" and job_deadline(meta) == 0
|
|
and meta:get_int("job_cost") == 0 and RESULTS[result] then
|
|
return result
|
|
end
|
|
return ""
|
|
end
|
|
|
|
local function schedule_timer(pos, remaining, now)
|
|
local meta = api.get_meta(pos)
|
|
local wait
|
|
if remaining > 0 then wait = remaining end
|
|
local job = valid_job(meta)
|
|
if job then
|
|
local job_wait = job.until_time - now
|
|
if job_wait <= 0 then job_wait = 0.1 end
|
|
wait = wait and math.min(wait, job_wait) or job_wait
|
|
end
|
|
local timer = api.get_node_timer(pos)
|
|
if wait then
|
|
timer:start(math.max(0.1, wait))
|
|
else
|
|
timer:stop()
|
|
end
|
|
end
|
|
|
|
local function refresh_fire(pos, remaining, now)
|
|
local meta = api.get_meta(pos)
|
|
now = now or os.time()
|
|
remaining = remaining == nil and fuel_left(pos, now) or remaining
|
|
if remaining <= 0 then
|
|
remaining = 0
|
|
meta:set_string("fuel_until", "0")
|
|
meta:set_int("lit", 0)
|
|
end
|
|
local job = valid_job(meta)
|
|
local output = pending_output(meta)
|
|
local lit = meta:get_int("lit") == 1 and remaining > 0
|
|
meta:set_int("lit", lit and 1 or 0)
|
|
if output ~= "" and not job then
|
|
meta:set_string("infotext", "Last Hearth: finished " .. output .. " ready to collect")
|
|
elseif job then
|
|
local verb = ({cook = "cooking", roast = "roasting fish", boil = "boiling", smelt = "smelting iron",
|
|
smelt_copper = "smelting copper",
|
|
dry = "drying food", dry_fish = "drying fish", fire = "firing", char = "making charcoal"})[job.kind] or "processing"
|
|
meta:set_string("infotext", "Last Hearth: " .. verb .. "; " .. math.max(0,
|
|
math.ceil(job.until_time - now)) .. "s remaining")
|
|
elseif lit then
|
|
meta:set_string("infotext", "Last Hearth: lit; " .. math.ceil(remaining)
|
|
.. "s fuel; feed sticks/logs/charcoal/coal or process a charge")
|
|
elseif remaining > 0 then
|
|
meta:set_string("infotext", "Fueled but cold hearth: ignite with a firestarter")
|
|
else
|
|
meta:set_string("infotext", "Cold hearth: feed dry sticks or a log")
|
|
end
|
|
local node = api.get_node(pos)
|
|
local name = lit and "hearth:fire_lit" or "hearth:fire"
|
|
if node.name ~= name then
|
|
api.swap_node(pos, {name = name, param2 = node.param2})
|
|
end
|
|
schedule_timer(pos, remaining, now)
|
|
end
|
|
|
|
local function finish_job(_, meta, job)
|
|
clear_job(meta) -- Clear the reservation before committing its one output.
|
|
meta:set_string("job_result", job.result)
|
|
end
|
|
|
|
local function reconcile_job(pos, now)
|
|
local meta = api.get_meta(pos)
|
|
local kind = meta:get_string("job_kind")
|
|
if kind == "" then
|
|
local output = meta:get_string("job_result")
|
|
if (output ~= "" and pending_output(meta) == "")
|
|
or job_deadline(meta) ~= 0 or meta:get_int("job_cost") ~= 0 then
|
|
clear_job(meta)
|
|
meta:set_string("job_result", "")
|
|
end
|
|
return
|
|
end
|
|
local job = valid_job(meta)
|
|
if not job or job.until_time <= 0 then
|
|
clear_job(meta)
|
|
meta:set_string("job_result", "")
|
|
return
|
|
end
|
|
if job.until_time <= now then
|
|
finish_job(pos, meta, job)
|
|
end
|
|
end
|
|
|
|
local function fire_tick(pos)
|
|
if not is_fire(pos) then return false end
|
|
local now = os.time()
|
|
reconcile_job(pos, now)
|
|
refresh_fire(pos, fuel_left(pos, now), now)
|
|
return false -- refresh_fire schedules only the next job/fuel deadline.
|
|
end
|
|
|
|
local function use_fire(pos, _, player, stack)
|
|
if not is_fire(pos) or not allowed(pos, player, "use") then return stack end
|
|
local now = os.time()
|
|
local remaining = fuel_left(pos, now)
|
|
local meta = api.get_meta(pos)
|
|
if stack:is_empty() then
|
|
local result = pending_output(meta)
|
|
if result ~= "" then
|
|
local inv = player:get_inventory()
|
|
local output = ItemStack(result)
|
|
if inv and inv:room_for_item("main", output) then
|
|
local leftover = inv:add_item("main", output)
|
|
if leftover:is_empty() then meta:set_string("job_result", "") end
|
|
else
|
|
hearth.notify(player, "Make inventory space to collect the hearth output.")
|
|
end
|
|
end
|
|
refresh_fire(pos, remaining, now)
|
|
return stack
|
|
end
|
|
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
|
|
meta:set_string("fuel_until", tostring(now + remaining))
|
|
else
|
|
hearth.notify(player, "The hearth has no room for that much fuel yet.")
|
|
end
|
|
else
|
|
local job = JOBS[item]
|
|
if job then
|
|
if meta:get_string("job_kind") ~= "" or meta:get_string("job_result") ~= "" then
|
|
hearth.notify(player, "Collect the finished hearth output first.")
|
|
elseif meta:get_int("lit") ~= 1 then
|
|
hearth.notify(player, "Ignite the fueled hearth before cooking or boiling.")
|
|
elseif remaining < job.duration + job.cost then
|
|
hearth.notify(player, "Add more fuel to reserve the full hearth process.")
|
|
else
|
|
meta:set_string("job_kind", job.kind)
|
|
meta:set_string("job_until", tostring(now + job.duration))
|
|
meta:set_int("job_cost", job.cost)
|
|
meta:set_string("job_result", job.result)
|
|
-- Reserve heat cost at queue time; elapsed processing burns
|
|
-- naturally against the absolute UTC deadline. Paused servers do
|
|
-- not grant free fuel, but a completed offline job remains queued.
|
|
remaining = remaining - job.cost
|
|
meta:set_string("fuel_until", tostring(now + remaining))
|
|
-- Start the timer before consuming input; queued output is persistent.
|
|
schedule_timer(pos, remaining, now)
|
|
stack:take_item(1)
|
|
end
|
|
end
|
|
end
|
|
refresh_fire(pos, remaining, now)
|
|
return stack
|
|
end
|
|
|
|
local function ignite_fire(pos, player)
|
|
if not is_fire(pos) or not allowed(pos, player, "use") then return false end
|
|
local meta = api.get_meta(pos)
|
|
local now = os.time()
|
|
local remaining = fuel_left(pos, now)
|
|
if meta:get_int("lit") == 1 or remaining <= 0 then
|
|
refresh_fire(pos, remaining, now)
|
|
return false
|
|
end
|
|
meta:set_int("lit", 1)
|
|
refresh_fire(pos, remaining, now)
|
|
if api.sound_play then
|
|
api.sound_play("hearth_fire", {pos = pos, gain = 0.5, max_hear_distance = 16})
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function use_firestarter(stack, player, pointed)
|
|
if pointed and pointed.type == "node" and ignite_fire(pointed.under, player) then
|
|
stack:add_wear(math.ceil(65535 / 20))
|
|
end
|
|
return stack
|
|
end
|
|
|
|
return {use_fire = use_fire, fire_tick = fire_tick,
|
|
refresh_fire = refresh_fire, fuel_left = fuel_left, ignite_fire = ignite_fire,
|
|
use_firestarter = use_firestarter}
|
|
end
|