Weapon Mutator Tutorial
This page is one of a series of UnrealScript Lessons.
The UT2003 version of this tutorial is available at Weapon Mutator Tutorial (UT2003). This version has been refactored and updated for UT2004.
A Weapon Mutator – Making a more explosive Minigun
In UT2004, the weapon code is more subdivided than in UT, with seperate classes for the weapon, the weapon's pickup, the ammo, the fire, the damage, etc. At first, it's actually a bit annoying – but in practice quite flexible, since you get a lot of mileage out of subclassing only a few files. Here we will attempt to create a minigun that delivers a much more powerful alt fire. Since all we want to do is change the alt-fire of the Minigun, we are going to extend the following classes:
- Minigun >> MinigunHE
- The new weapon, to reference the new alt-fire and pickup class.
- MinigunAltFire >> MinigunHEAltFire
- The new explosive alt-fire.
- MinigunPickup >> MinigunHEPickup
- The pickup class for the new weapon.
- Mutator >> MutMinigunHE
- A weapon/pickup substitution mutator that replaces the regular Minigun with our modified version.
MinigunHE.uc
class MinigunHE extends Minigun; //============================================================================= defaultproperties { FireModeClass(1)=class'MinigunHEAltFire' PickupClass=class'MinigunHEPickup' }
defaultproperties
Since you generally cannot (or at the VERY least, SHOULD NOT) edit the existing classes and recompile, to make any changes you typically must subclass and override functions and properties to implement your changes. So basically, this weapon class only serves as a vehicle to get our new alt-fire behavior into the Minigun. To do this, we create a new weapon that inherits all the properties and behavior of Minigun, then override two default properties.
FireModeClass(1) | Each weapon in UT2004 has two FireModeClasses: primary (0) and alt (1). Here, we substitute our new alt-fire class. |
PickupClass | The PickupClass defines the item you drop when you get killed and your weapon drops to the ground. In this case, we override with our new pickup class. |
MinigunHEAltFire.uc
class MinigunHEAltFire extends MinigunAltFire; var() class<Actor> ExplosionClass; var() class<Projector> ExplosionDecalClass; //============================================================================= function BlowUp(vector HitLocation) { Instigator.HurtRadius(15, 45, DamageType, 250, HitLocation ); Instigator.MakeNoise(1.0); } //============================================================================= simulated function Explode(vector HitLocation, vector HitNormal) { Instigator.PlaySound(sound'WeaponSounds.BExplosion3',,2.5*TransientSoundVolume); if ( Instigator.EffectIsRelevant(HitLocation,false) ) { Instigator.Spawn(ExplosionClass,,,HitLocation + HitNormal*16,rotator(HitNormal)); Instigator.Spawn(ExplosionDecalClass,,,HitLocation, rotator(HitNormal)); } BlowUp(HitLocation); } //============================================================================= simulated function SpawnBeamEffect(Vector Start, Rotator Dir, Vector HitLocation, Vector HitNormal, int ReflectNum) { Super.SpawnBeamEffect( Start, Dir, HitLocation, HitNormal, ReflectNum); Explode( HitLocation, HitNormal ); } //============================================================================= defaultproperties { ExplosionClass=class'NewExplosionB' ExplosionDecalClass=class'RocketMark' }
defaultproperties
The ExplosionClass and ExplosionDecalClass properties simply indicate what explosion and decal scorchmark effects will be spawned on the hit surface in Explode().
SpawnBeamEffect()
A few levels up in the hierarchy, in the InstantFire class, DoTrace() is called to perform a raycast for an instant-hit (a.k.a hitscan) weapon fire. Then SpawnBeamEffect(), an empty function in InstantFire, is called with several relevant values, including the hit location and the hit surface normal. In this function, you can spawn hit effects, scorch-mark decals, alter reflected projectiles, etc. In our version of this function, we simply pass these values to our new Explode() function, after calling the parent class (Super) version of the function, of course.
Explode()
Mimicking the other UT2004 fire classes, our Explode() function plays a sound, spawns explosion and decal FX on all clients which deem this hit location relevant, and call our BlowUp() to finish up. Note that this function is simulated, meaning it will be called on each client (i.e. each client will hear and see the explosion, if relevant).
Geist: Hmmmm. This part is a little fishy right now. This whole mod needs to be fitted for networking, and I suspect this function and/or its description will change. As-is, I don't think this function will ever be called client-side, because it is not called from another simulated function. I will confirm and correct this soon.
porkmanii: In UT2004, WeaponFire is derived from Object, not Actor. Non-Actor Objects have no concept of replication. Any function of any non-Actor Object can be executed client-side with or without the simulated keyword. It is also worth noting that the server and client have independant WeaponFire objects - being Objects, they cannot be replicated.
BlowUp()
Again, this function is merely mimicking other UT2004 firing code. This function deals splash damage to nearby actors, then calls MakeNoise() to alert nearby AI of the racket you're making with this weapon. Note that this function is NOT simulated (even though it is being called from a simulated function), meaning it will only be executed on the server. This only makes sense, because only the server should be dealing damage, and the AI's only live on the server.
MinigunHEPickup.uc
class MinigunHEPickup extends MinigunPickup; //============================================================================= defaultproperties { InventoryType=class'MinigunHE' PickupMessage="You got the Minigun HE." }
defaultproperties
This pickup class is very similar to the new MinigunHE weapon class. It simply inherits all the properties/behavior of MinigunPickup, but overrides a few properties.
InventoryType | An object of this class is spawned into your inventory when a player activates (touches) this pickup. |
PickupMessage | No-brainer. This is the message the player sees when activating/touching this pickup. |
MutMinigunHE.uc
class MutMinigunHE extends Mutator config(user); //============================================================================= function bool CheckReplacement( Actor Other, out byte bSuperRelevant ) { local int i; local WeaponLocker L; bSuperRelevant = 0; if ( xWeaponBase(Other) != None ) { if ( string( xWeaponBase(Other).WeaponType ) ~= "XWeapons.Minigun" ) { xWeaponBase(Other).WeaponType = class'MinigunHE'; return false; } } else if ( WeaponPickup(Other) != None ) { if ( string(Other.Class) ~= "XWeapons.MinigunPickup" ) { ReplaceWith( Other, "MinigunHEPickup" ); return false; } } else if ( WeaponLocker(Other) != None ) { L = WeaponLocker(Other); for (i = 0; i < L.Weapons.Length; i++) { if ( string( L.Weapons[i].WeaponClass ) ~= "XWeapons.Minigun" ) L.Weapons[i].WeaponClass = class'MinigunHE'; } } return true; } //============================================================================= defaultproperties { GroupName="Minigun HE" FriendlyName="Minigun HE" Description="Change Minigun alt-fire rounds to High Explosive rounds." }
Finally, we add a mutator to replace that old Minigun with the shiny new one.
defaultproperties
These are fairly self-explanatory (i.e. you should know what these are by now!) ...
GroupName | The name of the group that this mutator belongs to. |
FriendlyName | The name displayed in the Mutators list. |
Description | The discription displayed when you highlight this mutator in the list. |
CheckReplacement()
This is a Mutator function which hits every actor as it initializes, and asks if it should be replaced. It's an extremely powerful call and if implemented incorrectly, it can completely crash your game. Here, it just looks for weapon bases, pickups, and weaponlockers that reference the Minigun and replaces them with our new classes.
Conclusion
Now compile the package and fire up UT2004, grab a MinigunHE and let loose on those hapless bots with the alt-fire. Mmmmm, cuts like a hot knife through butter!
Related Topics
- UnrealScript Lessons – all the UnrealScript tutorials
- UnrealScript – all the reference pages
- Mutator Topics – more on mutators
- Making Mods – more on the organizational and social aspects
- Creating A New Weapontype – more on weapon creation/modification
Comments
Geist: In writing this UT2004 version, I tried to retain as much of the original UT2003 tutorial as I thought was relevant. I think I got all the discussion refactored in, and this version does not depend on any xxxPak resource files. It's not perfect, but it compiles and seems to work (in a local game, at least). As mentioned above, this script is still likely to work less than 100% in a net game. I will try to rectify this ASAP. Comments and suggestions for improvement (or juet improve it yourself) are welcome!
RegularX: Good work. I took the liberty of adjusting some of the intro paragraph to remove some of my original text about Freehold, etc., which made much more sense when these were just UTutes off my website. The network problem with this gun turned out to be the fact that Epic here hands off the creation of such effects to the attachment class, I'm assuming because it has to be replicated (to be seen in the pawn's hands). I had based this code off my earlier UT version. I have a network friendly version, but sadly it's for my unreleased mod UXL and contains tons of UXL references. The gist would be something like adding the attachment class, with an AltFire effect:
class MinigunHEAttachment extends MinigunAttachment; var() class<Actor> AltEffect;
And then down in ThirdPersonEffects, update it to be like:
if ( FiringMode == 0 ) { Spawn(class'HitEffect'.static.GetHitEffect(mHitActor, mHitLocation, mHitNormal),,, mHitLocation, Rotator(mHitNormal)); } else { Spawn(AltEffect,,, mHitLocation, Rotator(mHitNormal)); }
And then define AltEffect in the default props as something like AltEffect=class'XEffects.ExploWallHit' ... I can try compiling this tonight, but if you're around a computer today you might see if this works. Cheers!
Geist: I really wish they had saved all these in string/class properties to start with. Then I wouldn't have to copy/paste that whole function just to substitute a new effect. It would make maintenence easier too, if a patch and new script were to break things. Ah well, not too much of a hassle, but still a hassle. Thanks for the info, RegularX. I'll probably try it out tomorrow (and track down any other non-netcode-friendly stuff).
Stagger: This seems to have the following problems- (A) Explosion sound plays at location of person firing weapon, not at where hit occurs. (B) HurtRadius doesn't actually hurt anything. (C) Explosion decal isn't applied.
RegularX: I think A & C could also be solved via the attachment class. Are you sure about B? i oculd have sworn that worked both on and offline before. I can look at the UXL and see if there is a functional difference though.
Geist: I just cleaned this up a bit (e.g. rearranged the return values for logic clarity in CheckReplacement(), etc) and added support for Onslaught weapon lockers. I've been a bit too busy at work to test/fix the netcode-relative stuff (i.e. to add the attachement class stuff), but that is still coming soon. I will say that I've tried exaggerated values in HurtRadius(), and at least in a local bot game, it definitely does hurt them. But as mentioned above, it probably doesn't work in a net game, because the chain of simulated functions is probably broken. More soon, as time permits...
Ferazel2001: This is not compiling for me, "Error, Bad or missing expression in 'If'
(this is referring to the first of the two if's)
if (xWeaponBase(Other) != None) { if ( string( xWeaponBase(Other).WeaponType ) ~= "XWeapons.ShieldGun" ) { ...
I dunno... someone might have changed something, but this doesnt work... Thanks,
Geist: Hey Ferazel, make sure you didn't mistype the Other parameter in your CheckReplacement() declaration. If the compiler doesn't recognize xWeaponBase or Other, you'll get that error message. If Other is correct, then make sure it parses the xGame package when you compile (that's where xWeaponBase is defined). As I mentioned before, all this script compiles.
Bob_The_Beheader: Umm...What would I do (or where would I look) if I wanted to use a different model for the MinigunHE? Also, wouldn't it be nice if there were some comments in this source code...
RangerWhelk: What code do i put in to make the mutator checkreplace ammo?
porkmanii: I've added a comment about WeaponFire Objects (not Actors!) in UT2004, their lack of replication, and the fact the simulated keyword has no effect. Perhaps this tidbit of information could be better placed?
Anonymous: In response to your question RangerWhelk:
... else if ( UTAmmoPickup(Other) != None ) { if ( string(Other.Class) ~= "XWeapons.MinigunAmmoPickup" ) { UTAmmoPickup(Other).Transmogrify(class'MinigunHEAmmoPickup'); return false; } }
I've found that it works well enough, thought I'm not sure about its network compatability.
RangerWhelk: Yes that works. Thankyou very much. I've tested it and it seems to work online and lan. Also heres some script that will replace the starting weapons.
function string GetInventoryClassOverride(string InventoryClassName) { if (InventoryClassName == "XWeapons.ShieldGun") InventoryClassName = "YourPackage.YourGun"; if (InventoryClassName == "XWeapons.AssaultRifle") InventoryClassName = "YourPackage.YourGun"; return Super.GetInventoryClassOverride(InventoryClassName); }
Raimian: where can i get a full example script for replacing the starting weapons??