Skip to content

Cockfight

hobbs_cockfight, current version 1.0.1

Overview

Cockfighting for your server. A rooster is an inventory item that a player names once and then owns for good, with its own level, record, trait and plumage. Fights happen in fixed pits that you place in the config, or in a wild pit a player stakes out anywhere in the open. The server runs the fight as the referee, both owners and everyone standing around see a HUD, hear the bell and the crowing, and can bet on the outcome before the bell rings. A bookmaker NPC at each fixed pit keeps the book (leaderboard, your roosters, statistics, rules), offers sparring matches against his own roosters and runs knockout tournaments with an entry fee and a pot.

There is no job to set up. Anyone who holds a rooster can fight, anyone standing at the pit can bet.

How it plays

  1. A player gets a rooster item and uses it. A dialog asks for a name, the name stays with this rooster forever. Right after that the rooster shows its trait ("a daredevil who never walks away from a fight") and, if plumage is on, the player picks one of three looks while the rooster struts in front of them.
  2. The player walks to a pit and uses the rooster again. It appears in its corner and waits for a rival. While it waits, the owner can recall it with G, or strap on rooster_spurs by using that item.
  3. A second player drops their rooster in. The betting window opens for 15 seconds, spectators near the pit press L, pick a side and enter a stake. Then a 5 second countdown, the bell, and the fight.
  4. The roosters circle, peck, dodge and clash. Every hit is decided on the server, the clients only play what they are told. The HUD shows both health bars, the pot and the odds. Spectators at a fixed pit get a small cheering crowd.
  5. One rooster goes down. The winner gets XP, a level maybe, a longer streak and a title in the book. The loser is injured for 30 minutes by default, or dies if you turn that on. Bets are paid out, a banner shows the winner, everyone cheers.
  6. No rival around? The owner uses the rooster away from any pit and stakes out a wild pit: a red ring on the ground that they move with the arrow keys and confirm with Enter. It shows as a blip on the map and lasts five minutes without a rival. Or they put the rooster in a fixed pit, talk to the bookmaker and pay $2 for a sparring match against one of his roosters at the same level.
  7. At the bookmaker anyone can call a tournament. Entrants use their rooster at the bookmaker and pay the entry fee into the pot. After the signup time the bookmaker pairs them round by round, calls each fight by name, and the last rooster standing takes the pot.

Requirements

Resource Note
vorp_core Characters, cash and gold, notifications
vorp_inventory The rooster and spurs items, their metadata and the usable item hooks
nss_libs Prompts, the bookmaker NPC, blips, database access, client and server events, the Discord webhook
oxmysql Database behind nss_libs

All four are hard dependencies, the resource does not start without them.

Installation

  1. Unpack the resource into resources and keep the folder name hobbs_cockfight.
  2. Run sql/install.sql on your database. It inserts the two items rooster and rooster_spurs into items and creates the tables hobbs_cockfight_roosters (one row per named rooster) and hobbs_cockfight_owed (money owed to players who were offline at a payout). The tables are also created by the resource on first start, so if the SQL file only fails on the CREATE TABLE lines, you are fine.
  3. Put item images named rooster.png and rooster_spurs.png into your inventory's image folder.
  4. Add it to server.cfg after its dependencies:

    ensure oxmysql
    ensure vorp_core
    ensure vorp_inventory
    ensure nss_libs
    ensure hobbs_cockfight
    
  5. Start the server. The console prints [hobbs_cockfight] ready, item "rooster" registered. and, with spurs enabled, a second line for rooster_spurs. If one of those lines is missing, vorp_inventory was not up when the resource started.

  6. Open config/config.lua, set Config.Locale and replace the pit in Config.Spots with your own (see below). Give players a rooster through a shop or an admin command of your choice.

The config is downloaded by every client. Only server_config.lua stays on the server, that is why the Discord webhook lives there and nowhere else.

Configuration

Everything a player can see or touch lives in config/config.lua. Texts live in locales.lua, the Discord settings in server_config.lua.

Language and notifications

Config.Locale = 'en'   -- "en" or "de"
Config.Debug  = false

Config.Notify = {
    dict     = 'generic_textures',
    duration = 5000,
    icons = { rooster = 'tick', tourney = 'tick', spurs = 'tick', money = 'tick' },
}

English and German ship with the script. To add a language copy a block in locales.lua, translate it and set Config.Locale to its key. The texts are written in character, the bookmaker talks back to the player, so keep that tone if you translate.

Notifications go through vorp_core (NotifyLeft). The four icon keys are the four notification topics, the titles are notify_rooster, notify_money, notify_spurs and notify_tourney in the locale.

Config.Debug prints extra lines to the console, adds the /hahnfx test command for the blood effect and lets one player put both of their roosters into the same pit for testing. Leave it off on a live server.

The rooster item

Config.ItemName     = 'rooster'
Config.RoosterModel = 'a_c_rooster_01'

Config.Coat = {
    enabled = true,
    count   = 3,       -- outfit presets of the model
    keys = {
        prev    = 0xA65EBAB4, -- arrow left
        next    = 0xDEB34313, -- arrow right
        confirm = 0xC7B5340A, -- enter
        cancel  = 0x4AF4D473, -- delete, pick later
    },
}

Config.NameRules = { min = 3, max = 24 }

The item is never used up. The first use names the rooster and writes a roosterId into the item's metadata, every use after that sends it into a pit. The item label and description are rewritten after every fight so the inventory shows name, title, level, record, streak and state ("INJURED (12 min)" or "FALLEN").

A rooster belongs to the character who uses it. If the item is traded, the next owner who uses it takes it over with everything it has earned, the book shows the last owner's name.

Plumage is one of the model's outfit presets. The owner picks it once, after naming, and it stays. Set enabled = false and the plumage step is skipped, every rooster then gets a random look each time it spawns.

Keys

Config.Keys = {
    recall = 0x760A9C6F, -- G, recall a waiting rooster
    bet    = 0x80F28E95, -- L, open the betting bar
    book   = 0xCEFD9220, -- E, open the book at the bookmaker
    spar   = 0xE30CD707, -- R, sparring match at the bookmaker
}

Values are RedM control hashes. The keys for placing a wild pit are in Config.Arena.keys, the keys for picking plumage in Config.Coat.keys.

Names above the roosters

Config.Labels = {
    enabled = true,
    range   = 10.0,
    zOffset = 0.55,
}

Shows the rooster's name over its head for everyone within range. Costs a little on the client while roosters stand in a pit, so lower the range or turn it off on a busy server.

Fixed pits

Config.DefaultSpotRadius = 1.5

Config.Spots = {
    {
        id     = 'vanhorn_pit',
        label  = 'Van Horn pit',
        center = vector3(2982.08, 584.83, 44.19),
        radius = Config.DefaultSpotRadius,
        corners = {
            vector4(2981.89, 584.03, 44.24, 345.77),
            vector4(2982.4, 586.07, 44.12, 165.82),
        },
        blip = { enabled = false, sprite = 'blip_ambient_hunting', color = 'RED', scale = 0.2 },
        npc = {
            enabled  = true,
            model    = 'A_M_M_FiveFingerFilletPlayers_01',
            coords   = vector4(2980.31, 589.92, 44.47, 181.72),
            scenario = 'WORLD_HUMAN_SMOKE',
        },
    },
}

Config.SpotProximityPadding = 2.0
Key Meaning
id Unique name of the pit, used internally and in the Discord log
label Shown in the HUD and the book
center Middle of the pit. Using the rooster within radius of it puts the rooster in
corners The two spots where the roosters appear, w is the heading so they face each other
blip Map blip of the pit, off by default
npc The bookmaker: book, sparring and tournaments. Set enabled = false for a pit without one

The one pit that ships is an example in Van Horn, replace it with your own. A pit without a bookmaker still works for normal fights and bets, but nobody can spar or call a tournament there.

SpotProximityPadding is slack the server adds to every distance check because it sees player positions a little late. Raise it if players at the edge of the pit get "There is no cockfight pit here".

Tip

Put the two corners about two metres apart and the bookmaker a few steps outside the ring, otherwise the crowd (see below) has no room and the bookmaker stands in the fight.

Wild pits

Config.Arena = {
    enabled       = true,
    previewOffset = 2.2,    -- metres in front of the player at the start
    offsetMin     = 1.2,
    offsetMax     = 5.0,
    offsetStep    = 0.2,
    maxSlope      = 0.55,   -- max height difference in metres along the ring

    minDistanceToSpot  = 25.0,  -- to a fixed pit
    minDistanceToArena = 20.0,  -- to another wild pit
    maxActive          = 6,     -- wild pits at once, server wide
    onePerPlayer       = true,
    cooldownSec        = 60,    -- between two placements by the same player
    waitTimeoutSec     = 300,   -- no opponent = the pit expires

    claimExtra         = 0.0,   -- extra buffer while somebody places, negative = no reservation
    claimTtlSec        = 120,

    blip = { enabled = true, sprite = 'blip_ambient_hunting', color = 'RED', scale = 0.15 },
    ring = {
        drawDistance = 30.0,
        height       = 0.35,
        zOffset      = -0.05,
        colorIdle    = { 196, 30, 30, 60 },
        colorFight   = { 200, 168, 75, 75 },
    },
    keys = {
        confirm = 0xC7B5340A,  -- enter
        cancel  = 0x4AF4D473,  -- delete
        further = 0x6319DB71,  -- arrow up
        nearer  = 0x05CA7C52,  -- arrow down
    },
}

Using the rooster away from every fixed pit opens placement mode: a ring on the ground that the player pushes further away or closer with the arrow keys and confirms with Enter. The server checks the distance to fixed pits and other wild pits, the slope, the global cap and the cooldown before it accepts the spot. The pit lives until the fight is over, the owner leaves, or nobody shows up for waitTimeoutSec.

While a player is placing, the area around them is reserved so two players cannot stake out the same patch at once. claimExtra widens that reservation, a negative value turns it off.

Set enabled = false and fights happen at the fixed pits only. The player then sees "Fights happen at the fixed pits only." and the rules tab in the book drops the wild pit paragraph.

Countdown and fight

Config.Countdown = { seconds = 5 }   -- after the betting window, until the bell

Config.Fight = {
    lightDamageMin = 6,
    lightDamageMax = 12,
    heavyDamageMin = 16,
    heavyDamageMax = 28,
    heavyHitChance = 0.28,
    safetyMaxMs    = 180000, -- longer fights end, the rooster with less HP loses
    despawnDelayMs = 10000,  -- winner and loser stay this long after the end

    clashChance    = 0.58,   -- share of beats that are a real clash, the rest is choreography
    arenaRadius    = 3.8,    -- size of the fight and of a wild pit
    runSpeed       = 3.2,
    circleSpeed    = 1.9,

    desperationBelow     = 25,   -- below this HP the rooster speeds up and crows once, 0 = off
    desperationSpeedMult = 1.35,
}

Both roosters start with 100 HP. The server runs the fight in beats, a beat is either a clash (one attacks, the other dodges, damage only if the peck lands) or choreography (circling, closing in, taunting). The block has many more timing values for the chase, the dodge, the knockdown and the recovery. They are tuned to the animations, so change the damage and chance values first and leave the millisecond values alone unless a fight looks wrong to you.

A fight that runs longer than safetyMaxMs ends on time, the rooster with less HP loses. The Discord log and the banner then say "on time" instead of "by knockout".

How much strength matters

Config.Outcome = {
    powerInfluence  = 0.60,   -- share of the attacks the stronger one gets, 0 = even, 1 = by strength
    upsetClamp      = 0.28,   -- the attack share stays within [0.28, 0.72]
    damageInfluence = 0.25,
    damageRatioMin  = 0.80,
    damageRatioMax  = 1.35,
}

Strength comes from level and spurs (see Progression). powerInfluence decides how much of the attack share the stronger rooster gets, upsetClamp keeps a weak rooster in the fight so an upset stays possible. I kept the clamp fairly wide because fights nobody can lose are no fun to bet on. Set powerInfluence = 0 for coin flip fights where level is only a title.

Loser, cooldowns, same owner

Config.LoserOutcome     = CF.LOSER.INJURED   -- INJURED, PERMADEATH or CHANCE
Config.LoserDeathChance = 0.5                -- only with CHANCE

Config.Cooldowns = {
    injuredSeconds = 1800,  -- loser
    restSeconds    = 180,   -- winner
}

Config.SameOwnerAllowed = false
Outcome What happens to the loser
INJURED Cannot fight for injuredSeconds, then it is back
PERMADEATH The rooster is marked fallen. It stays in the inventory with "FALLEN" in its description but never fights again
CHANCE Permadeath with LoserDeathChance, otherwise injured

The winner rests for restSeconds. A player disconnecting mid fight loses by forfeit, with the same outcome as a normal loss.

SameOwnerAllowed = false stops a player from putting two of their own roosters into one pit to farm wins or bets.

Progression, size and titles

Config.Progression = {
    enabled     = true,
    xpPerWin    = 3,
    xpPerKill   = 2,    -- extra for a knockout win
    xpPerLoss   = 1,
    xpPerLevel  = 20,   -- level = 1 + floor(xp / xpPerLevel)
    maxLevel    = 31,
    powerBase     = 100,
    powerPerLevel = 12,
}

Config.PedScale = {
    default = 0.8,
    steps = {
        { min = 3,  scale = 0.9 },
        { min = 6,  scale = 1.0 },
        -- ...
        { min = 30, scale = 1.6 },
    },
}

Config.Titles = {
    { minWins = 0 },   -- rank_0 in locales.lua: Greenhorn
    { minWins = 5 },   -- Pit fighter
    { minWins = 12 },  -- Daredevil
    { minWins = 25 },  -- Veteran of the pit
    { minWins = 45 },  -- Champion
    { minWins = 75 },  -- Living legend
    { minWins = 99 },  -- Immortal
}

Config.Streak = {
    milestoneEvery = 2,   -- every X wins in a row an extra notification, 0 = off
    bonusXpEvery   = 1,
    bonusXp        = 2,
}

Every fight gives XP, wins give more, a knockout gives extra. Level raises the rooster's power (powerBase + powerPerLevel * (level - 1)) and its size in the pit, so an old hand visibly struts wider. Titles come from total wins, the names are rank_<minWins> in locales.lua, so if you add a step at 150 wins you also add rank_150 to every language.

With default values a rooster needs 20 XP per level, that is about six or seven wins per level early on. Lower xpPerLevel if your players fight rarely and you want them to see progress.

Sparring

Config.Sparring = {
    enabled     = true,
    fee         = 2,      -- per sparring match, 0 = free
    currency    = 0,      -- 0 cash, 1 gold
    xp          = 1,
    maxLevel    = 15,     -- sparring takes a rooster up to this level only
    perDay      = 10,     -- sparring matches per rooster and day, 0 = no cap
    restSeconds = 180,
    npcRange    = 1.5,    -- distance to the bookmaker (plus SpotProximityPadding)
}

A rooster waiting alone in a fixed pit can spar: the owner presses R at the bookmaker, pays the fee and the bookmaker puts one of his own roosters in at the same level. No bets, no win or loss on the record, no injury, only xp and a short rest. Sparring stops at maxLevel, after that only real fights take the rooster further. The bookmaker's name and his rooster names (spar_owner_name, spar_rooster_names) are in locales.lua.

The fee is simply taken from the player, it does not go anywhere.

Tournaments

Config.Tournament = {
    enabled       = true,
    sizes         = { 4, 6, 8, 10, 12, 14, 16, 18, 20 },   -- choices when calling one
    minEntries    = 4,
    entryFee      = 10,
    currency      = 0,            -- 0 cash, 1 gold
    houseMargin   = 0.10,         -- share of the pot the bookmaker keeps
    payout        = { 0.7, 0.3 }, -- share of the rest for first and second
    signupMinutes = 10,
    breakSec      = 20,           -- between the call and the fight
    presenceRange = 25.0,         -- this close to the pit when called
    announceRange = 400.0,        -- who hears the announcement, entrants always
    cooldownMin   = 10,           -- no new tournament at this pit for this long
    npcRange      = 1.5,
}

Anyone standing at a bookmaker can call a tournament from the book's Tournament tab, picking a size from sizes. Calling is free. Entrants use their rooster at the bookmaker during the signup time, pay entryFee into the pot, one rooster per person. With fewer than minEntries the tournament is off and every fee is refunded.

Rounds are knockout. Each pairing is announced with a breakSec countdown, both owners have to be within presenceRange of the pit when called, whoever is missing is out (walkover). A rooster with no rival in a round advances without a fight. Tournament fights run like normal fights, with a betting window for the spectators. At the end the bookmaker keeps houseMargin of the pot, the winner gets 70 percent of the rest and the runner up 30 percent. The pit is reserved for the tournament while it runs.

Spurs

Config.Spurs = {
    enabled     = true,
    item        = 'rooster_spurs',
    damageBonus = 0.30,
    uses        = 10,
}

The owner uses the spurs item while their rooster waits in the pit. The rooster then attacks more often and hits damageBonus harder for that fight. One pair lasts uses fights, the item's description counts down, then it breaks and is removed. Bettors see a spurs mark in the betting bar, so nobody is tricked. Spurs have to go on before the second rooster enters, "Too late for spurs" otherwise.

Traits

Config.Traits = {
    enabled = true,
    list = {
        { key = 'daredevil', initiative = 0.06 },  -- attacks this much more often
        { key = 'nimble',    heavyTaken = 0.5 },   -- factor on the chance to take a heavy hit
        { key = 'tough',     damageTaken = 0.88 }, -- factor on the damage taken
        { key = 'heart',     initiativeLow = 0.12 }, -- initiative, but only below Fight.desperationBelow HP
        { key = 'beak',      damageDealt = 1.10 }, -- factor on the damage dealt
    },
}

Every rooster gets one trait at random when it is named and keeps it. Names and texts are trait_<key>_name, trait_<key>_desc and trait_<key>_reveal in locales.lua. Add a trait by adding a line here and the three texts in every language. The trait shows in the book and in the betting bar.

Betting

Config.Betting = {
    enabled     = true,
    windowSec   = 15,    -- betting window once two roosters stand, then the countdown
    promptRange = 4.0,
    currency    = 0,     -- 0 cash, 1 gold
    min         = 5,
    max         = 100,   -- 0 = only the cash counts
    houseMargin = 0.08,  -- share of the losers' pot the house keeps
    quickBets   = { 5, 10, 20, 50 },
}

Config.Hud = {
    range = 15.0,   -- spectators this close see the fight HUD
}

Pool betting. All stakes go into a pot, one bet per character and fight, the stake is taken when the bet is placed. After the fight the losers' pot minus houseMargin is shared among the winners in proportion to their stakes, on top of their own stake back. With fewer than two bettors, or when nobody bet on the winner, every stake comes back. A cancelled or forfeited fight refunds everything.

The house margin is simply removed from the game, it is not paid to a society or an account. Owners may bet on their own fight.

The betting bar opens with L for anyone within promptRange of the pit while the window is open. The HUD, the banner, the bell and the pot updates reach everyone within Hud.range.

Sounds, crowd, animations, blood

Config.Sound = {
    enabled = true,
    bell  = { file = 'bell.mp3',  radius = 15.0, volume = 0.2, falloff = 1.5, durationMs = 4000 },
    crow  = { files = { 'crow1.mp3', 'crow2.mp3', 'crow3.mp3', 'crow4.mp3', 'crow5.mp3' }, radius = 20.0, volume = 0.4, falloff = 1.5, durationMs = 2500 },
    cheer = { file = 'cheer.mp3', radius = 15.0, volume = 0.3, falloff = 1.5, durationMs = 5000 },
}

Config.Crowd = {
    enabled    = true,
    count      = 5,
    radius     = 5.2,    -- distance to the middle of the pit
    avoidNpc   = 1.8,    -- room around the bookmaker
    models = { 'a_m_m_valtownfolk_01', 'a_m_m_valtownfolk_02', --[[ ... ]] },
    -- stand, kinds and cheer: the animations the spectators play
}

Config.Particles = {
    enabled = true,
    dict    = 'core',
    onHit   = { effect = 'blood_entry_head_fatal', scale = 0.6, z = 0.0 },
    onHeavy = { effect = 'blood_entry_critical',   scale = 1.0, z = 0.0 },
}

Sounds are the files in html/sounds, played on the client by distance. The crowd is five local peds (no network traffic) that stand around a fixed pit while betting or fighting and clap, cheer and point, then cheer once more when the fight is decided. A pit can bring its own spectator points with crowd = { points = { vector4(...), ... } } in its Config.Spots entry, or crowd = false for none. Config.Anims holds the rooster animations and is tuned to the sounds, leave it.

Turn Particles.enabled off if you do not want blood.

Rate limits and the book

Config.RateLimit = {
    default  = 8,
    windowMs = 3000,
    bet      = 6,
    name     = 4,
    -- ...
}

Config.Leaderboard = {
    command   = 'cockfightbook',
    limit     = 25,
    scoreWin  = 2,   -- score = wins * scoreWin + kills * scoreKill
    scoreKill = 1,
}

Rate limits cap how many requests a player may send per bucket within windowMs, a guard against spam from modified clients. The book's leaderboard shows the top limit roosters by score.

Discord (server_config.lua)

Config.Discord = {
    enabled   = false,
    webhook   = '',
    name      = 'Cockfight',
    avatar    = '',   -- optional avatar url of the bot
    thumbnail = '',   -- optional thumbnail of the embed
}

With a webhook the server posts an embed after every fight (winner, loser, knockout or time, level, title, streak, pot, pit) and a message when a tournament is called and when it is won. The texts are the discord_* keys in locales.lua. Sparring matches are not logged.

Items

Item What it does
rooster The fighting rooster. First use names it, every use after that at a pit sends it in, away from the pits it stakes out a wild pit. Never used up. Stack limit 5 in install.sql
rooster_spurs Used on your own waiting rooster. More attacks and 30 percent more damage for 10 fights, then the pair breaks and is removed

Both are item_standard, usable, and need an image in your inventory.

Commands and keys

Command What it does
/cockfightbook Opens the book on the leaderboard tab from anywhere. The Tournament tab only works when the book was opened at a bookmaker. Name set by Config.Leaderboard.command
/hahnfx [effect] [scale] [dict] Test the blood effect in front of you. Only exists with Config.Debug = true

Keys in the pit:

Key Where Action
G Standing at your waiting rooster Recall it
L Within promptRange of a pit while bets are open Open the betting bar
Esc In the betting bar Close it
E At the bookmaker Open the book
R At the bookmaker, your rooster waiting alone Sparring match

Staking out a wild pit:

Key Action
Arrow Up / Arrow Down Push the ring further away or closer
Enter Stake out the pit
Delete Cancel

Picking plumage:

Key Action
Arrow Left / Arrow Right Previous or next plumage
Enter Keep this one
Delete Decide later

In the book:

Key Action
Q / E Switch tabs
Enter Select the highlighted row
Esc Back, or close the book

Exports and events

The script offers no exports and no events for other resources. All traffic between client and server runs over nss_libs channels that belong to this resource.

FAQ

The rooster does nothing when I use it. Check the console for ready, item "rooster" registered. at start. If it is missing, vorp_inventory started after hobbs_cockfight, fix the server.cfg order. Also make sure the item in your database is called exactly what Config.ItemName says and is marked usable.

I use the rooster at the pit and get "There is no cockfight pit here". The player has to be within radius of the pit's center, plus SpotProximityPadding. Raise one of them a little, or move center. With Config.Arena.enabled = true a use away from every pit starts placement mode instead of that message.

How do I add a pit? Copy the entry in Config.Spots, give it a unique id, set center, the two corners facing each other and, if you want sparring and tournaments there, the bookmaker's coords. Restart the resource. Blips are off by default, turn them on per pit under blip.

Where does the house margin go? Nowhere. The 8 percent of the losers' pot and the 10 percent of a tournament pot are taken out of the game, the same for the sparring fee. There is no society or account to receive them.

A bettor logged off before the fight ended. Is the money lost? No. Winnings and refunds for a character who is offline are written to hobbs_cockfight_owed and paid the next time that character is selected, with a notification from the bookmaker.

What happens on a resource restart? Running fights are aborted and every open bet is refunded, wild pits are removed, running tournaments are cancelled with all entry fees refunded. Roosters in a pit go back to being normal items, nothing is lost. The rooster records live in the database and survive.

What happens when a player disconnects? Mid fight the other rooster wins by forfeit, bets are refunded, the leaver's rooster takes a normal loss. A rooster that was only waiting is taken out of the pit. A wild pit whose owner leaves is removed, a tournament entrant who leaves during signup is taken off the list, their fee stays in the pot and only comes back if the tournament is cancelled. During the rounds a leaver loses their next fight by walkover.

Can a player bet on their own rooster? Yes. The only limits are one bet per character and fight, the min and max stake, and standing close enough to the pit.

Can I give or sell a rooster to someone else? Yes, it is a normal item. The next character who uses it becomes the owner, the record, level and trait stay with the rooster.

The loser died but the item is still in the inventory. That is how PERMADEATH works: the rooster is marked fallen, its description says "FALLEN" and it can never fight again. Players can throw it away or keep it.

Does the script cost performance? The fight logic runs on the server, the clients only play animations. The crowd is local peds without network traffic, the name labels and the ring drawing only run while a rooster stands in a pit near the player. Rate limits stop clients from flooding the server. Most of the cost is inside Hud.range of an active pit.

Changelog

1.0.1

First release.