Skip to content

Weapon Attributes & The Buff System

TheOnly8Z edited this page Jul 25, 2022 · 1 revision

Almost every variable on ArcCW weapons can be modified by attachments or external hooks. These are known as "buffs" and allow attachments to change the performance and behavior of the weapon in almost any way imaginable.

Basic usage

A buff is any variable that may exist on the weapon. For example, SWEP.Damage = 100 implies the existence of a buff called Damage.

Buffs can be modified in three ways: Override, Add and Mult, processed in that order. There is also a special buff type, Hook, which are functions that run under certain conditions. To modify a buff, add one of these modifiers as a prefix, followed by an underscore, e.g:

-- example_attachment.lua

att.Name = "My Attachment"

att.Mult_Damage = 1.5
att.Add_ClipSize = 1
att.Override_Ammo = "ar2"

-- Do 25% more damage on a headshot
att.Hook_BulletHit = function(wep, data)
    if SERVER and data.tr and data.tr.HitGroup == HITGROUP_HEAD then
        data.damage = data.damage * 1.25
    end
end

Buff modifiers primarily exist in four places:

  • Within an attachment table, applied when the attachment is equipped.
  • Within the ToggleAtt table of an attachment, applied when the option is selected.
  • Within the firemode table, applied when the firemode is selected.
  • Within an AttachmentElements table, applied when the element is enabled (not recommended).

Hooks may also exist within the weapon table.

Getting a buff value

You may call one of the following functions:

-- "smart" function that will process all three buff types for you.
wep:GetBuff("Damage")

-- get only the override value. second parameter is the default value (if the override does not exist)
wep:GetBuff_Override("Override_Damage", wep.Damage)

-- get the multiplier. defaults to 1 if no modifiers exist
wep:GetBuff_Mult("Mult_Damage")

-- get the additional value. defaults to 0 if no modifiers exist
wep:GetBuff_Add("Add_Damage")

-- call a hook with the provided data parameter. the return value will be whatever the first hook returns.
wep:GetBuff_Hook("Hook_GetCapacity", 30)

Priority

Hook and Override buffs may set a priority value to determine which function/value is used first. Priority defaults to 1. To specify a priority, add a suffix _Priority and specify a value, e.g:

att.Override_Firemodes = {
  {
    Mode = 1,
  }
}
att.Override_Firemodes_Priority = 0.5 -- lower than 1, so another attachment may be able to take priority over us

No matter how low the priority value is, it will always override the default value.

Examples

-- Weapon can fire faster in burst mode compared to automatic.
SWEP.Firemodes = {
  {
    Mode = 2,
  },
  {
    Mode = -3,
    Mult_RPM = 1.5,
  }
}

-- Change the trivia text when an attachment element is active.
SWEP.AttachmentElements = {
  ["9mm"] = {
    Override_Trivia_Calibre = "9x19mm",
    VMBodygroups = {{ind = 1, bg = 1}},
  }
}

Hooks

Hooks are processed similar to GMod hooks - they run until the first function returns a non-nil value. All hooks have one or two parameters, the first one being the weapon entity and the second one being some parameter specified by the hook. You may find a list of all hooks in default.lua.

Importantly, tables are pass-by-reference in Lua, so if the data parameter passed in is a table, modifications you make will affect the original value - this is useful to modify values without interfering with other hooks.

Consider these two hooks (assume they are in separate attachments):

-- In attachment foo: 125% damage on a headshot
att.Hook_BulletHit = function(wep, data)
    if SERVER and data.tr and data.tr.HitGroup == HITGROUP_HEAD then
        data.damage = data.damage * 1.25
    end
    -- do not return anything
end

-- In attachment bar: 200% damage against NPCs
att.Hook_BulletHit = function(wep, data)
    if SERVER and data.tr and IsValid(data.tr.Entity) and data.tr.Entity:IsNPC() then
        data.damage = data.damage * 2
    end
    -- do not return anything
end

If both of these are equipped on one weapon, we can expect 250% damage when hitting a headshot on an NPC.

Variable Hooks

Every buff modifier can be programmatically set using a hook with a specific prefix. These hooks can be called like a regular ArcCW hook, or using GMod's hook library. For example, for the buff Damage:

  • Override: O_Hook_Override_Damage
  • Mult: M_Hook_Mult_Damage
  • Add: A_Hook_Add_Damage
-- In an attachment file:

-- Damage is overridden to be equal to the remaining clip + 20.
-- For override hooks, return a table with a key, "current", containing the new value.
att.O_Hook_Override_Damage = function(wep, data)
  return {current = 20 + wep:Clip1()}
end
-- Priority works against other hooks of the same name but not against the buff itself.
att.O_Hook_Override_Damage_Priority = 2 

-- Deal up to 100% more damage based on the owner's health percentage.
-- For mult hooks, modify data.mult but do not return a value.
att.M_Hook_Mult_Damage = function(wep, data)
  data.mult = (data.mult or 1) * (1 + wep:GetOwner():Health() / wep:GetOwner():GetMaxHealth())
end

-- Deal extra damage equal to the owner's armor value.
-- For add hooks, modify data.add but do not return a value.
att.A_Hook_Add_Damage = function(wep, data)
  data.add = (data.add or 0) + wep:GetOwner():Armor()
end

-- In some other lua file:

-- Admin abuse, applies to ALL ArcCW weapons
hook.Add("M_Hook_Mult_Damage", function(wep, data)
  if wep:GetOwner():IsPlayer() and wep:GetOwner():IsAdmin() then
    data.mult = data.mult * 9999
  end
end)

Technical Details

  • Buffs are not networked. Developers are expected to either put buffs in shared realms (attachments, weapon table) or to not use a buff in a realm it is not defined in.
    • Similarly, do not expect all hooks to be run in a shared realm. This is usually not clearly annotated, so you may need to consult the source code where the hook is called, or specify the realm in an if statement.
  • Buffs are cached during SWEP:AdjustAtts(). This does not cache the actual value, but all non-cached buffs are assumed to never change.
  • Buff prefixes like Override_ are not technically required. You can look for buffs with arbitrary names, they just won't work with SWEP:GetBuff().