Lots of content
This commit is contained in:
Executable
+258
@@ -0,0 +1,258 @@
|
||||
-- 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
|
||||
Reference in New Issue
Block a user