Legacy:Trystan/12 26 02a

From Unreal Wiki, The Unreal Engine Documentation Site
Jump to navigation Jump to search

HOWTO: Create Reloadable Weapons

First linkage.

Syndicate Mod coder Tanus has a tutorial up based on Solid Snake's code and modified by him and HEGI. I've read this tutorial but decided against implementing it like he has it in certain ways. And since I also want to know my code very, very well, I've decided to use what I learned in this tutorial but to implement the code myself.

BeyondUnreal has the original post by [SAS]Solid Snake, a coder who's proficient both at code and at explaining what he does. A rarity.

What we need to do

  • Reloading consists of the following actions:
    • Checking for a magazine.
    • Discarding the old magazine if empty, and retaining it if not. (Retaining the old magazine is not necessary.)
    • Reloading the weapon with the new magazine.
  • Things we need to track are:
    • How many magazines we have left.
    • How many bullets are in the current magazine.
    • What type of ammunition are we firing?
  • Things we need to change are:
    • Firing weapons, to have them check ammunition in the clip instead of total ammunition.
    • HUD display to show number of clips left, and amount of ammunition in the clip currently loaded.
  • Things we need to create are:
    • Reload animations.
    • Empty click sound for when we have no ammunition.
    • Ammunition clip pickup.

Not so hard when you phrase it like this. =) The biggest problem is this will be our first foray into replication and replicated functions. In order to reload your weapon your client must send a reload command to the server, which then decides whether or not you should be able to reload.

Thought Process

Bullets and magazines are closely related so most of the functions that modify ammunition should be where we begin our search. There's currently a variable in place for ammunition that is properly checked in all locations for whether or not we can fire; we'll leave that variable in place for simplicity's sake. What we will do is turn down the maximum ammunition of the weapons to something more realistic - perhaps 25 a magazine. The reload command will simply replace the amount of ammunition if there's a magazine available.

For now we will not attempt to keep clips that have remaining bullets. This will allow us to simplify the reload code as we won't have to maintain information on how many bullets each magazine has in it - we can assume that each magazine will fill the weapon to maximum.

The first thing we must do is extend Weapon so that we have our own base weapon class. This is so that we don't have to manually include and rewrite all of the functions necessary to support reloading for each and every weapon. Indeed if you're planning on extending the weapons in UT2K3 much at all it's a good idea to create your own base weapon class. Tracing how a weapon fires.. ok, Wiki page found (thanks HunterKiller =)) at How UT2003 Weapons Work/Firing A Weapon. Transferring work temporarily there to there.

...a week later...

The Reload Exec Function

We're back. I managed to get distracted quite a bit by determining exactly how weapons fire. Now we've got a nifty flowchart available and we can keep working on extending how a weapon works.

We need to create an exec function to reload. Exec functions are functions that can be bound to inside UT2K3. They basically serve as the beginning point for any new functionality you wish to put into UT2K3. I've got my own PlayerController class and that is where I place my new exec functions for the most part. Some folks think it's best to not extend the PlayerController unless you have to, and to use your HUD or other class to place your exec functions. This is a matter of style.

<uscript> exec function Reload() {

   if( ( Pawn != None ) && ( Pawn.Weapon != None ) && Pawn.Weapon.IsA('WAWWeapon') )

{ WAWWeapon( Pawn.Weapon ).Reload(); // Play reload animation here. } } </uscript>

Added: Test against IsA('WAWWeapon') to ensure that the object could be cast properly. If your new weapons are coexisting with UT2K3 stock weapons this is semi important. =)

First we want to ensure we have proper variables in place for Pawn and Pawn.Weapon. UnrealScript uses short circuit logic so if Pawn == None the test against Pawn.Weapon will never occur; this should keep any "Access Nones" from occurring. The next part gets a bit difficult to explain, but you'll be doing it a lot in your custom code. By default Pawn.Weapon is class Weapon. In class Weapon there is no function of Reload. In OOP you can travel up or down the hierachy assuming that the functions truly exist. In our case we know all our weapons are derived from WAWWeapon, and so we cast Pawn.Weapon to WAWWeapon so that we have access to the Reload() function in the WAWWeapon class.

A Short Detour into Casting

NOTE: I'm not a great teacher, nor am I very good at explaining things. Casting is important to understand though so I'm going to spit out a few more paragraphs on it to hopefully help those with a weak grasp on the subject understand what's occurring. (And so Mychaeel can correct me where I'm wrong. heh.) An object hierarchy is just that, a hierarchy. For instance WAWWeapon is derived from Weapon which is derived from .. ah, this is why Wiki markup is here.

Object >> Actor >> Inventory >> Weapon >> WAWWeapon

You can cast up or down that chain. For instance if you wanted to create a function that would work on a variety of objects based on what they were, but didn't want to write a function for each object type, you could cast a WAWWeapon to Object, similar to this:

<uscript> bool function TakeObject( Object a ) {

    // Do something here.

} </uscript>

A WAWWeapon isn't directly an object, and can't be passed to the function TakeObject without the compiler being told explicitly what we want to cast WAWWeapon as.

<uscript>

    ...
    TakeObject( Object( Pawn.Weapon ) );
    ...

</uscript>

Mychaeel: Note that this typecast is unnecessary – a Weapon is an Object and the compiler knows it, even without casting. (In fact, the compiler won't even compile that but tell you "Cast from 'Weapon' to 'Object' is unnecessary.")
Trystan: Okay, going up in the hierarchy doesn't require casting. Only going down. I did not know that.

This will take the Pawn.Weapon object, wrap it so it looks to be an Object class, and pass it to the function. It's important to note that the actual Pawn.Weapon isn't modified. The same data and functions are still available to it, and tests against IsA will still show it as a WAWWeapon class. Indeed, inside the TakeObject function we can recast a to WAWWeapon similarly:

<uscript>

    ...
    local WAWWeapon weapon;
    weapon = WAWWeapon( a );
    ...

</uscript>

You'd want to perform some tests to ensure that a was a valid object of the class you're casting too first, however. This is a generic function and as such it's not guaranteed that the object passed in is a WAWWeapon object.

TODO: Examine the below paragraph to see if variable/reference are being used properly. After coffee.

If you look at the definition of Pawn you'll find that it contains a variable named Weapon. We could change the variable that we store our weapon in, but that would break a good deal of existing code and probably not be worth the effort. Instead we leave the Weapon variable inside of Pawn as it is, and when necessary we cast it to the actual weapon type. This maintains compatibility with all the old code, and allows us to extend the class appropriately in our new code.

Reloading Continued

The point of all the prior discussion is to say that the Reload() function is defined in WAWWeapon, but before we can access it we must cast Pawn.Weapon to be an object of type WAWWeapon.

Reload() in WAWWeapon could function in one of two ways. For now we're simply going to have it set the weapon's current ammunition amount to the maximum ammunition amount it can support. (Also keep in mind that there are two types of ammunition for some weapons.)

<uscript> function Reload() {

    if( Ammo[0] != None )
         Ammo[0].AmmoAmount = Ammo[0].MaxAmmo;
    if( Ammo[1] != None )
         Ammo[1].AmmoAmount = Ammo[1].MaxAmmo;

} </uscript>

Simple. And written without testing.. let's see if it works. :)

Okay we now have a simple reload command which resets ammunition to the maximum amount a gun can handle. Now we actually want to set the amount of ammunition to what's in the clip. So we need to create a clip object to track how many clips we have.

<uscript> class MyClip extends Ammunition;

var() int MaximumAmmunition; // Tracks the maximum number of bullets a magazine can hold. var() int CurrentAmmunition; // Tracks the current number of bullets a magazine has.

replication {

   // Only CurrentAmmunition changes.  MaximumAmmunition should be static.
   reliable if( Role == ROLE_Authority )
       CurrentAmmunition;

}

// Does this magazine have ammunition? simulated function bool MagazineHasAmmo( ) {

   if( CurrentAmmunition > 0 )
       return true;
   return false;

}

simulated function bool UseAmmo( int AmountNeeded, optional bool bAmountNeededIsMax ) {

   // This basically says "give me everything you've got."
   if( bAmountNeededIsMax && CurrentAmmunition < AmountNeeded )
       AmountNeeded = CurrentAmmunition;
   if( CurrentAmmunition < AmountNeeded )          // Can't fire.
       return false;
   CurrentAmmunition -= AmountNeeded;              // Remove bullets from clip.
   if( Level.NetMode == NM_StandAlone || Level.NetMode == NM_ListenServer )
       CheckOutOfAmmo();
   return true;

}

DefaultProperties {

   MaximumAmmunition   =   25;
   CurrentAmmunition   =   25;

} </uscript>

Now we need to link this clip to our weapon. And we need one of each object for each clip we have in our inventory.

ummmm how?