Custom buffs
A resource can create its own buffs, with one of the game's status icons, a timer, stat changes and an optional tick. The server
runs the buff, and the player sees it with its timer in their status list. Buffs.register has two forms.
| Form | Best for |
|---|---|
Buffs.register(id, definition) | full buffs with a GUID, stacks and modifiers, including the game's own stats |
Buffs.register(definition) | a simple buff owned by your resource, with an onTick callback |
Full form
const { Buffs } = kcds.players; const { Events } = kcds.core; const blessing = Buffs.register('myres:blessing', { name: 'Blessing', description: 'A priest blessed you.', visual: 'well_rested', duration: 120, tickInterval: 10, stackable: true, maxStacks: 3, modifiers: { stamina: 20, health: { operation: 'add', value: 5 } }, }); Events.on('playerCustomBuffApply', (player, buff) => { console.info(`${buff.displayName} on ${player.nickname}`); }); Events.on('playerCustomBuffTick', (player, buff) => { player.addStat('health', 1); }); Events.on('playerCustomBuffRemove', (player, buff, reason) => { console.info(`${buff.name}: ${reason}`); }); player.addBuff(blessing); // the GUID, or use the id
It returns the buff's GUID, which comes from the id and so is the same at each start. Registering the same definition under the same id
again returns the same GUID. A different definition throws Custom buff myres:blessing is already registered with another definition.
| Field | Meaning |
|---|---|
id | 1 to 128 characters of A-Z a-z 0-9 : _ . -; not the name of a game buff |
name | required; the text the player sees, up to 256 bytes |
description | optional text, up to 4096 bytes |
visual | required; the game buff, by name or GUID, whose status icon it borrows |
duration | seconds, above 0 and up to 7 days; -1 (default) has no end |
tickInterval | 0.1 to 86400 seconds; 0 (default) has no ticks |
stackable | whether a player can have several instances; default false |
maxStacks | 1 to 16 (default 1): how many instances a stackable buff keeps |
refresh | at the limit, true (default) replaces the oldest; false ignores the new one |
modifiers | { stat: points } or { stat: { operation, value } }; Buffs.modifiers lists the stats |
An unknown key, wrong value, or visual that is not a game buff throws Invalid custom buff: ....
Modifiers
| Operation | Effect |
|---|---|
'add' (a number) | adds the points |
'multiply' | multiplies the stat by value, 0 or more; 1.5 adds half again |
'set' | sets the stat to value |
Modifiers work on three groups of stats.
Direct stats: health, stamina, exhaust and hunger. An instance works out its change from the player's last reported value when
it lands, and takes back exactly that when it ends. stamina: { operation: 'multiply', value: 1.5 } on 50 stamina adds 25, then later takes
25 away. Three stacks of stamina: 20 give 60, and leave nothing behind.
Rates per second: healthRegeneration gives health, hungerDrain takes hunger, and energyDrain adds exhaust. These accept a number of 0
or more, or 'add', and apply each second while an instance lasts. What they changed stays changed.
The game's own stats: maxStamina, carryCapacity, staminaRegeneration, runSpeed, charisma, strength, agility, vitality,
speech, and skills by track name, such as weapon_sword, fencing and stealth. These go through the game's buff system. The game applies
them once, however many stacks there are, while the buff is on the status list. Derived values follow, so more strength lets a player carry
more.
A custom buff cannot change maximum health or armor rating. The game has no buff modifier that raises them. Its buff table never changes them,
and its mhs and bba codes moved neither stat when tested.
const { Buffs } = kcds.players; Buffs.register('myres:giant', { name: 'Giant', visual: 'well_rested', duration: 600, modifiers: { maxStamina: { operation: 'multiply', value: 1.5 }, carryCapacity: 100, charisma: 5 }, }); Buffs.register('myres:marathon', { name: 'Marathon', visual: 'well_rested', duration: 600, modifiers: { hungerDrain: 0.2, energyDrain: 0.1 }, }); Buffs.register('myres:feast', { name: 'Feast', visual: 'well_rested', duration: 300, modifiers: { hunger: { operation: 'set', value: 100 }, healthRegeneration: 0.5 }, });
Events and reading
playerCustomBuffApplyfires for each instance.playerCustomBuffTickfires for each instance and interval.playerCustomBuffRemovegives a reason:expired,server(fromremoveBuff, which takes all instances),refreshed(a new stack replaced the oldest) ordisconnect.addBuffreturnsfalsewhen the buff is at its limit and does not refresh.- In
player.buffs, each instance has its own entry withcustom: true,displayNameanddescription. Game buffs havecustom: falseandnullfor those texts.
The buff these events receive is what Buffs.find returns:
{
"name": "myres:blessing",
"class": "custom",
"aiTag": null,
"duration": 120,
"custom": true,
"displayName": "Blessing",
"description": "A priest blessed you."
}Simple form
const { Buffs } = kcds.players; const warmth = Buffs.register({ name: 'warmth', label: 'Warm', icon: 'hangover', duration: 120, effects: { stats: { stamina: 20 }, tickStats: { health: 1 } }, tickSeconds: 5, onTick: (player, buff) => console.info(player.nickname, buff.name), }); kcds.core.Events.on('playerBuffRemoved', (player, buff, reason) => { if (buff === 'warmth') { kcds.core.Chat.sendToAll(`The warmth wore off (${reason})`); } }); player.addBuff(warmth); // or 'warmth' player.hasBuff(warmth); player.removeBuff(warmth);
| Field | Meaning |
|---|---|
name | 1 to 64 characters of A-Z a-z 0-9 : _ . -; not a game buff or one already registered |
icon | the name of a game buff, as Buffs.find spells it, whose status icon it uses |
label | the text in the status list; name by default |
duration | seconds, above 0 and up to 7 days; -1 has no end |
effects.stats | stat changes, as for player.addStat, applied each time it is added |
effects.tickStats | stat changes on each tick |
tickSeconds | the tick interval, at least 1 second; needs onTick or effects.tickStats |
onTick(player, buff) | called every tickSeconds while the buff lasts; missed ticks are not made up |
It returns a frozen description, with name, icon, label, duration and tickSeconds, which player.addBuff, removeBuff and hasBuff take
in place of the name. The buff belongs to your resource and goes when it stops, including from players.
How it behaves:
addBuffapplies at once, sohasBuffistruestraight away, andplayerBuffAddedfires with the sourceserver.- Adding a buff the player already has restarts its timer and applies its effects again, with no events.
- When it ends,
playerBuffRemovedfires with the reasonexpired, orserverforremoveBuff. - In
player.buffsit hasclass: 'custom'andsource: 'server'. - It ends when the player disconnects and does not carry over.
To decide the game's own effects on the server, see Buffs.claim in Buffs.