Modme Forums

AAT Basics: Simple guide to new AAT Effects

Game Asset Reversing | Releases


ModmeBot:

Thread By: Pokepunch
Here are the basics of making new Alternate Ammo Types, also known as double PaP, effects.

Let's go over the .gsh file first. This is where things such as strings and values the AAT uses are defined for ease of use.

Here's the important part from Dead Wire's .gsh file:

#define ZM_AAT_DEAD_WIRE_LOCALIZED_STRING			"zmui_zm_aat_dead_wire"
#define ZM_AAT_DEAD_WIRE_ICON						"t7_icon_zm_aat_dead_wire"
#define ZM_AAT_DEAD_WIRE_PERCENTAGE 				0.20
#define ZM_AAT_DEAD_WIRE_COOLDOWN_ENTITY			0
#define ZM_AAT_DEAD_WIRE_COOLDOWN_ATTACKER			5
#define ZM_AAT_DEAD_WIRE_COOLDOWN_GLOBAL			2
#define ZM_AAT_DEAD_WIRE_OCCURS_ON_DEATH			true
#define ZM_AAT_DEAD_WIRE_DAMAGE_FEEDBACK_ICON		"t7_hud_zm_aat_deadwire"
#define ZM_AAT_DEAD_WIRE_DAMAGE_FEEDBACK_SOUND		"wpn_aat_dead_wire_plr"

In order we have:

Localized String - The name of the AAT displayed on the in game HUD. I don't think we can currently make localized strings so instead just use an actual string, like "Dead Wire" for example.

Icon - The icon displayed on the HUD for the AAT. This is an image in APE.

Percentage - The percentage chance that the AAT will trigger. In this example, for Dead Wire each shot that hits a zombie has a 20% chance of triggering the its effect given that the cooldowns are over...

Cooldown Entity - This isn't used, presumably because ATT effects tend to kill zombies so the cooldown wouldn't really work because the one zombies it checks against is dead.

Cooldown Attacker - Per player cooldown. A player won't be able to trigger the AAT again until this is over.

Cooldown Global - Level wide cooldown. Any player with this AAT won't be able to trigger it until this cooldown is over.

Occurs on Death - Whether or not the AAT can trigger when a zombie dies. Can't turn a dead zombie.

Damage Feedback Icon - The icon that displays when the AAT effect triggers. This is a material in APE.

Damage Feedback Sound - The sounds that plays when the ATT effect triggers.

The .gsh files often also hold any values used by the AAT effect.

Another definition we need that isn't located here for the base AAT effects are the AAT name definition. This is the internal name for the AAT effect which is essential. The base game AAT effects store their name definitions in a aat_zm.gsh, but for my custom AAT effects I've just placed it above the other definitions which works just fine:
#define ZM_AAT_LUCKY_DRAW_NAME						"zm_aat_lucky_draw"

Having all these values defined it handy because they are used in both the .gsc and .csc files where the AAT effects are registered to the scripts that handle the AAT effect logic:
.gsc:
REGISTER_SYSTEM( ZM_AAT_LUCKY_DRAW_NAME, &__init__, "aat" )

function __init__()
{
	if ( !IS_TRUE( level.aat_in_use ) )
	{
		return;
	}

	aat::register( ZM_AAT_LUCKY_DRAW_NAME, ZM_AAT_LUCKY_DRAW_PERCENTAGE, ZM_AAT_LUCKY_DRAW_COOLDOWN_ENTITY, ZM_AAT_LUCKY_DRAW_COOLDOWN_ATTACKER, ZM_AAT_LUCKY_DRAW_COOLDOWN_GLOBAL,
	               ZM_AAT_BLAST_FURNACE_OCCURS_ON_DEATH, &result, ZM_AAT_BLAST_FURNACE_DAMAGE_FEEDBACK_ICON, ZM_AAT_BLAST_FURNACE_DAMAGE_FEEDBACK_SOUND );
}

.csc:
REGISTER_SYSTEM( ZM_AAT_LUCKY_DRAW_NAME, &__init__, undefined )

function __init__()
{
	if ( !IS_TRUE( level.aat_in_use ) )
	{
		return;
	}
	
	aat::register( ZM_AAT_LUCKY_DRAW_NAME, ZM_AAT_LUCKY_DRAW_LOCALIZED_STRING, ZM_AAT_LUCKY_DRAW_ICON );
}

First a check is made to see if AAT effects are enabled for the level or not. Afterwards the AAT is registered to the main AAT script using all the values we defined before, with a couple of exceptions. The only apparent outlier is "@result" which is the function that actual does the effect, like Dead Wire electrocuting zombies or Turned turning zombies etc. In other words here is where the fun stuff happens! You can do anything you want here, provided you have the knowledge to do so.

Another function call is made in some cases, this called the Validation function. The Validation function is only used by Turned and Fireworks, both making extra checks to determine if the zombie the effect would trigger on is inside the playable area or doing something scripted like traversing. If you need this for your effect you can find examples in the Fireworks and Turned .gsc files. The Validation function is registered at the end of the registration like so:
_zm_aat_turned.gsc:
aat::register( ZM_AAT_TURNED_NAME, ZM_AAT_TURNED_PERCENTAGE, ZM_AAT_TURNED_COOLDOWN_ENTITY, ZM_AAT_TURNED_COOLDOWN_ATTACKER, ZM_AAT_TURNED_COOLDOWN_GLOBAL,
	               ZM_AAT_TURNED_OCCURS_ON_DEATH, &result, ZM_AAT_TURNED_DAMAGE_FEEDBACK_ICON, ZM_AAT_TURNED_DAMAGE_FEEDBACK_SOUND, &turned_zombie_validation );

When you've made a new AAT you'll need to add it to your mapname.gsc and .csc by adding this line in both files before the main function:
#using scripts\zm\aats\zm_aat_lucky_draw;

I'm including a new AAT here for you to download and use as you wish (with credit of course).

Lucky Draw picks one of the existing AAT effects when it triggers meaning you get a random effect each time. This even applies to any custom AAT effects you might add to your map. Instructions are included with the download. Have fun!

Download


ModmeBot:

Reply By: ProRevenge
Fantastic work, great to see such a detailed guide for something like this :)
Making Script easy to understand and explaining what its doing and how its working like you've done here is what we need more of around here haha, for people like me who struggle to script

When my map eventually gets close to completion ill look into this and maybe do something cool for a new AAT


ModmeBot:

Reply By: XxRaPiDK3LLERxX
+1 win
I understand everything you said, you really explained it well, probably try this sometime :)
Hope you'll make more of these tutorials!


ModmeBot:

Reply By: AUsernameLz443
I have an AAT offset after adding Lucky Draw.
Any way to fix the aat offset?
Here's an example of my issue

Claimed Type = Actual Type

Thunderwall = Fireworks
Fireworks = Deadwire
Deadwire = Blastfurnace
Turned = Thunderwall
Blastfurnace = Lucky Draw
Lucky Draw = Turned

If my KRM came fresh out of the puncher and said it's alternate ammo type is Lucky Draw, then the KRM would only shoot turned. If KRM came out saying it had Fireworks, then the gun would only shoot deadwire. If my KRM said it had Blast Furnace on it; then I'd have Lucky Draw. Any way to fix this so that lucky draw is lucky draw and blast furnace is blast furnace, and so on?
Note; I have Brutus in my map and I found this in the configs;

function aat_override()
{
	while( isDefined(self) )
	{
		archetype = self.archetype; 
		self.aat_cooldown_start[ZM_AAT_BLAST_FURNACE_NAME] = GetTime() ;  // always force the cooldown to be less than current time
		self.aat_cooldown_start[ZM_AAT_DEAD_WIRE_NAME] = GetTime() ;  // always force the cooldown to be less than current time
		self.aat_cooldown_start[ZM_AAT_FIRE_WORKS_NAME] = GetTime() ;  // always force the cooldown to be less than current time
		self.aat_cooldown_start[ZM_AAT_THUNDER_WALL_NAME] = GetTime() ;  // always force the cooldown to be less than current time
		self.aat_cooldown_start[ZM_AAT_TURNED_NAME] = GetTime() ;  // always force the cooldown to be less than current time
		self.no_powerups = true; 
		self.b_octobomb_infected = true; 
		
		level.aat[ ZM_AAT_FIRE_WORKS_NAME ].immune_trigger[ self.archetype ] = true;
		level.aat[ ZM_AAT_FIRE_WORKS_NAME ].immune_result_direct[ self.archetype ] = true;
		level.aat[ ZM_AAT_FIRE_WORKS_NAME ].immune_result_indirect[ self.archetype ] = true;
		
		level.aat[ ZM_AAT_BLAST_FURNACE_NAME ].immune_trigger[ self.archetype ] = true;
		level.aat[ ZM_AAT_BLAST_FURNACE_NAME ].immune_result_direct[ self.archetype ] = true;
		level.aat[ ZM_AAT_BLAST_FURNACE_NAME ].immune_result_indirect[ self.archetype ] = true;
		
		level.aat[ ZM_AAT_DEAD_WIRE_NAME ].immune_trigger[ self.archetype ] = true;
		level.aat[ ZM_AAT_DEAD_WIRE_NAME ].immune_result_direct[ self.archetype ] = true;
		level.aat[ ZM_AAT_DEAD_WIRE_NAME ].immune_result_indirect[ self.archetype ] = true;
		
		level.aat[ ZM_AAT_THUNDER_WALL_NAME ].immune_trigger[ self.archetype ] = true;
		level.aat[ ZM_AAT_THUNDER_WALL_NAME ].immune_result_direct[ self.archetype ] = true;
		level.aat[ ZM_AAT_THUNDER_WALL_NAME ].immune_result_indirect[ self.archetype ] = true;
		
		level.aat[ ZM_AAT_TURNED_NAME ].immune_trigger[ self.archetype ] = true;
		level.aat[ ZM_AAT_TURNED_NAME ].immune_result_direct[ self.archetype ] = true;
		level.aat[ ZM_AAT_TURNED_NAME ].immune_result_indirect[ self.archetype ] = true;
		
		wait(0.05); 
	}
	
		level.aat[ ZM_AAT_FIRE_WORKS_NAME ].immune_trigger[ archetype ] = false;
		level.aat[ ZM_AAT_FIRE_WORKS_NAME ].immune_result_direct[ archetype ] = false;
		level.aat[ ZM_AAT_FIRE_WORKS_NAME ].immune_result_indirect[ archetype ] = false;
		
		level.aat[ ZM_AAT_BLAST_FURNACE_NAME ].immune_trigger[ archetype ] = false;
		level.aat[ ZM_AAT_BLAST_FURNACE_NAME ].immune_result_direct[ archetype ] = false;
		level.aat[ ZM_AAT_BLAST_FURNACE_NAME ].immune_result_indirect[ archetype ] = false;
		
		level.aat[ ZM_AAT_DEAD_WIRE_NAME ].immune_trigger[ archetype ] = false;
		level.aat[ ZM_AAT_DEAD_WIRE_NAME ].immune_result_direct[ archetype ] = false;
		level.aat[ ZM_AAT_DEAD_WIRE_NAME ].immune_result_indirect[ archetype ] = false;
		
		level.aat[ ZM_AAT_THUNDER_WALL_NAME ].immune_trigger[ archetype ] = false;
		level.aat[ ZM_AAT_THUNDER_WALL_NAME ].immune_result_direct[ archetype ] = false;
		level.aat[ ZM_AAT_THUNDER_WALL_NAME ].immune_result_indirect[ archetype ] = false;
		
		level.aat[ ZM_AAT_TURNED_NAME ].immune_trigger[ archetype ] = false;
		level.aat[ ZM_AAT_TURNED_NAME ].immune_result_direct[ archetype ] = false;
		level.aat[ ZM_AAT_TURNED_NAME ].immune_result_indirect[ archetype ] = false;
}