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