Weapon Stats-Modifing Attachments – Arma Reforger
Overview
Some attachments, like Bayonets and Silencers, change vital internal stats of a weapon they are attached to. These changes are maintained and controlled by the weapon stats manager. The basic variant is SCR_WeaponStatsManagerComponent and needs to be put on a weapon in order to make it "modifiable". The class can be overridden/inherited by a mod in order to introduce more moddable weapon attributes.
There are a number of rules that need to be followed to make this work. I'll go over them below. Every attachment is its own entity, and must have an InventoryItemComponent. The latter has a list of custom attributes which we use as a basis for modification. If an attachment adds muzzles, like an UGL, it also must have its own SCR_WeaponStatsManagerComponent or subclass thereof.
Weapon Stats that can be modified
There are a number of stats on a weapon that can be modified without resorting to modding. We divide these into two groups:
- Muzzle-Specific: Attributes that override the specifics of a muzzle MUST have an attachment slot that is a child of the MuzzleComponent, so that the system can easily assign the right muzzle to the attachment. That means that if a weapon has more than one muzzle, or there are extra muzzles on attachments (like a Grenade Launcher), a muzzle specific attachment only modifies the muzzle it is attached to. For example, a silencer makes the rifle more quiet, but not the UGL.
- Global: Attributes that override global parameters of the weapon, for example obstruction length.
Any attachment can have any number of global attributes it modifies, but only muzzle attachments can have extra muzzle specific attributes.
The following table lists all stats that can currently be modified, their data type, and whether they are global or muzzle specific:
Muzzle Velocity Coefficient | Muzzle | float | A modifier for the muzzle velocity of the bullet being fired. This is a floating point value that is multiplied with the existing muzzle velocity coefficient as defined by the weapon. A value of 1.0 does not change anything, any value greater than one will make the bullet faster, while a lower value will make it slower. Most suppressors add a bit of muzzle velocity because of the extra barrel length |
Dispersion Factor | Muzzle | float | A modifier for the muzzle dispersion. Normally, a longer barrel means less dispersion. A value of 1.0 is neutral, while a value between 0 and 1 will make the weapon shoot more precise. This value scales the dispersion diameter specified in the muzzle component, not the range. |
Linear Recoil Factors | Muzzle | vector | A three element vector that is used to scale the linear recoil curve of a muzzle. 1.0 again is a neutral value, while values between 0 and 1 make the weapon have less linear recoil in that direction, and values greater than 1 will make the recoil stronger. The values in the vector are applied to x, y and z direction, respectively |
Angular Recoil Factors | Muzzle | vector | As above, but with angular instead of linear recoil |
Turn Factor Recoil Factors | Muzzle | vector | As above but for turn recoil curves. The Z value is stored but since the Z value is usually set to zero in a weapon, it will likely not have any inmpact. |
Override Shot Sound | Muzzle | boolean | A boolean that specifies that the weapon is silenced. A silenced weapon will trigger SOUND_SUPPRESSED_SHOT sound event instead of the normal SOUND_SHOT. It can also be used to modify AI reaction. |
Extra Obstruction Length | Global | float | A floating point number (in meters) that defines how much longer the weapon becomes through this attachment. This is used for weapon obstruction, meaning that a mounted suppressor will trigger obstruction earlier than a normal weapon.
See also Hierarchies below |
Muzzle Effect Override | Muzzle | boolean | If set, muzzle effects of the weapon may be disabled and replaced by those on the attachment. Most notably, effects that have the "Override by Attachment" flag set will not play if the attachment sets this flag. Instead, it will use the effects on the attachment. Some effects like case ejection and ejection port smoke normally come without the override flag set, and those will always be played.
See also Hierarchies below |
Is Bayonet | Global | bool | If this flag is set, then the melee attacks with this weapon will use a different animation and sound effects to make it look like the player is stabbing with a bayonet instead of the buttstock hit. |
Melee Damage | Global | float | This is a floating point multiplier that is applied to the weapon damage when this attachment is fitted to the weapon. Typically this would be used for bayonets, but it isn't restricted to it. |
Melee Range | Global | float | A factor for the weapon range when using melee attacks. It's a multiplier applied to the existing melee range of the weapon |
Melee Accuracy | Global | float | A factor for weapon accuracy with melee attacks. The factor is multiplied by the existing melee accuracy of the weapon, meaning that positive values lower than 1.0 make the attack more accurate (the accuracy specifies the radius of a sphere that is used to "track" for hit objects). |
Hierarchies
Attachments can be mounted on other attachments. Certain rules apply here:
- If an attachment is attached to a muzzle, all subsequent attachments that are attached to this specific attachment inherit the muzzle they are assigned. A usage example is given below.
- Rules apply to the values depending on hierarchy. Most factors are multiplied along the chain of attachments, while booleans usually only require a single attachment in the chain to have this flag set for it to take effect.
- Overridden muzzle effects are subject to the same rules as the original muzzle's effect, meaning that if an attachment further down the chain overrides muzzle effects, then muzzle effects on all attachments before that may be skipped if the have the "override by attachment" flag.
- Obstruction Length is a special case - for this, the longest chain of attachments is used, measured in the given obstruction length (not number of attachments)
Let's take as an example a weapon that has its barrels as attachments, You would have a generic 16 inch barrel, and a 14 inch barrel. Both barrels have an attachment point at the tip to mount a flash hider/muzzle brake or suppressor.
- The ammo would be attuned to the 16 inch barrel, making its muzzle velocity factor 1.0.
- A muzzle brake would not change that, so it doesn't override the value
- A suppressor would make the bullet 5 percent faster.
That means the 16 inch barrel has a factor of 1.0 , and the suppressor has 1.05.
The 14 inch barrel is shorter so it's muzzle velocity factor is 0.9.
This gives a total velocity factor of:
- 1.0 with the 16 inch barrel
- 0.9 with the 14 inch barrel.
- 1.05 with a silenced 16 inch barrel
- 0.945 with a silenced 14 inch barrel
Likewise, both barrels would have a muzzle effect attached that has an exaggerated muzzle flash which is set to be overridden by attachment. A typical basket-type muzzle brake would have an effect with the typical "bloom".-effect. Finally a silencer would have its own, subdued effect. The muzzle brake must be removed (at least on the M4/M16) in order to attach a silencer, so any attachment would always override the barrel's effect.
Attaching/Detaching Hierarchies
An entity getting attached may, by itself, already have other attachments on it, and it may do so without having its own stats manager. For example, consider a weapon whose barrel is an attachment itself (Attachment resp. attaching doesn't necessarily be a user action, prefabs will also get "attached" to the weapon in terms of code path). That barrel might come with a muzzle brake already attached, and both might modify certain aspects of the weapon, like muzzle velocity, dispersion etc. There will definitely be a difference in muzzle flash between a naked barrel and a barrel with a muzzle brake or flash hider.
When a hierarchy is attached (runtime or during initialization), the attachment is performed from the weapon outwards. This means that the barrel is handled first, followed by the muzzle brake. This is so that the muzzle break "overlays" any changes done through the barrel, since they will be more relevant.
Detaching works the other way around. The attachment furthest down the chain is detached first (in the example, the muzzle brake), then the barrel.
It is of course sill possible to only detach the muzzle brake and replace it with a flash hider or suppressor.
How to Configure an Attachment
SCR_WeaponAttachmentAttributes
As part of its custom attribute list, the typical InventoryItemComponent has a WeaponAttachmentAttributes entry that, by default, only lists an Attachment Type class that is used to find out if an attachment fits a certain slot. SCR_WeaponAttachmentAttributes is a "smart" subclass of that, but in itself doesn't add functionality. That is done by subclasses. These are the currently defined subclasses:
SCR_WeaponAttachmentSuppressorAttributes | This offers the typical values of a weapon suppressor attached to a muzzle like muzzle velocity coefficient, dispersion, muzzle effects, silenced, obstruction length and recoil factors |
SCR_WeaponAttachmentBayonetAttributes | This offers the typical values of a bayonet, like weapon melee damage/range/accuracy, is bayonet and obstruction length |
Currently, only one of these can be added at any time (all subsequent ones are ignored). In the future, though, this behaviour might change. This is because the ItemAttributeCollection API does not currently support anything but finding a single attribute.
Under the Hood - How to make your own attachments
Custom SCR_WeaponAttachmentAttributes
If an attachment doesn't fit in the above categories, a subclass of SCR_WeaponAttachmentAttributes can be used. Any subclass must implement two scripted functions:
As an example, here is the scripting of the SCR_WeaponAttachmentSuppresorAttributes:
SCR_WeaponStatsManagerComponent
Each entity that has a modifiable attributes must have its own SCR_WeaponStatsManagerComponent. This usually only affects attachments that have their own muzzle, like an under barrel grenade launcher, and only if that muzzle can be modified. If a weapon doesn't have a stats manager, it will function normally but any attachment will not cause any stats modification. Likewise, an attached grenade launcher (for example) would not support changing any attributes if there is no stats manager component on it.
If an attachment has its own stats manager, then muzzle specific attributes are handled by that stats manager, while global attributes are passed along to the parent.
BaseWeaponStatsManagerComponent API
BaseWeaponStatsManagerComponent has a set of at least three functions per modifiable attribute. They usually follow the same scheme:
- SetXXX is used to override a weapon stat
- ClearXXX is used to clear the override and return to "normal" values for this attachment
- GetXXX is used to see if and how the attribute is overridden
SetXXX
Setters set the override on a specific attachment, and cause a recalculation of the override values.
Setters usually have two or three arguments, depending on whether they set a global or muzzle attribute:
- The first argument is always the entity that is being attached/detached.
- The second argument depends on the scope of the changes stat. If it is a muzzle stat, there is a muzzle index passed in as second argument, which specifies which muzzle this attach operation went to. This value is always passed in via the ApplyModifier call, and passing anything else is an error
- The third (or second, depending on scope) parameter is the value(s) to set.
SetXXX returns true on successful completion. It returns false when the value could not be modified, for example, if a muzzle stat is modified and the muzzle index is -1.
ClearXXX
Clear calls clear the override settings on a specific attachment and cause a recalculation of the override values.
They usually come with one or two arguments:
- The first argument is the entity that is being attached/detached
- If a muzzle value is cleared, the second argument is the index of the muzzle
ClearXXX returns true if successful, or false if there was an error, typically if a muzzle index was not appropriate
GetXXX
While the SetXXX and ClearXXX calls are usually only used while attaching/detaching, Get calls are used within the components that can be modified. For example, the WeaponSoundsComponent will check if the weapon is suppressed using this call type and issue the appropriate sound event.
The usually have only one or two arguments:
- The first argument is the muzzle index, passed in only for stats of the Muzzle category.
- The second argument is a reference to a variable where the result is stored.
The return value of the function is true if the stat is overridden, and the variable reference is updated with the value of the override. If the value is not overridden, the function returns false.
Example of using a Getter
API
The following table lists the appropriate calls for each of the modifiable stats:
Muzzle Velocity Coefficient | bool SetMuzzleVelocityCoefficient(IEntity attach, int iMuzzleIndex, float muzzleCoeff)
bool ClearMuzzleVelocityCoefficient(IEntity attach, int iMuzzleIndex) bool GetMuzzleVelocityCoefficient(int iMuzzleIndex, out float coeff) |
Dispersion Factor | bool SetMuzzleDispersionFactor(IEntity attach, int iMuzzleIndex, float fFactor)
bool ClearMuzzleDispersionFactor(IEntity attach, int iMuzzleIndex) bool GetMuzzleDispersionFactor(int iMuzzleIndex, out float fFactor) |
Linear Recoil Factors | bool SetRecoilLinearFactors(IEntity attach, int iMuzzleIndex, vector vLinearFactors)
bool ClearRecoilLinearFactors(IEntity attach, int iMuzzleIndex) bool GetRecoilLinearFactors(int iMuzzleIndex, out vector vLinearFactors) |
Angular Recoil Factors | bool SetRecoilAngularFactors(IEntity attach, int iMuzzleIndex, vector vAngularFactors)
bool ClearRecoilAngularFactors(IEntity attach, int iMuzzleIndex) bool GetRecoilAngularFactors(int iMuzzleIndex, out vector vAngularFactors) |
Turn Factor Recoil Factors | bool SetRecoilTurnFactors(IEntity attach, int iMuzzleIndex, vector vTurnFactors)
bool ClearRecoilTurnFactors(IEntity attach, int iMuzzleIndex) bool GetRecoilTurnFactors(int iMuzzleIndex, out vector vTurnFactors) |
Override Shot Sound | bool SetShotSoundOverride(IEntity attach, int iMuzzleIndex, bool bOverride)
bool ClearShotSoundOverride(IEntity attach, int iMuzzleIndex) |
Extra Obstruction Length | bool SetExtraObstructionLength(IEntity attach, float extraLength) |
Muzzle Effect Override | bool SetMuzzleEffectOverride(IEntity attach, int iMuzzleIndex, bool bOverrde)
bool ClearMuzzleEffectOverride(IEntity attach, int iMuzzleIndex) bool GetMuzzleEffectOverride(int iMuzzleIndex, out bool bOverride) |
Is Bayonet | bool SetIsBayonet(IEntity attach, bool bIsBayonet) |
Melee Damage | bool SetMeleeDamageFactor(IEntity attach, float fDamageFactor) |
Melee Range | bool SetMeleeRangeFactor(IEntity attach, float fRangeFactor) |
Melee Accuracy | bool SetMeleeAccuracyFactor(IEntity attach, float fAccuracyFactor) |
Special case: Muzzle Effects
Muzzle Effects are a special case. The attachment must add all effects that it has and wants to play to the stats manager via the call
See the example for silencers above for an example how to use this. These muzzle effects stay in effect until the appropriate ClearMuzzleEffectOverride call.