| Home Page | Recent Changes | Preferences

Reloading Weapons

Reloading Weapons.

I've put quite a bit of effort in to making robust reloading code for the Weapons in the LawDogs mod. Since reloading is something that is potentially useful to everyone, I have made the code available here (minus all the stuff that doesn't relate to reloading).

It is complete with features allowing for sounds, animations and other effects, it works on-line and as far as I can tell it is virtually free of bugs. All the main code is contained in a weapon class. It is an abstract class, the actual weapons are subclasses of it.

class ReloadingWeapon extends Weapon
    abstract;

var() int ClipCount; //What's that you say? Clip is not a technically correct term? Do I care?
var() float ReloadRate; //Time it takes to insert one bullet.
var float ReloadTimer;

var() sound ReloadBeginSound, ReloadSound, ReloadEndSound; //Sounds played when start to reload, on insertion of each bullet, and when reloading has ended.
var() name ReloadAnim; //Animation to play when reloading is started.
var() float ReloadAnimRate;

var bool bIsReloading, bReloadEffectDone;

replication
{
    reliable if(Role == ROLE_Authority)
        ClipCount;

    //Functions called on the server from the client.
    reliable if(Role < ROLE_Authority)
        ReloadMeNow, FinishReloading;

    //Functions called on the client from the server.
    reliable if(Role == ROLE_Authority)
        ClientReload, ClientFinishReloading, ClientReloadEffects;
}

//So reloading can be bound to a key. Exec functions in weapons can only be called for the currently held weapon, which is perfect for this purpose.
exec function ReloadMeNow()
{
    if(!AllowReload())
        return;

    bIsReloading = true;
    ReloadTimer = Level.TimeSeconds;
    PlaySound(ReloadBeginSound, SLOT_Misc, TransientSoundVolume,,,, false);
    ClientReload();
}

//Called on the client when reloading starts.
simulated function ClientReload()
{
    bIsReloading = true;
    PlayAnim(ReloadAnim, ReloadAnimRate, 0.1);
}

//For effects during reloading, like smoke or shells ejected from the breech.
simulated function ClientReloadEffects(){}

//Reloading ends when the key is released.
exec function FinishReloading()
{
    if(!bIsReloading)
        return;

    PlaySound(ReloadEndSound, SLOT_Misc, TransientSoundVolume,,,, false);
    ClientFinishReloading();
    bIsReloading = false;
    bReloadEffectDone = false;
}

//Called on the client when reloading ends.
simulated function ClientFinishReloading()
{
    bIsReloading = false;
    PlayIdle();

    if(Instigator.PendingWeapon != None && Instigator.PendingWeapon != self)
        Instigator.Controller.ClientSwitchToBestWeapon();
}

function bool AllowReload()
{
    //Can't reload whilst firing.
    if(FireMode[0].IsFiring() || FireMode[1].IsFiring())
        return false;
    //Can't reload if already reloading.
    else if(bIsReloading)
        return false;
    //Can't reload if already full.
    else if(ClipCount >= Default.ClipCount)
        return false;
    //Can't reload if not enough ammo.
    else if(Ammo[0] == None || Ammo[0].AmmoAmount <= ClipCount)
        return false;

    return true;
}

//Don't allow weapon switching whilst reloading.
simulated function bool PutDown()
{
    if(bIsReloading)
        return false;

    return Super.PutDown();
}

simulated function BringUp(optional Weapon PrevWeapon)
{
    Super.BringUp(PrevWeapon);
    bIsReloading = false;
}

//Reduce ClipCount every time a bullet is fired.
function ConsumeAmmo(int Mode, float load)
{
    if(Ammo[Mode] != None)
    {
        if(Ammo[Mode].UseAmmo(int(load)) && load > 0)
            ClipCount--;
    }
}

simulated function bool HasAmmo()
{
    //Ignore FireMode[1] which doesn't use any ammo.
    return (Ammo[0] != None && FireMode[0] != None && Ammo[0].AmmoAmount >= FireMode[0].AmmoPerFire);
}

event WeaponTick(float dt)
{
    if(!bIsReloading)
    {
        //Stupid bots like to run around with an empty weapon, so force them to reload when appropriate.
        if(!Instigator.IsHumanControlled())
        {
            if(Level.TimeSeconds - Instigator.Controller.LastSeenTime > ClipCount)
                ReloadMeNow();
        }
    }
    else
    {
        //Add one bullet at a time as long as reloading key is held down.
        if(Level.TimeSeconds - ReloadTimer >= ReloadRate)
        {
            if(ClipCount >= Default.ClipCount) //Full.
            {
                ClipCount = Default.ClipCount;
                FinishReloading();
            }
            else if(Ammo[0].AmmoAmount <= ClipCount) //Out of ammo.
            {
                ClipCount = Ammo[0].AmmoAmount;
                FinishReloading();
            }
            else //Add another bullet.
            {
                PlaySound(ReloadSound, SLOT_Misc, TransientSoundVolume,,,, false);
                InsertBullet();

                if(ClipCount >= Default.ClipCount)
                {
                    ReloadTimer = Level.TimeSeconds - (ReloadRate / 2);
                }
                else
                    ReloadTimer = Level.TimeSeconds;
            }
        }
        else if(!bReloadEffectDone && Level.TimeSeconds - ReloadTimer >= ReloadRate / 2)
        {
            bReloadEffectDone = true;
            ClientReloadEffects();
        }
    }
}

//Can play animations for inserting individual bullets here, or other effects. Server-side only, so should call a replicated function here and play animations in it, rather than in this function itself.
function InsertBullet()
{
    ClipCount++;
}

//Show how many bullets left until reload.
simulated function float ChargeBar()
{
    local float CurrentClip, MaxClip;

    //Store the int values as floats to avoid rounding errors.
    CurrentClip = ClipCount;
    MaxClip = Default.ClipCount;

    return FMin(1, CurrentClip/MaxClip);
}

function byte BestMode()
{
    if(ClipCount > 0)
        return 0;

    return 1;
}

defaultproperties
{
    ClipCount=6
    ReloadRate=1.0
    ReloadAnim=Reload
    ReloadAnimRate=1.0

    bShowChargingBar=True
}

Bullets are inserted one at a time. If you wanted to completely fill the weapon in one go (as would be the case with most modern-style weapons) then change the bit in the InsertBullet function to ClipCount = Default.ClipCount

To have reloading triggered from a key press, two functions need to be bound to one key. For example, in User.ini:

E=ReloadMeNow | OnRelease FinishReloading

To make it easier to bind for end-users, you can make a custom GUIUserKeyBinding, which will make the bind show up in the controls menu. Here is an example:

class ReloadBinding extends GUIUserKeyBinding;

defaultproperties
{
    KeyData(0)=(KeyLabel="YourMod",bIsSection=True)
    KeyData(1)=(Alias="ReloadMeNow | OnRelease FinishReloading",KeyLabel="Reload")
}

As well as reloading from a key, it can also be done from alt-fire, like this:

class ReloadFire extends WeaponFire;

event ModeDoFire()
{
    ReloadingWeapon(Weapon).ReloadMeNow();
}

function StopFiring()
{
    ReloadingWeapon(Weapon).FinishReloading();
}

function bool IsFiring()
{
    return false;
}

defaultproperties
{
    bModeExclusive=true
    bWaitForRelease=true
    FireRate=0.2
    BotRefireRate=1.0
    AmmoClass=class'BallAmmo'
}

Last but not least, to ensure that the firemode(s) can only fire whilst there is ammo in the clip:

class ReloadFire extends ProjectileFire
    abstract;

var float LastClickTime;

var() Name EmptyAnim;
var() float EmptyAnimRate;

simulated function bool AllowFire()
{
    if(ReloadingWeapon(Weapon).bIsReloading)
        return false;

    if(ReloadingWeapon(Weapon).ClipCount < 1)
    {
        if(Level.TimeSeconds - LastClickTime > FireRate)
        {
            Weapon.PlayOwnedSound(NoAmmoSound, SLOT_Interact, TransientSoundVolume,,,, false);
            LastClickTime = Level.TimeSeconds;

            if(Weapon.HasAnim(EmptyAnim))
                weapon.PlayAnim(EmptyAnim, EmptyAnimRate, 0.0);
        }

        return false;
    }

    LastClickTime = Level.TimeSeconds;

    return Super.AllowFire();
}

defaultproperties
{
}

In this case it is a ProjectileFire?, but it applies exactly the same for InstantFire classes.

Category Tutorial

The Unreal Engine Documentation Site

Wiki Community

Topic Categories

Image Uploads

Random Page

Recent Changes

Offline Wiki

Unreal Engine

Console Commands

Terminology

Mapping Topics

Mapping Lessons

UnrealEd Interface

Questions&Answers

Scripting Topics

Scripting Lessons

Making Mods

Class Tree

Questions&Answers

Modeling Topics

Questions&Answers

Log In