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

163 lines
6.3 KiB
Lua
Executable File

-- Bounded, bait-funded riverside fishing. A trap can hold one catch at a time.
return function(hearth, api, construction)
local CATCH_SECONDS = 600
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
return value
end
local function safe_now()
local value = os.time()
if type(value) ~= "number" or value ~= value or value < 0 or value == math.huge or value == -math.huge then return 0 end
return math.floor(value)
end
local function node(pos)
local value = api.get_node_or_nil(pos)
if not value or value.name == "ignore" then return nil end
return value
end
local function has_water(pos)
for y = -1, 1 do for x = -3, 3 do for z = -3, 3 do
local nearby = node({x = pos.x + x, y = pos.y + y, z = pos.z + z})
if nearby and (nearby.name == "hearth:water_source" or nearby.name == "hearth:water_flowing") then
return true
end
end end end
return false
end
local function valid_site(pos)
local here = node(pos)
local below = node({x = pos.x, y = pos.y - 1, z = pos.z})
local here_def = here and api.registered_nodes[here.name]
return here_def and here_def.buildable_to == true and below
and (below.name == "hearth:sand" or below.name == "hearth:grass")
and has_water(pos)
end
local function clear_job(meta)
meta:set_string("state", "idle")
meta:set_string("ready_at", "0")
meta:set_int("catch_count", 0)
end
local function valid_catch(meta)
local count = meta:get_int("catch_count")
return count == 1 or count == 2
end
local function finish(pos, meta, completed_at)
local season = hearth.climate_at and hearth.climate_at(completed_at)
local count = season and season.season == "Spring" and 2 or 1
meta:set_string("state", "ready")
meta:set_string("ready_at", "0")
meta:set_int("catch_count", count)
meta:set_string("infotext", count == 2
and "River trap: spring run caught two fish; right-click empty-handed to collect."
or "River trap: catch ready; right-click empty-handed to collect.")
api.get_node_timer(pos):stop()
end
local function reconcile(pos, now)
local current = node(pos)
if not current or current.name ~= "hearth:fish_trap" then return false end
local meta = api.get_meta(pos)
local state = meta:get_string("state")
if state == "ready" then
if not valid_catch(meta) then clear_job(meta) end
return false
end
if state ~= "baited" then
clear_job(meta)
return false
end
local ready = finite(meta:get_string("ready_at"))
now = now or safe_now()
if not ready or ready <= 0 or ready > now + CATCH_SECONDS then
clear_job(meta)
meta:set_string("infotext", "River trap: ready for bait beside water.")
api.get_node_timer(pos):stop()
return false
end
if ready <= now then
finish(pos, meta, ready)
return false
end
api.get_node_timer(pos):start(math.max(1, ready - now))
meta:set_string("infotext", "River trap: baited; catch in " .. math.ceil(ready - now) .. " seconds.")
return true
end
local function plant_trap(stack, player, pointed)
if not pointed or pointed.type ~= "node" or not player or not player:is_player() then return stack end
local pos = pointed.above
if not valid_site(pos) then
hearth.notify(player, "Set a fish trap on firm riverbank within three blocks of water.")
return stack
end
if not construction.allowed(pos, player, "build") then return stack end
api.set_node(pos, {name = "hearth:fish_trap"})
local def = api.registered_nodes["hearth:fish_trap"]
if def and def.after_place_node then def.after_place_node(pos, player) end
stack:take_item(1)
return stack
end
local function collect(pos, player, meta)
local count = meta:get_int("catch_count")
if count ~= 1 and count ~= 2 then clear_job(meta); return end
local inventory = player:get_inventory()
local output = ItemStack("hearth:raw_fish " .. count)
if not inventory or not inventory:room_for_item("main", output) then
hearth.notify(player, "Make room for the catch before emptying the river trap.")
return
end
local leftover = inventory:add_item("main", output)
if leftover and not leftover:is_empty() then
meta:set_int("catch_count", leftover:get_count())
hearth.notify(player, "Some fish were collected; make room for the rest.")
return
end
clear_job(meta)
meta:set_string("infotext", "River trap: ready for bait beside water.")
end
local function rightclick(pos, _, player, stack)
if not player or not player:is_player() or not construction.allowed(pos, player, "use") then return stack end
local meta = api.get_meta(pos)
local state = meta:get_string("state")
if state == "ready" then
if stack:is_empty() then collect(pos, player, meta) else
hearth.notify(player, "Collect the waiting catch before baiting this trap again.")
end
return stack
end
if stack:get_name() ~= "hearth:fish_bait" then
local remaining = state == "baited"
and math.max(0, (finite(meta:get_string("ready_at")) or safe_now()) - safe_now()) or nil
hearth.notify(player, remaining and ("River trap: waiting " .. remaining .. " seconds.")
or "River trap: add fish bait to set it for ten minutes.")
return stack
end
if state == "baited" then
hearth.notify(player, "This trap is already baited; return after the catch is ready.")
return stack
end
if not has_water(pos) then
hearth.notify(player, "This trap is too far from water to catch fish.")
return stack
end
local now = safe_now()
meta:set_string("state", "baited")
meta:set_string("ready_at", tostring(now + CATCH_SECONDS))
meta:set_int("catch_count", 0)
meta:set_string("baiter", player:get_player_name())
api.get_node_timer(pos):start(CATCH_SECONDS)
meta:set_string("infotext", "River trap: baited; catch in " .. CATCH_SECONDS .. " seconds.")
stack:take_item(1)
return stack
end
local function on_timer(pos)
return reconcile(pos)
end
local function can_dig(pos, player)
return construction.allowed(pos, player, "dig")
and api.get_meta(pos):get_string("state") == "idle"
end
return {plant_trap = plant_trap, rightclick = rightclick, reconcile = reconcile,
on_timer = on_timer, can_dig = can_dig, has_water = has_water,
CATCH_SECONDS = CATCH_SECONDS}
end