Forum rules
Please don't start threads here asking for help. This forum is not for requesting guides, only for posting them. If you need help, the Editing forum is for you.
Here's a neat magic trick:
1. Load up a weapon mod that features reloading.
2. Empty your gun and then drop it.
3. Pick up another gun of the same kind.
4. Make sure your new gun is fully loaded.
5. Drop the new gun and pick up the old one again.
6. Your old, emptied gun is now magically fully loaded.
This is usually unnoticeable in a typical mod where there's no reason to drop a weapon once you have it, but it can totally break a mod where a weapon's inherent properties can change with use - wear and tear, experience levels, ammo capacity, etc. and you might well be expected to swap out different guns of the same kind depending on need and circumstances.
To get around this without ZScript, you'd need to have a convoluted series of steps in the gun's spawn state that looks for the nearest player and forcibly extracts all the player's loaded-ammo inventory items, then replaces itself with a custominventory item that on pickup will restore the exact same numbers of those items for the player picking it up (which itself was a lot more complicated before A_SetInventory was a thing). This leads to all sorts of other weird side effects and edge cases where you'd have to worry about replacing the real weapon actor, the custominventory respawning properly in multiplayer, etc. which creates dozens of lines of additional code just to support this already edge case. Here and here are examples of the simplest and the most complicated such pickup actors one might expect, keeping in mind these particular actors represent only part of the required code.
In ZScript, you can get around all of this by making your loaded ammo counter a variable within the weapon itself. That way, you can drop it and pick it back up anytime thereafter, however many other weapons of the same kind you've reloaded, and it will have exactly the same amount of ammo it had when you dropped it (unless another player has picked it up and messed with it.)
With all that in mind, let's create a very basic reloadable pistol. I won't bother this time with a step-by-step evolution - hopefully the comments will make things clear enough.
// optional: set a constant for your max loaded ammo
// this may not be appropriate for more advanced stuff
// like loading different-sized mags
const PIST_MAXAMMO = 15;
class reloadpistol:pistol replaces shotgun //just so we have lots of them on any given map to test
{
int loaded; // here's the variable!
default
{
weapon.slotnumber 2;
+rollsprite +rollcenter // we'll need this for the spawn state
+weapon.noalert // so you don't alert firing an empty
// since we're not accessing the ammo item itself
// the need for alt will be clear with the HUD hack
+weapon.ammo_optional +weapon.alt_ammo_optional
weapon.ammouse1 0; weapon.ammouse2 0;
}
states
{
nope:
// i like to set up a generic state to dump things into when things must abort.
// no ammo, unloaded gun, etc. - all these can point here
---- A 1 A_WeaponReady(WRF_NOFIRE);
goto ready;
ready:
// need to allow reload and zoom
PISG A 1 A_WeaponReady(WRF_ALLOWRELOAD|WRF_ALLOWZOOM);
wait;
fire:
// note that "invoker" must always be used to refer to the weapon,
// otherwise it will assume it's the player.
PISG A 4
{
if(invoker.loaded<1)
{
// go to the abort state if no ammo loaded
player.setpsprite(PSP_WEAPON,invoker.findstate("nope"));
}
}
PISG B 6
{
A_FirePistol();
// deplete loaded ammo and alert monsters
invoker.loaded--;
A_AlertMonsters();
}
PISG C 4;
PISG B 5 A_ReFire();
goto ready;
reload:
TNT1 A 35
{
// figure out how much to load
// the lesser of the amount of "Clip" available to load
// and the amount that would be required to top off the mag
// (for more advanced stuff you could empty the pistol first,
// but let's keep it simple for the sake of the tutorial)
int toload = min
(
countinv("Clip"),
PIST_MAXAMMO-invoker.loaded
);
//abort if fully loaded or no spare ammo
if(toload<1){
player.setpsprite(PSP_WEAPON,invoker.findstate("nope"));
return;
}
// and then finally do it
invoker.loaded+=toload;
A_TakeInventory("Clip",toload,TIF_NOTAKEINFINITE);
A_Log("This is the greatest reload animation ever!");
}
goto ready;
zoom:
// optional: allow player to extract spare ammo from discarded guns.
// by no means do you need to use zoom, it's just the next button available
// that i'm using for the sake of this tutorial.
TNT1 A 35{
// abort if gun is empty or maxed out
// AJII(...0...) is still the best way to check if maxed out!
if(
invoker.loaded < 1
|| A_JumpIfInventory("Clip",0,"null")
){
player.setpsprite(PSP_WEAPON,invoker.findstate("nope"));
return;
}
// figure out how much to unload
// there are lots of ways to do this
// for simplicity's sake, i'll opt for the player
// taking exactly the number of rounds they can carry
// and leaving the rest in the gun.
// figure out what the maximum is
// first, find the inventory actor representing Clip in the player
// or refer to default
int maxclip=0;
inventory playerclips=findinventory("Clip");
if(!playerclips) maxclip=getdefaultbytype("Clip").maxamount;
else maxclip=playerclips.maxamount;
// the amount taken should be the less of
// the amount actually loaded and
// the amount of space remaining in the player's inventory
int tounload = min
(
invoker.loaded,
maxclip-countinv("Clip")
);
// and then finally do it
invoker.loaded-=tounload;
A_GiveInventory("Clip",tounload);
A_Log("This is the greatest unload animation ever!");
}
goto ready;
spawn:
PIST A -1 nodelay
{
// optional visual aid:
// if the pistol is unloaded, turn the sprite upside down
// note that "invoker" must be used to refer to the weapon
// in ALL anonymous functions within states
if(invoker.loaded<1) invoker.roll=180;
else invoker.roll=0;
}
stop;
}
// optional: start the weapon loaded
// again, since this is not a state, no "invoker." needed
override void beginplay(){
super.beginplay();
if(bdropped) loaded=random(0,15);
else loaded=15;
}
}
Now this works, but we've got 2 issues:
1. The amount in the gun does not show up on your HUD.
2. Dropping and picking up pistols to loot is really tedious.
For issue 1, we can either redo the HUD entirely, or create a dummy ammo item.
weapon.ammotype1 "relpistmagtracker";
weapon.ammotype2 "Clip";
weapon.ammogive1 0; // to keep pistols from being wasted uselessly
For issue 2, we can add the following Touch() override, by sticking this into the weapon actor definition, which basically replicates the unload function but drops a clip instead of adding it to your inventory:
override void Touch(actor user)
{
if(
user.countinv("reloadpistol")
){
if(loaded > 0) // note that we do NOT need "invoker." here!
{
// drop a separate Clip pickup for the amount loaded
// then set own loaded value to zero
A_DropItem("Clip",loaded);
loaded=0;
// copypaste "figure out what the maximum is"
// then make a few changes
// the biggest change: adding "user." to specify
// it's the player not the weapon we're looking at
int maxclip=0;
inventory playerclips=user.findinventory("Clip");
if(!playerclips) maxclip=getdefaultbytype("Clip").maxamount;
else maxclip=playerclips.maxamount;
// similarly, remove "invoker" here
int tounload = min
(
loaded,
maxclip-user.countinv("Clip")
);
// now drop the appropriate amount
if(tounload > 0)
{
loaded-=tounload;
A_DropItem("Clip",tounload);
// or use user.A_GiveInventory and explicitly
// set flashes and logs for feedback
}
// update weapon appearance and abort touch sequence
setstatelabel("spawn");
}
return;
}
// otherwise, proceed normally
super.Touch(user);
}
(Obviously all this can apply to any tracked variable you want to follow a particular weapon, besides loaded ammo: you can track wear and tear, whether it has a certain attachment installed (grenade launcher, full shotgun choke, sawed-off barrel, bayonet, scope, night sights, flashlight), any settings (semi, full-auto, zoom level etc.), whether a certain part is broken, even a custom paint job specific to each team. You can even track the original owner who brought the weapon to the map (if any) and implement a sci-fi biometric security system (yes you can use that rocket launcher but you'll be using the zombie's hand to pull the trigger so accuracy might be affected) - [wiki=ZScript_variables]any variable that can be defined for an actor can be exploited[/wiki].)