Skip to content

Examples

ESTONlA edited this page Sep 8, 2026 · 3 revisions

Examples

Each example below is a separate main.lua. Use its own mod folder and a manifest with hostOnly: true. Reload from Pause > Lua Mods after edits.

Persistent Crew Catch Counter

local catches = tonumber(htf.get_data("catches", "0")) or 0

htf.on("fish_hooked", function(fish_name)
  catches = catches + 1
  htf.set_data("catches", tostring(catches))
  htf.log("Catch " .. catches .. ": " .. fish_name)
end)

htf.button("Show catches", function()
  htf.chat("Crew catches: " .. catches)
end)

Counts rod attachments observed by the host. The stored count survives reloading scripts and restarting the game. It is shared across worlds on this installation, not per player.

Boss Reward

htf.on("boss_killed", function(boss_name)
  if htf.money(100) then
    htf.chat("Defeated " .. boss_name .. ". Crew reward: $100.")
  end
end)

Mini-bosses also trigger this event. Environmental deaths may count. The callback does not receive killer information.

Host Announcement Command

htf.command("crew", function(args)
  local message = table.concat(args, " ")
  if message == "" then
    htf.log("Usage: /crew message")
    return
  end
  htf.chat(string.sub(message, 1, 120))
end)

The host types /crew Return to the boat. Command arguments are split at spaces; quotation marks do not group words. Keep the manifest display name short so the message and its prefix fit the 200-character limit.

Delayed Welcome

htf.on("player_joined", function(player_name, steam_id)
  htf.log("Joined: " .. player_name .. " (" .. steam_id .. ")")
  htf.after(2, function()
    if htf.is_host() then
      htf.chat("Welcome, " .. string.sub(player_name, 1, 40))
    end
  end)
end)

This is a broadcast, not a private message. The roster event includes the host and players already present on the first poll. It does not guarantee the named player is still connected two seconds later.

Repeating Notice

htf.every(300, function()
  htf.chat("Crew notice: check your equipment before leaving.")
end)

Timers use unscaled time and include time spent paused. Host-only timers wait until hosting. Repeating timers resume with one callback, without replaying all missed intervals. Reloading clears and recreates the timer.

Log Actual Fish Sales

htf.on("fish_sold", function(item, seller)
  local name = seller and seller.name or "Unknown"
  htf.log(name .. " sold " .. item.name .. " for $" .. item.worth)
end)

The seller is the last holder, not guaranteed to be the person who originally caught the fish. A shared-money grant does not trigger this event. The sale is already being processed; returning false cannot stop it.

Heal the Living Crew

htf.button("Heal crew", function()
  for _, player in ipairs(htf.players.list()) do
    if player.steam_id ~= "0" then
      htf.players.heal(player.steam_id, 100)
    end
  end
end)

This button restores up to 100 health through the host's native game API. It does not revive dead players. Client installations cannot use it to heal themselves on somebody else's server.

Track Per-Player Death Counts

htf.on("player_died", function(player)
  if not player or player.steam_id == "0" then return end
  local key = "deaths_" .. player.steam_id
  local deaths = (tonumber(htf.get_data(key, "0")) or 0) + 1
  htf.set_data(key, tostring(deaths))
end)

Counts only deaths observed while this script is loaded. Config data persists across this installation's worlds. Use a new per-session Lua table instead when you do not want persistent totals.

Island Ready Notification

htf.on("island_loaded", function(index)
  local spawn = htf.world.spawn_position()
  if spawn then
    htf.log("Island " .. index .. " spawn: " .. spawn.x .. ", " .. spawn.y .. ", " .. spawn.z)
  end
end)

Island indices are zero-based. The event observes host load completion, not every remote client's loading state. A newly queued island change may mean spawn_position() is already nil by delivery time.

Complete Crew Tools Example

The package's optional examples/crew-tools combines sale logging, saved death counts, heal/feed buttons, /luacrew, and /luareturn steam_id. The return command verifies a connected Steam ID before sending the current spawn position. Install the folder under mods with its included manifest. It requires framework 0.2.0 and does not run unless you install the example.

World Tools Example (0.3.0)

Copy examples/world-tools under BepInEx/plugins/HowToLua/mods/, reload, and host a spare test world. The script registers commands and buttons without changing gameplay on load.

  • /luacatalog writes definition IDs, names and spawn eligibility to the BepInEx log.
  • /luaspawn definition_id spawns one eligible item at the host's position offset by +3 world-X and +1 world-Y. It does not aim at the crosshair or check terrain clearance. Use an ID from the catalog, not a network instance ID.
  • Show boat status broadcasts the current motor index and radar state.
  • Save world requests a native save and reports acceptance/refusal in the log. Acceptance does not prove successful disk persistence.
  • Boat motor changes and NPC quest progression are logged while the example is active.

Spawning respects the framework's shared 8-per-second/128-live-item limits and excludes protected prefabs. Keep only one installed folder with this example's manifest ID.

Crew Bait Reward (0.3.0)

htf.command("baitreward", function(args)
  local id = tonumber(args[1])
  if not id or id ~= math.floor(id) or id < 1 or id > 255 then
    htf.log("Usage: /baitreward bait_id")
    return
  end
  for _, player in ipairs(htf.players.list()) do
    if player.steam_id ~= "0" then
      htf.inventory.give_bait(player.steam_id, id, 1)
    end
  end
end)

This is a free native grant, not a shop purchase. Unknown/unavailable bait IDs return false. Inspect htf.catalog.baits() for IDs; bait zero is the default and cannot be granted as stock. See Gameplay API before adding costs or progression conditions.

Clone this wiki locally