Skip to content

Fishing

SwoftLang ships a complete fishing engine. A player right-clicks a fishing rod, a bobber flies out and settles on the water (or lava), a bite lands inside a configurable window, and reeling at the right moment pulls in a catch — an item that flies to the player, or a mob that spawns at the hook. What each rod pulls up is driven by declarative loot tables, and the whole lifecycle is exposed through four typed events.

swoftlang
fishing_loot "overworld_water" {
    medium: water
    catch item "COD" weight 40
    catch item "SALMON" weight 25
    catch item "PUFFERFISH" weight 10 message "<yellow>Careful, it puffs!"
}

Loot tables

A fishing_loot "<name>" { } block declares a weighted catch table. Each catch line adds one possible result; when a bite is landed against this table the engine picks one line at random, weighted by weight.

FieldMeaning
medium:which fluid this table fishes — water or lava
world:(optional) restrict the table to one world by name
catch item "<id>" weight <n>an item result — a vanilla material or a declared custom item
catch mob "<id>" weight <n>a mob result — spawns a declared custom mob at the hook
... message "<text>"(optional) a MiniMessage line sent to the fisher on that catch

Item ids resolve against your declared items first, then vanilla materials; mob ids must name a declared mob. An unknown id is a compile error, and the medium: enum is closed:

swoftlang
fishing_loot "ow" {
    medium: acid
    catch item "COD" weight 40
}
txt
med.sw:2:13: error: unknown fishing medium 'acid'; valid mediums: water, lava
    medium: acid
            ^

Table matching

Tables are matched by medium plus optional world. The most specific table wins — a table with a matching world: beats a medium-only table — and a built-in vanilla water table serves any bite that no declared table claims. Declaring a table for water in "world" and a broad lava table anywhere is enough to cover both fluids:

swoftlang
fishing_loot "overworld_water" {
    medium: water
    world: "world"
    catch item "COD" weight 40
    catch item "SALMON" weight 25
    catch item "PUFFERFISH" weight 10 message "<yellow>Careful, it puffs!"
}

fishing_loot "any_lava" {
    medium: lava
    catch item "MAGMA_CREAM" weight 8
    catch item "BLAZE_ROD" weight 2 message "<gold>Still warm."
}

The bite window

The delay between casting and a bite is configured in the server block, under a fishing { } section. min_bite and max_bite are duration literals; each cast rolls a bite time uniformly in that range.

swoftlang
server {
    motd: "Gone fishin'"
    fishing {
        min_bite: 4 seconds
        max_bite: 20 seconds
    }
}

With no fishing { } section, the engine uses its default window. Reel in before the bite and nothing happens; reel during the bite window and the catch is resolved.

The fishing events

Four typed events cover the lifecycle. Each binds player, and — like every curated event — exposes its properties both bare and under event.*.

EventCancellableBindingsFires
PlayerCastRodyesplayerbefore the bobber spawns — cancel to refuse the cast
FishBiteplayer, hook_location (Location)when the bobber dips
PlayerCatchFishyesplayer, caught_item (optional<Item>, rw), caught_mob (optional<Mob>)just before the catch is delivered
PlayerReelInplayerwhen the line comes back in

PlayerCastRod gates casting — a good place to enforce a permission, a region, or a cooldown:

swoftlang
event PlayerCastRod {
    execute {
        if player.world.time > 100000 {
            send "<gray>The fish are asleep." to player
            cancel event
        }
    }
}

PlayerCatchFish fires before delivery. caught_item is read-write, so you can swap the delivered stack; caught_mob is read-only; and cancel event (or halt) discards the catch entirely:

swoftlang
event PlayerCatchFish {
    execute {
        if event.caught_mob exists {
            send "<red>brace yourself..." to player
            halt
        }
        if event.caught_item exists {
            send "you landed ${event.caught_item.name}!" to player
            if event.caught_item.material is "minecraft:pufferfish" {
                set event.caught_item to item("COD")
                send "<gray>...swapped the puffer for a safer cod." to player
            }
        }
    }
}

A complete custom fishing setup

Declared custom item and mob, two loot tables, a bite window, and handlers for the whole lifecycle — the entire feature in one file:

swoftlang
item "glimmering_cod" {
    material: "COD"
    name: "<aqua>Glimmering Cod"
    rarity: rare
    lore {
        line "<gray>It hums faintly."
    }
}

mob "sea_walker" {
    type: "ZOMBIE"
    name: "<dark_aqua>Sea Walker"
    health: 40
    ai: melee
}

fishing_loot "overworld_water" {
    medium: water
    world: "world"
    catch item "COD" weight 40
    catch item "SALMON" weight 25
    catch item "glimmering_cod" weight 5 message "<aqua>It glimmers in your hands."
    catch mob "sea_walker" weight 3 message "<red>Something walked out of the water..."
    catch item "PUFFERFISH" weight 10 message "<yellow>Careful, it puffs!"
}

fishing_loot "any_lava" {
    medium: lava
    catch item "MAGMA_CREAM" weight 8
    catch item "BLAZE_ROD" weight 2 message "<gold>Still warm."
}

server {
    fishing {
        min_bite: 4 seconds
        max_bite: 20 seconds
    }
}

event PlayerCastRod {
    execute {
        if player.world.time > 100000 {
            send "<gray>The fish are asleep." to player
            cancel event
        }
    }
}

event FishBite {
    execute {
        send "bite at x=${hook_location.x} z=${hook_location.z}" to player
    }
}

event PlayerCatchFish {
    execute {
        if event.caught_mob exists {
            send "<red>brace yourself..." to player
            halt
        }
        if event.caught_item exists {
            send "you landed ${event.caught_item.name}!" to player
        }
    }
}

event PlayerReelIn {
    execute {
        send "<gray>line reeled in" to player
    }
}

Custom-item catches carry their identity all the way into the player's inventory, and mob catches spawn as full custom mobs — aggro, drops, and all.