163 lines
6.3 KiB
Lua
Executable File
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
|