Weapon
A Weapon is an Equipment that comes with built-in Abilities and fires Projectiles.
Properties
| Property Name | Return Type | Description | Tags | 
|---|---|---|---|
| animationStance | string | When the Weapon is equipped this animation stance is applied to the Player. | Read-Only | 
| attackCooldownDuration | number | Interval between separate burst sequences. The value is set by the Shoot ability's Cooldown duration. | Read-Only | 
| multiShotCount | integer | Number of Projectiles/Hitscans that will fire simultaneously inside the spread area each time the Weapon attacks. Does not affect the amount of ammo consumed per attack. | Read-Only | 
| burstCount | integer | Number of automatic activations of the Weapon that generally occur in quick succession. | Read-Only | 
| shotsPerSecond | number | Used in conjunction with burstCount to determine the interval between automatic weapon activations. | Read-Only | 
| shouldBurstStopOnRelease | boolean | If true, a burst sequence can be interrupted by the Player by releasing the action button. Iffalse, the burst continues firing automatically until it completes or the Weapon runs out of ammo. | Read-Only | 
| isHitscan | boolean | If false, the Weapon will produce simulated Projectiles. Iftrue, it will instead use instantaneous line traces to simulate shots. | Read-Only | 
| range | number | Max travel distance of the Projectile (isHitscan = False) or range of the line trace (isHitscan = True). | Read-Only | 
| damage | number | Damage applied to a Player when the weapon attack hits a player target. If set to zero, no damage is applied. | Read-Only | 
| projectileTemplateId | string | Asset reference for the visual body of the Projectile, for non-hitscan Weapons. | Read-Only | 
| muzzleFlashTemplateId | string | Asset reference for a Vfx to be attached to the muzzle point each time the Weapon attacks. | Read-Only | 
| attackSoundTemplateId | string | Asset reference for a sound effect to be played each time the Weapon attacks. | Read-Only | 
| trailTemplateId | string | Asset reference for a trail Vfx to follow the trajectory of the shot. | Read-Only | 
| beamTemplateId | string | Asset reference for a beam Vfx to be placed along the trajectory of the shot. Useful for hitscan Weapons or very fast Projectiles. | Read-Only | 
| impactSurfaceTemplateId | string | Asset reference of a Vfx to be attached to the surface of any CoreObjects hit by the attack. | Read-Only | 
| impactProjectileTemplateId | string | Asset reference of a Vfx to be spawned at the interaction point. It will be aligned with the trajectory. If the impacted object is a CoreObject, then the Vfx will attach to it as a child. | Read-Only | 
| impactPlayerTemplateId | string | Asset reference of a Vfx to be spawned at the interaction point, if the impacted object is a player. | Read-Only | 
| projectileSpeed | number | Travel speed (cm/s) of Projectiles spawned by this weapon. | Read-Only | 
| projectileLifeSpan | number | Duration after which Projectiles are destroyed. | Read-Only | 
| projectileGravity | number | Gravity scale applied to spawned Projectiles. | Read-Only | 
| projectileLength | number | Length of the Projectile's capsule collision. | Read-Only | 
| projectileRadius | number | Radius of the Projectile's capsule collision | Read-Only | 
| projectileDrag | number | Drag on the Projectile. | Read-Only | 
| projectileBounceCount | integer | Number of times the Projectile will bounce before it's destroyed. Each bounce generates an interaction event. | Read-Only | 
| projectilePierceCount | integer | Number of objects that will be pierced by the Projectile before it's destroyed. Each pierce generates an interaction event. | Read-Only | 
| maxAmmo | integer | How much ammo the Weapon starts with and its max capacity. If set to -1 then the Weapon has infinite capacity and doesn't need to reload. | Read-Only | 
| currentAmmo | integer | Current amount of ammo stored in this Weapon. | Read-Write | 
| ammoType | string | A unique identifier for the ammunition type. | Read-Only | 
| isAmmoFinite | boolean | Determines where the ammo comes from. If true, then ammo will be drawn from the Player's Resource inventory and reload will not be possible until the Player acquires more ammo somehow. Iffalse, then the Weapon simply reloads to full and inventory Resources are ignored. | Read-Only | 
| outOfAmmoSoundId | string | Asset reference for a sound effect to be played when the Weapon tries to activate, but is out of ammo. | Read-Only | 
| reloadSoundId | string | Asset reference for a sound effect to be played when the Weapon reloads ammo. | Read-Only | 
| spreadMin | number | Smallest size in degrees for the Weapon's cone of probability space to fire Projectiles in. | Read-Only | 
| spreadMax | number | Largest size in degrees for the Weapon's cone of probability space to fire Projectiles in. | Read-Only | 
| spreadAperture | number | The surface size from which shots spawn. An aperture of zero means shots originate from a single point. | Read-Only | 
| spreadDecreaseSpeed | number | Speed at which the spread contracts back from its current value to the minimum cone size. | Read-Only | 
| spreadIncreasePerShot | number | Amount the spread increases each time the Weapon attacks. | Read-Only | 
| spreadPenaltyPerShot | number | Cumulative penalty to the spread size for successive attacks. Penalty cools off based on spreadDecreaseSpeed. | Read-Only | 
Functions
| Function Name | Return Type | Description | Tags | 
|---|---|---|---|
| HasAmmo() | boolean | Informs whether the Weapon is able to attack or not. | None | 
| Attack(target) | None | Triggers the main ability of the Weapon. Optional target parameter can be a Vector3 world position, a Player, or a CoreObject. | None | 
Events
| Event Name | Return Type | Description | Tags | 
|---|---|---|---|
| targetImpactedEvent | Event<Weaponweapon,ImpactDataimpactData> | Fired when a Weapon interacts with something. For example a shot hits a wall. The ImpactDataparameter contains information such as which object was hit, who owns the Weapon, which ability was involved in the interaction, etc. | Server-Only | 
| projectileSpawnedEvent | Event<Weaponweapon,Projectileprojectile> | Fired when a Weapon spawns a projectile. | None | 
Examples
Example using:
projectileSpawnedEvent
Although it is ineffective to modify a projectile that comes through the projectileSpawnedEvent, it's still a useful event for various gameplay mechanics. In this example, a weapon script adds recoil impulse in the opposite direction of shots.
local WEAPON = script:FindAncestorByType('Weapon')
local KNOCKBACK_SPEED = 1000
-- Adds impulse to the owner once the attack ability is executed
function OnProjectileSpawned(weapon, projectile)
    local player = weapon.owner
    local projectileDirection = projectile:GetWorldTransform():GetForwardVector()
    local knockbackVector = projectileDirection * player.mass * -KNOCKBACK_SPEED
    -- Push the player away from the spawned projectile
    player:AddImpulse(knockbackVector)
end
WEAPON.projectileSpawnedEvent:Connect(OnProjectileSpawned)
See also: Equipment.owner | Player.AddImpulse | CoreObject.FindAncestorByType | Projectile.GetWorldTransform | Transform.GetForwardVector | Vector3 * Number
Example using:
targetImpactedEvent
In this example, a weapon has a healing mechanic, where the player gains 2 hit points each time they shoot an enemy player.
local WEAPON = script:FindAncestorByType('Weapon')
function OnTargetImpactedEvent(weapon, impactData)
    if impactData.targetObject and impactData.targetObject:IsA("Player") then
        weapon.owner.hitPoints = weapon.owner.hitPoints + 2
    end
end
WEAPON.targetImpactedEvent:Connect(OnTargetImpactedEvent)
See also: Equipment.owner | CoreObject.FindAncestorByType | ImpactData.targetObject | other.IsA | Player.hitPoints | Event.Connect
Example using:
Attack
Generally, weapons are thought to be equipped on players. However, a weapon can be used on an NPC such as a vehicle or tower by calling the Attack() function. In this example, a weapon simply fires each second. Shots will go out straight in the direction the weapon is pointing.
local WEAPON = script:FindAncestorByType('Weapon')
function Tick()
    WEAPON:Attack()
    Task.Wait(1)
end
See also: CoreObject.FindAncestorByType | CoreLua.Tick | Task.Wait
Example using:
HasAmmo
In this example, a custom sound is played when someone picks up a weapon that has no ammo in it. For this hypothetical game, weapons can be found without any ammo and it's an important mechanic. It should be displayed in the user interface. However, players hear sound effects much faster than they can read UI.
local WEAPON = script:FindAncestorByType('Weapon')
local EMPTY_PICKUP_SOUND = script:GetCustomProperty("EmptyPickupSound")
function OnEquipped(weapon, player)
    if (not weapon:HasAmmo()) then
        World.SpawnAsset(EMPTY_PICKUP_SOUND, {position = weapon:GetWorldPosition()})
    end
end
WEAPON.equippedEvent:Connect(OnEquipped)
See also: Equipment.equippedEvent | CoreObject.FindAncestorByType | World.SpawnAsset | Event.Connect
Example using:
ammoType
isAmmoFinite
currentAmmo
In this simple auto-reload script, the weapon's current ammo is monitored. If it goes to zero and the player has ammo of the correct type, then the reload ability is activated. This script only works in a client-context and expects the Reload ability to be assigned as a custom property.
local WEAPON = script:FindAncestorByType('Weapon')
local RELOAD_ABILITY = script:GetCustomProperty("Reload"):WaitForObject()
local LOCAL_PLAYER = Game.GetLocalPlayer()
function Tick(deltaTime)
    if WEAPON.owner ~= LOCAL_PLAYER then return end
    if WEAPON.currentAmmo == 0 and
    (not WEAPON.isAmmoFinite or LOCAL_PLAYER:GetResource(WEAPON.ammoType) > 0) then
        RELOAD_ABILITY:Activate()
        Task.Wait(
            RELOAD_ABILITY.castPhaseSettings.duration +
            RELOAD_ABILITY.executePhaseSettings.duration +
            RELOAD_ABILITY.recoveryPhaseSettings.duration)
    end
end
See also: Weapon.currentAmmo | Equipment.owner | Ability.Activate | AbilityPhaseSettings.duration | CoreObject.FindAncestorByType | CoreObjectReference.WaitForObject | Game.GetLocalPlayer | Player.GetResource | Task.Wait | CoreLua.Tick
Example using:
animationStance
A weapon's animationStance is assigned to the player automatically when the item is equipped. In this example, we add an additional stance to the weapon in the form of a defensive posture that players can trigger by holding down the secondary ability button (mouse right-click). The script alternates between the shield block stance and the weapon's default stance, as the secondary button is pressed/released.
local WEAPON = script:FindAncestorByType('Weapon')
local ACTION_NAME = "Aim"
local ACTIVE_STANCE = "1hand_melee_shield_block"
function EnableStance(player)
    if Object.IsValid(player) and player == WEAPON.owner then
        player.animationStance = ACTIVE_STANCE
    end
end
function DisableStance(player)
    if WEAPON and Object.IsValid(player) then
        player.animationStance = WEAPON.animationStance
    end
end
function OnActionPressed(player, actionName)
    if actionName == ACTION_NAME then
        EnableStance(player)
    end
end
function OnActionReleased(player, actionName)
    if actionName == ACTION_NAME then
        DisableStance(player)
    end
end
function OnPlayerDied(player, damage)
    DisableStance(player)
end
function OnEquipped(weapon, player)
    Input.actionPressedEvent:Connect(OnActionPressed)
    Input.actionReleasedEvent:Connect(OnActionReleased)
end
WEAPON.equippedEvent:Connect(OnEquipped)
See also: Equipment.equippedEvent | Player.animationStance | Input.actionPressedEvent | CoreObject.FindAncestorByType | Object.IsValid | Event.Connect
Example using:
attackCooldownDuration
multiShotCount
burstCount
shotsPerSecond
The following function approximates a weapon's effective damage per second (DPS).
local WEAPON = script:FindAncestorByType("Weapon")
function ComputeDPS(weapon)
    local dps = 6.2
    while weapon.shotsPerSecond < dps do
        dps = dps / 2
    end
    local burst = math.max(1, weapon.burstCount)
    if burst < weapon.maxAmmo or weapon.maxAmmo <= 0 then
        local burstPeriod = (burst / dps + weapon.attackCooldownDuration)
        dps = burst / burstPeriod
    end
    return dps * weapon.damage * weapon.multiShotCount
end
print("DPS = " .. ComputeDPS(WEAPON))
See also: Weapon.maxAmmo | CoreObject.FindAncestorByType | CoreLua.print
Example using:
currentAmmo
maxAmmo
This script plays audio to the weapon owner when the weapon reaches 20% amount of ammo. It works best if the script is in a client context under the weapon, that way the audio is heard only by the player who is using the weapon.
local WEAPON = script:FindAncestorByType('Weapon')
local SHOOT_ABILITY = script:GetCustomProperty("ShootAbility"):WaitForObject()
local LOW_AMMO_SOUND = WEAPON:GetCustomProperty("LowAmmoSound")
local LOW_AMMO_PERCENTAGE = 0.2
function OnShootExecute(ability)
    if Object.IsValid(WEAPON) and ability.owner == WEAPON.owner then
        if WEAPON.currentAmmo / WEAPON.maxAmmo <= LOW_AMMO_PERCENTAGE then
            if LOW_AMMO_SOUND then
                World.SpawnAsset(LOW_AMMO_SOUND, {position = WEAPON:GetWorldPosition()})
            end
        end
    end
end
SHOOT_ABILITY.executeEvent:Connect(OnShootExecute)
See also: Equipment.owner | Ability.owner | CoreObject.FindAncestorByType | CoreObjectReference.WaitForObject | Object.IsValid | World.SpawnAsset | Event.Connect
Example using:
isAmmoFinite
reloadSoundId
While various properties are read-only, they are still useful in determining what behavior should occur, leading to more general purpose scripts. In this example, a script controls auto-reloading of weapons. It expects to be in a client context, because the ability's Activate() function is client-only.
local WEAPON = script:FindAncestorByType('Weapon')
local RELOAD_ABILITY = nil
-- Grabs reload ability from the weapon. Keep trying in case the client hasn't loaded the object yet
while not Object.IsValid(RELOAD_ABILITY) do
    Task.Wait()
    RELOAD_ABILITY = WEAPON:GetAbilities()[2]
end
-- The client script can now keep going, after it has acquired a reference to the reload ability
-- The above could also have been implemented with a :GetCustomProperty(...):WaitForObject()
-- Manually spawn the reloading audio
function SpawnReloadingAudio()
    if WEAPON.reloadSoundId ~= nil then
        World.SpawnAsset(WEAPON.reloadSoundId, {position = WEAPON:GetWorldPosition()})
    end
end
function Tick(deltaTime)
    -- Makes sure that the weapon owner is the local player
    if not Object.IsValid(WEAPON) then return end
    if not WEAPON.owner == Game.GetLocalPlayer() then return end
    if not WEAPON.isAmmoFinite then
        -- Checks when the weapon has empty ammo to reload
        if WEAPON.currentAmmo == 0 then
            SpawnReloadingAudio()
            RELOAD_ABILITY:Activate()
            Task.Wait(RELOAD_ABILITY.castPhaseSettings.duration)
        end
    end
end
See also: Weapon.GetAbilities | Equipment.owner | Ability.Activate | AbilityPhaseSettings.duration | Game.GetLocalPlayer | CoreObject.FindAncestorByType | World.SpawnAsset | Object.IsValid | Task.Wait | CoreLua.Tick
Example using:
isHitscan
range
damage
projectileTemplateId
trailTemplateId
impactSurfaceTemplateId
impactProjectileTemplateId
impactPlayerTemplateId
projectileSpeed
projectileLifeSpan
projectileGravity
projectileLength
projectileRadius
projectileDrag
This script implements a Wall Bang mechanic, allowing shots to go through walls.
Configuring walls/objects to be penetrable: For each wall or object that should be penetrable, add to them the "WallBang" custom property (float). Only objects with this property will be penetrable. Higher values mean the wall reduces more damage from shots that go through them. Objects with a "WallBang" value of 0 will let shots through but will not affect the damage amount.
Configuring this script on a weapon: Set this script's "WallBang" property to affect the weapon's penetrability when it's compared against objects. A higher value means it penetrates tougher walls and takes less damage reduction. A weapon can further control how much of its damage is reduced by setting the "DamageReduction" property on this script (Between zero and 1).
local WEAPON = script:FindAncestorByType("Weapon")
local WALL_BANG = script:GetCustomProperty("WallBang") or 2
local DAMAGE_REDUCTION = script:GetCustomProperty("DamageReduction") or 1
if WALL_BANG <= 0 then return end
function OnTargetImpactedEvent(weapon, impactData)
    if not Object.IsValid(weapon) then return end
    local wall = impactData.targetObject
    if not wall or not wall:IsA("StaticMesh") then return end
    -- If the wall hasn't defined the WallBang property it's impenetrable
    local wallBangResistance = wall:GetCustomProperty("WallBang")
    if not wallBangResistance or wallBangResistance >= WALL_BANG then return end
    -- Calculate damage
    local damage = weapon.damage
    if DAMAGE_REDUCTION > 0 then
        local percent = (WALL_BANG - wallBangResistance) / WALL_BANG
        damage = CoreMath.Lerp(0, weapon.damage, percent)
        percent = CoreMath.Clamp(DAMAGE_REDUCTION)
        damage = CoreMath.Lerp(weapon.damage, damage, percent)
    end
    -- Gather info about position and direction of the shot
    local impactPos = impactData:GetHitResult():GetImpactPosition()
    local direction = impactPos - weapon:GetWorldPosition()
    local remainingTravel = weapon.range - impactData.travelDistance
    -- Perhaps do more if the weapon is of hitscan type
    if not weapon.isHitscan then
        if impactData.projectile then
            direction = impactData.projectile:GetVelocity()
        end
    end
    direction = direction:GetNormalized()
    -- Do a series of raycasts to figure out where is the bullet's exit point
    local rayStart = impactPos + direction * 5
    local rayEnd = rayStart + direction * remainingTravel
    local rayParams = {}
    if Object.IsValid(impactData.weaponOwner) and impactData.weaponOwner.team > 0 then
        rayParams.ignoreTeams = weapon.owner.team
    end
    local hit = World.Raycast(rayStart, rayEnd, rayParams)
    if hit then
        rayEnd = rayStart
        rayStart = hit:GetImpactPosition()
    else
        local swapValue = rayEnd
        rayEnd = rayStart
        rayStart = swapValue
    end
    -- The 'hitInverted' is the info about the bullet's exit point
    local hitInverted = World.Raycast(rayStart, rayEnd, rayParams)
    if not hitInverted then return end
    -- Spawn the surface impact VFX on the opposite side of the object
    if weapon.impactSurfaceTemplateId then
        local t = hitInverted:GetTransform()
        SpawnVfx(weapon.impactSurfaceTemplateId, t:GetPosition(), t:GetRotation())
    end
    -- Spawn a new projectile to continue on the trajectory
    local projLength = 5 + weapon.projectileLength + weapon.projectileRadius
    startPos = hitInverted:GetImpactPosition() + direction * projLength
    local projectile = Projectile.Spawn(weapon.projectileTemplateId, startPos, direction)
    -- Copy properties from the weapon to the new projectile
    projectile.owner = impactData.weaponOwner
    projectile.sourceAbility = impactData.sourceAbility
    projectile.speed = weapon.projectileSpeed
    projectile.gravityScale = weapon.projectileGravity
    projectile.drag = weapon.projectileDrag
    projectile.lifeSpan = weapon.projectileLifeSpan * remainingTravel / weapon.range
    projectile.capsuleLength = weapon.projectileLength
    projectile.capsuleRadius = weapon.projectileRadius
    -- If some weapon properties are needed later it's safer to stash them in serverUserData,
    -- because the weapon might be destroyed while the projectile is still in the air:
    projectile.serverUserData.impactSurfaceTemplateId = weapon.impactSurfaceTemplateId
    projectile.serverUserData.impactPlayerTemplateId = weapon.impactPlayerTemplateId
    projectile.serverUserData.impactProjectileTemplateId = weapon.impactProjectileTemplateId
    projectile.serverUserData.direction = direction
    -- Store damage calculation onto the projectile because there may be multiple ones
    projectile.serverUserData.damage = damage
    -- Listen for the impact, to spawn effects and apply damage
    projectile.impactEvent:Connect(OnProjectileImpacted)
    -- Spawn a trail to follow the projectile
    if weapon.trailTemplateId and projectile.speed > 0 then
        local pos = hitInverted:GetImpactPosition()
        local trailLifeSpan = (rayStart - pos).size / projectile.speed
        trailLifeSpan = math.min(projectile.lifeSpan, trailLifeSpan)
        if trailLifeSpan > 0 then
            local rot = Rotation.New(direction, Vector3.UP)
            local trail = World.SpawnAsset(weapon.trailTemplateId, {position = pos, rotation = rot})
            trail:MoveContinuous(direction * projectile.speed)
            trail.lifeSpan = trailLifeSpan
        end
    end
end
function OnProjectileImpacted(projectile, other, hitResult)
    if not Object.IsValid(projectile) then return end
    local impactTemplate = nil
    if other:IsA("Player") then
        -- Construct and apply damage to player
        local dmg = Damage.New(projectile.serverUserData.damage)
        dmg.reason = DamageReason.COMBAT
        dmg:SetHitResult(hitResult)
        dmg.sourceAbility = projectile.sourceAbility
        dmg.sourcePlayer = projectile.owner
        other:ApplyDamage(dmg)
        impactTemplate = projectile.serverUserData.impactPlayerTemplateId
    else
        impactTemplate = projectile.serverUserData.impactSurfaceTemplateId
       end
    -- Spawn impact VFX
    local t = hitResult:GetTransform()
    if impactTemplate then
        SpawnVfx(impactTemplate, t:GetPosition(), t:GetRotation())
    end
    impactTemplate = projectile.serverUserData.impactProjectileTemplateId
    if impactTemplate then
        local rot = Rotation.New(projectile.serverUserData.direction, Vector3.UP)
        SpawnVfx(impactTemplate, t:GetPosition(), rot)
    end
end
function SpawnVfx(template, pos, rot)
    local vfx = World.SpawnAsset(template, {position = pos, rotation = rot})
    if vfx.lifeSpan <= 0 then
        vfx.lifeSpan = 1.2
    end
end
WEAPON.targetImpactedEvent:Connect(OnTargetImpactedEvent)
See also: Weapon.targetImpactedEvent | World.Raycast | ImpactData.targetObject | HitResult.GetImpactPosition | Projectile.Spawn | Damage.New | Player.team | CoreObject.FindAncestorByType | Object.serverUserData | other.isA | CoreMath.Lerp | Vector3.GetNormalized | Rotation.New | Transform.GetPosition | Event.Connect
Example using:
muzzleFlashTemplateId
This sample demonstrates several things. First, it creates a copy of the weapon's muzzle flash effect and attaches it to where the script is. Then, it shows how to traverse an object's hierarchy and create a custom table of objects to operate upon later--in this case it's trying to find smart objects that have both the Stop() and Play() functions. Finally, It shows how sound and VFX from a single spawned template can be played and stopped randomly. In other words, they are reused without having to spawn a new copy of the template each time.
local WEAPON = script:FindAncestorByType("Weapon")
if WEAPON.muzzleFlashTemplateId == nil then return end
local smartObjects = {}
local muzzleInstance = World.SpawnAsset(WEAPON.muzzleFlashTemplateId, {parent = script})
-- A utility function that runs the same operation on all nodes in an object's hierarchy
function ForEachChild(coreObj, functionToCall)
    functionToCall(coreObj)
    for _, child in ipairs(coreObj:GetChildren()) do
        ForEachChild(child, functionToCall)
    end
end
-- Find all the core objects in the template that have both the Play() and Stop() functions
ForEachChild(muzzleInstance, function(coreObj)
    if coreObj.Play and coreObj.Stop then
        table.insert(smartObjects, coreObj)
        coreObj:Stop()
    end
end)
while true do
    -- Wait between 0 and 1 second
    Task.Wait(math.random())
    -- Play all the effects
    for _, obj in ipairs(smartObjects) do
        obj:Play()
    end
    -- Wait between 0 and 0.3 seconds
    Task.Wait(math.random() * 0.3)
    -- Stop all the effects
    for _, obj in ipairs(smartObjects) do
        obj:Stop()
    end
    -- Repeat...
end
See also: CoreObject.FindAncestorByType | World.SpawnAsset | Vfx.Play | SmartAudio.Play | Task.Wait
Example using:
outOfAmmoSoundId
Weapons are also of type Equipment. In this example we listen to when a player equips the weapon. When they do, if the weapon is out of ammo then we play the "out of ammo" sound effect which normally only plays after trying to shoot while empty.
local WEAPON = script:FindAncestorByType('Weapon')
function OnEquipped(equipment, player)
    if WEAPON.currentAmmo == 0 and WEAPON.outOfAmmoSoundId then
        local pos = WEAPON:GetWorldPosition()
        World.SpawnAsset(WEAPON.outOfAmmoSoundId, {position = pos})
    end
end
WEAPON.equippedEvent:Connect(OnEquipped)
See also: Equipment.equippedEvent | CoreObject.FindAncestorByType | World.SpawnAsset | Event.Connect
Example using:
projectileBounceCount
projectilePierceCount
A weapon-viewing interface can show detailed specs about each weapon to players. In this example, the weapon's damage, as well as indicators if the shots bounce or pierce are setup for the player to view. This script would exist as part of a greater user interface, with various images and texts, and the ShowUI() function would be called depending on the game state (for example the player is browsing a shop).
local WEAPON_DETAILS_UI = script.parent
local DAMAGE_LABEL = script:GetCustomProperty("DamageLabel"):WaitForObject()
local BOUNCE_UI = script:GetCustomProperty("BounceGroup"):WaitForObject()
local PIERCE_UI = script:GetCustomProperty("PierceGroup"):WaitForObject()
function ShowUI(weapon)
    WEAPON_DETAILS_UI.visibility = Visibility.INHERIT
    -- Damage
    DAMAGE_LABEL.text = "Damage: " .. tostring(weapon.damage)
    -- Bounces? yes/no
    if weapon.projectileBounceCount > 0 then
        BOUNCE_UI.visibility = Visibility.INHERIT
    else
        BOUNCE_UI.visibility = Visibility.FORCE_OFF
    end
    -- Pierces? yes/no
    if weapon.projectilePierceCount > 0 then
        PIERCE_UI.visibility = Visibility.INHERIT
    else
        PIERCE_UI.visibility = Visibility.FORCE_OFF
    end
end
function HideUI()
    WEAPON_DETAILS_UI.visibility = Visibility.FORCE_OFF
end
See also: Weapon.damage | CoreObject.parent | CoreObjectReference.WaitForObject | UIText.text
Example using:
shouldBurstStopOnRelease
The following function evaluates a weapon and returns the "type" of weapon it thinks it is, based on some of its properties.
WeaponClass.AutomaticRifle = 1
WeaponClass.BurstRifle = 2
WeaponClass.Sniper = 3
WeaponClass.Pistol = 4
WeaponClass.Shotgun = 5
function ClassifyWeapon(weapon)
    if weapon.burst > 1 then
        if weapon.shouldBurstStopOnRelease then
            return WeaponClass.AutomaticRifle
        else
            return WeaponClass.BurstRifle
        end
    else
        if weapon.multiShotCount > 1 then
            return WeaponClass.Shotgun
        elseif string.match(weapon:GetAbilities()[1].animation, "pistol") then
            return WeaponClass.Pistol
        else
            return WeaponClass.Sniper
        end
    end
end
See also: Equipment.GetAbilities | Ability.animation
Example using:
spreadMin
spreadMax
spreadAperture
spreadDecreaseSpeed
spreadIncreasePerShot
spreadPenaltyPerShot
It can be hard to understand the implications of spread on the efficacy of a weapon, especially as there is a complex relationship with firing rate. This example demonstrates a data-driven approach to studying gameplay. If this script is added to each weapon they will register their stats into a global table, which could then be analyzed to draw conclusions about the game's balance.
local WEAPON = script:FindAncestorByType("Weapon")
if _G.WeaponStudy == nil then
    _G.WeaponStudy = {}
    _G.WeaponStudy.samples = {}
    _G.WeaponStudy.AddSample = function(weapon)
        local key = weapon.name
        if _G.WeaponStudy.samples[key] then return end
        _G.WeaponStudy.samples[key] = {
            spreadMin = weapon.spreadMin,
            spreadMax = weapon.spreadMax,
            spreadAperture = weapon.spreadAperture,
            spreadDecreaseSpeed = weapon.spreadDecreaseSpeed,
            spreadPenaltyPerShot = weapon.spreadPenaltyPerShot,
            damageDealt = 0
        }
    end
    _G.WeaponStudy.ReportDamage = function(weapon, amount)
        local key = weapon.name
        local dmg = _G.WeaponStudy.samples[key].damageDealt
        _G.WeaponStudy.samples[key].damageDealt = dmg + amount
    end
end
_G.WeaponStudy.AddSample(WEAPON)
local GOOD = true
local BAD = false
function CalcStatRating(statName, compareTo, goodOrBad)
    local minValue
    local maxValue
    for weaponName,data in pairs(_G.WeaponStudy.samples) do
        local statValue = data[statName]
        if not minValue or statValue < minValue then
            minValue = statValue
        end
        if not maxValue or statValue > maxValue then
            maxValue = statValue
        end
    end
    if not minValue then
        error("Missing samples")
        return
    end
    local result
    if maxValue == minValue then
        -- Avoid division by zero
        result = 1
    else
        result = (compareTo - minValue) / (maxValue - minValue)
    end
    if goodOrBad == GOOD then
        return result
    end
    return 1 - result
end
function RateMyGun()
    local spreadMinRating = CalcStatRating("spreadMin", WEAPON.spreadMin, BAD)
    local spreadMaxRating = CalcStatRating("spreadMax", WEAPON.spreadMax, BAD)
    local spreadApertureRating = CalcStatRating("spreadAperture", WEAPON.spreadAperture, BAD)
    local spreadDecreaseSpeedRating = CalcStatRating("spreadDecreaseSpeed", WEAPON.spreadDecreaseSpeed, GOOD)
    local spreadPenaltyPerShotRating = CalcStatRating("spreadPenaltyPerShot", WEAPON.spreadPenaltyPerShot, BAD)
    print("")
    print("Rating - " .. WEAPON.name)
    print("  spreadMin: " .. tostring(spreadMinRating))
    print("  spreadMax: " .. tostring(spreadMaxRating))
    print("  spreadAperture: " .. tostring(spreadApertureRating))
    print("  spreadDecreaseSpeed: " .. tostring(spreadDecreaseSpeedRating))
    print("  spreadPenaltyPerShot: " .. tostring(spreadPenaltyPerShotRating))
end
-- We're keeping track of damage dealt here, but additional study is needed to draw conclusions.
-- Plus, this would need to be tested in multiplayer and the data accessed somehow.
function OnTargetImpacted(weapon, impactData)
    if ImpactData.targetObject and ImpactData.targetObject:IsA("Player") then
        -- The damage calculation may change per weapon. This is a generic example
        local damageAmount = weapon.damage
        _G.WeaponStudy.ReportDamage(WEAPON, damageAmount)
    end
end
WEAPON.targetImpactedEvent:Connect(OnTargetImpacted)
-- Rate each gun based on how it measures against all the other ones in the hierarchy.
-- Works best if there are several guns in the scene, with a varying spread of stats.
Task.Wait(1)
RateMyGun()
See also: Weapon.targetImpactedEvent | ImpactData.targetObject | CoreObject.FindAncestorByType | CoreLua.print | other.IsA | Event.Connect | Task.Wait
Example using:
spreadMin
spreadMax
spreadDecreaseSpeed
Often in shooting games, the weapon loses precision while moving. For weapons in Core this is achieved by modifying the player's spreadModifier property, and can be implemented in many different ways. In this example, a client-context script uses the weapon's configured spreadMin and spreadMax properties to determine the maximum penalty when the player is moving. The weapon's spreadDecreaseSpeed is then used as an interpolation coefficient to smoothly move the spread penalty up and down, non-linearly, as the player moves or stops moving.
local WEAPON = script:FindAncestorByType("Weapon")
local MOVING_THRESHOLD = 250
local wasMoving = false
local targetSpreadModifier = 0
function Tick()
    local player = WEAPON.owner
    if not Object.IsValid(player) then return end
    -- Evaluate if the player is moving right now
    local isMovingNow = false
    if player.isJumping then
        isMovingNow = true
    else
        local playerSpeed = player:GetVelocity().size
        if playerSpeed >= MOVING_THRESHOLD then
            isMovingNow = true
        end
    end
    -- Select target spread modifier based on current movement
    if isMovingNow ~= wasMoving then
        if isMovingNow then
            -- Moving
            targetSpreadModifier = WEAPON.spreadMax - WEAPON.spreadMin
        else
            -- Not moving
            targetSpreadModifier = 0
        end
    end
    wasMoving = isMovingNow
    -- Adjust the player spread modify gradually over time
    local t = WEAPON.spreadDecreaseSpeed / 100
    player.spreadModifier = CoreMath.Lerp(player.spreadModifier, targetSpreadModifier, t)
end
See also: Equipment.owner | Player.spreadModifier | CoreObject.FindAncestorByType | Object.IsValid | Vector3.size | CoreLua.Tick | CoreMath.Lerp
Tutorials
Weapons in Core | Weapons & Abilities (Advanced)