// AAT stands for Alternative Ammunition Types #using scripts\shared\array_shared; #using scripts\shared\callbacks_shared; #using scripts\shared\clientfield_shared; #using scripts\shared\damagefeedback_shared; #using scripts\shared\spawner_shared; #using scripts\shared\system_shared; #using scripts\shared\util_shared; #using scripts\zm\_zm; #namespace aat; function autoexec __init__sytem__() { system::register("aat",&__init__,&__main__,undefined); } function private __init__() { if ( !( isdefined( level.aat_in_use ) && level.aat_in_use ) ) { return; } level.aat_initializing = true; level.aat = []; // Add "none" for HUD elements level.aat[ "none" ] = SpawnStruct(); level.aat[ "none" ].name = "none"; level.aat_reroll = []; callback::on_connect( &on_player_connect ); spawners = GetSpawnerArray(); foreach ( spawner in spawners ) { spawner spawner::add_spawn_function( &aat_cooldown_init ); } level.aat_exemptions = []; zm::register_vehicle_damage_callback( &aat_vehicle_damage_monitor ); callback::on_finalize_initialization( &finalize_clientfields ); /# level thread setup_devgui(); #/ } function __main__() { if ( !( isdefined( level.aat_in_use ) && level.aat_in_use ) ) { return; } zm::register_zombie_damage_override_callback( &aat_response ); } function private on_player_connect() { self.aat = []; self.aat_cooldown_start = []; keys = GetArrayKeys( level.aat ); foreach ( key in keys ) { self.aat_cooldown_start[key] = 0; } self thread watch_weapon_changes(); /# //self thread aat_debug_text_display_init(); #/ } function private setup_devgui() { /# waittillframeend; SetDvar( "aat_acquire_devgui", "" ); aat_devgui_base = "devgui_cmd \"Player/AAT/"; keys = GetArrayKeys( level.aat ); foreach ( key in keys ) { if ( key != "none" ) { AddDebugCommand( aat_devgui_base + key + "\" \"set " + "aat_acquire_devgui" + " " + key + "\" \n"); } } AddDebugCommand( aat_devgui_base + "Clear Current\" \"set " + "aat_acquire_devgui" + " " + "none" + "\" \n"); level thread aat_devgui_think(); #/ } /# function private aat_devgui_think() { for ( ;; ) { aat_name = GetDvarString( "aat_acquire_devgui" ); if ( aat_name != "" ) { for ( i = 0; i < level.players.size; i++ ) { if ( aat_name == "none" ) { level.players[i] thread remove( level.players[i] GetCurrentWeapon() ); } else { level.players[i] thread acquire( level.players[i] GetCurrentWeapon(), aat_name ); } level.players[i] thread aat_set_debug_text( aat_name, false, false, false ); } } SetDvar( "aat_acquire_devgui", "" ); wait( 0.5 ); } } #/ function private aat_debug_text_display_init() { /# self.aat_debug_text = NewClientHudElem( self ); self.aat_debug_text.elemType = "font"; self.aat_debug_text.font = "objective"; self.aat_debug_text.fontscale = 1.8; self.aat_debug_text.horzAlign = "left"; self.aat_debug_text.vertAlign = "top"; self.aat_debug_text.alignX = "left"; self.aat_debug_text.alignY = "top"; self.aat_debug_text.x = 15; self.aat_debug_text.y = 15; self.aat_debug_text.sort = 2; self.aat_debug_text.color = ( 1, 1, 1 ); self.aat_debug_text.alpha = 1; self.aat_debug_text.hidewheninmenu = true; self thread aat_debug_text_display_monitor(); #/ } function private aat_debug_text_display_monitor() { /# self endon( "disconnect" ); while ( true ) { self waittill( "weapon_change", weapon ); name = "none"; if ( IsDefined( self.aat[weapon] ) ) { name = self.aat[weapon]; } self thread aat_set_debug_text( name, false, false, false ); } #/ } function private aat_set_debug_text( name, success, success_reroll, fail ) { /# self notify( "aat_set_debug_text_thread" ); self endon( "aat_set_debug_text_thread" ); self endon( "disconnect" ); if( !IsDefined(self.aat_debug_text) ) { return; } percentage = "N/A"; if ( IsDefined( level.aat[name] ) && name != "none" ) { percentage = level.aat[name].percentage; } self.aat_debug_text fadeOverTime( 0.05 ); self.aat_debug_text.alpha = 1; self.aat_debug_text SetText( "AAT: " + name + " PCT: " + percentage ); if ( success ) { self.aat_debug_text.color = ( 0, 1, 0 ); } else if ( success_reroll ) { self.aat_debug_text.color = ( 0.8, 0, 0.8 ); } else if ( fail ) { self.aat_debug_text.color = ( 1, 0, 0 ); } else { self.aat_debug_text.color = ( 1, 1, 1 ); } wait( 1 ); self.aat_debug_text fadeOverTime( 1 ); self.aat_debug_text.color = ( 1, 1, 1 ); if ( "none" == name ) { self.aat_debug_text.alpha = 0; } #/ } // self = ai or vehicle actor function aat_cooldown_init() { self.aat_cooldown_start = []; keys = GetArrayKeys( level.aat ); foreach ( key in keys ) { self.aat_cooldown_start[key] = 0; } } // Returns damage for vehicle_damage_override function // self = vehicle actor function private aat_vehicle_damage_monitor( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) { willBeKilled = ( self.health - iDamage ) <= 0; if ( ( isdefined( level.aat_in_use ) && level.aat_in_use ) ) { self thread aat_response( willBeKilled, eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, damageFromUnderneath, vSurfaceNormal ); } return iDamage; } function get_nonalternate_weapon( weapon ) { if ( IsDefined( weapon ) && weapon.isAltMode ) { return weapon.altWeapon; } return weapon; } // Called from _zm.gsc // self = ai actor function aat_response( death, inflictor, attacker, damage, flags, mod, weapon, vpoint, vdir, sHitLoc, psOffsetTime, boneIndex, surfaceType ) { if ( !IsPlayer( attacker ) ) { return; } if ( mod != "MOD_PISTOL_BULLET" && mod != "MOD_RIFLE_BULLET" && mod != "MOD_GRENADE" && mod != "MOD_PROJECTILE" && mod != "MOD_EXPLOSIVE" && mod != "MOD_IMPACT" ) { return; } weapon = get_nonalternate_weapon( weapon ); name = attacker.aat[weapon]; if ( !IsDefined( name ) ) { return; } if ( death && !level.aat[name].occurs_on_death ) { return; } if ( !isdefined( self.archetype ) ) { return; } // if self's archetype is registered in immune_trigger, AAT check is completely bypassed if ( ( isdefined( level.aat[name].immune_trigger[ self.archetype ] ) && level.aat[name].immune_trigger[ self.archetype ] ) ) { return; } now = GetTime() / 1000; if ( now <= self.aat_cooldown_start[name] + level.aat[name].cooldown_time_entity ) { return; } if ( now <= attacker.aat_cooldown_start[name] + level.aat[name].cooldown_time_attacker ) { return; } if ( now <= level.aat[name].cooldown_time_global_start + level.aat[name].cooldown_time_global ) { return; } if ( isdefined( level.aat[name].validation_func ) ) { if ( !self [[level.aat[name].validation_func]]() ) { return; } } success = false; reroll_icon = undefined; percentage = level.aat[name].percentage; /# aat_percentage_override = GetDvarFloat( "scr_aat_percentage_override" ); if ( aat_percentage_override > 0.0 ) { percentage = aat_percentage_override; } #/ if ( percentage >= RandomFloat( 1 ) ) { success = true; attacker thread aat_set_debug_text( name, true, false, false ); } if ( !success ) { keys = GetArrayKeys( level.aat_reroll ); keys = array::randomize( keys );// randomize the keys so players don't assume one reroll is better than another just because of registration order foreach ( key in keys ) { if ( attacker [[level.aat_reroll[key].active_func]]() ) { for ( i = 0; i < level.aat_reroll[key].count; i++ ) { if ( percentage >= RandomFloat( 1 ) ) { success = true; reroll_icon = level.aat_reroll[key].damage_feedback_icon; attacker thread aat_set_debug_text( name, false, true, false ); break; } } } if ( success ) { break; } } } if ( !success ) { attacker thread aat_set_debug_text( name, false, false, true ); return; } level.aat[name].cooldown_time_global_start = now; attacker.aat_cooldown_start[name] = now; self thread [[level.aat[name].result_func]]( death, attacker, mod, weapon ); attacker thread damagefeedback::update_override( level.aat[name].damage_feedback_icon, level.aat[name].damage_feedback_sound, reroll_icon ); } /@ "Name: register( , , , , , , , , , )" "Summary: Register an AAT "Module: AAT" "MandatoryArg: Unique name to identify the AAT. "MandatoryArg: Float value representing the percentage chance that the result occurs. "MandatoryArg: Cooldown time per entity where we don't check for the same result from the same player/AAT combo. To prevent particularly intense AATs from spamming. 0 is a valid value "MandatoryArg: Cooldown time that applies per player where we check if the attacker has triggered the AAT. To prevent particularly intense AATs from spamming. 0 is a valid value "MandatoryArg: Cooldown time across all entities where we don't check for the same result from the same player/AAT combo. To prevent particularly intense AATs from spamming. 0 is a valid value "MandatoryArg: Bool representing whether the AAT can occur on death "MandatoryArg: Function pointer to run in response to the result occurring. This is responsible for 3rd person FX, sounds, and other results. "MandatoryArg: Name of the icon to use for damage_feedback. "MandatoryArg: Name of the sound to use for damage_feedback. "OptionalArg: [validation_func] Function pointer that, if defined, will run an AAT-specific check to see if the AAT should run. "Example: level aat::register( ZM_AAT_FIRE_WORKS_NAME, ZM_AAT_FIRE_WORKS_PERCENTAGE, ZM_AAT_FIRE_WORKS_COOLDOWN_ENTITY, ZM_AAT_FIRE_WORKS_COOLDOWN_ATTACKER, ZM_AAT_FIRE_WORKS_COOLDOWN_GLOBAL, ZM_AAT_FIRE_WORKS_OCCURS_ON_DEATH, &result, ZM_AAT_FIRE_WORKS_DAMAGE_FEEDBACK_ICON, ZM_AAT_FIRE_WORKS_DAMAGE_FEEDBACK_SOUND, &fire_works_zombie_validation );" "SPMP: both" @/ function register( name, percentage, cooldown_time_entity, cooldown_time_attacker, cooldown_time_global, occurs_on_death, result_func, damage_feedback_icon, damage_feedback_sound, validation_func ) { assert( ( isdefined( level.aat_initializing ) && level.aat_initializing ), "All info registration in the AAT system must occur during the first frame while the system is initializing" ); assert( IsDefined( name ), "aat::register(): name must be defined" ); assert( "none" != name, "aat::register(): name cannot be '" + "none" + "', that name is reserved as an internal sentinel value" ); assert( !IsDefined( level.aat[name] ), "aat::register(): AAT '" + name + "' has already been registered" ); assert( IsDefined( percentage ), "aat::register(): AAT '" + name + "': percentage must be defined" ); assert( 0 <= percentage && 1 > percentage, "aat::register(): AAT '" + name + "': percentage must be a value greater than or equal to 0 and less than 1" ); assert( IsDefined( cooldown_time_entity ), "aat::register(): AAT '" + name + "': cooldown_time_entity must be defined" ); assert( 0 <= cooldown_time_entity, "aat::register(): AAT '" + name + "': cooldown_time_entity must be a value greater than or equal to 0" ); assert( IsDefined( cooldown_time_entity ), "aat::register(): AAT '" + name + "': cooldown_time_attacker must be defined" ); assert( 0 <= cooldown_time_entity, "aat::register(): AAT '" + name + "': cooldown_time_attacker must be a value greater than or equal to 0" ); assert( IsDefined( cooldown_time_global ), "aat::register(): AAT '" + name + "': cooldown_time_global must be defined" ); assert( 0 <= cooldown_time_global, "aat::register(): AAT '" + name + "': cooldown_time_global must be a value greater than or equal to 0" ); assert( IsDefined( occurs_on_death ), "aat::register(): AAT '" + name + "': occurs_on_death must be defined" ); assert( IsDefined( result_func ), "aat::register(): AAT '" + name + "': result_func must be defined" ); assert( IsDefined( damage_feedback_icon ), "aat::register(): AAT '" + name + "': damage_feedback_icon must be defined" ); assert( IsString( damage_feedback_icon ), "aat::register(): AAT '" + name + "': damage_feedback_icon must be a string" ); assert( IsDefined( damage_feedback_sound ), "aat::register(): AAT '" + name + "': damage_feedback_sound must be defined" ); assert( IsString( damage_feedback_sound ), "aat::register(): AAT '" + name + "': damage_feedback_sound must be a string" ); level.aat[name] = SpawnStruct(); level.aat[ name ].name = name; level.aat[ name ].hash_id = HashString(name); level.aat[ name ].percentage = percentage; level.aat[ name ].cooldown_time_entity = cooldown_time_entity; level.aat[ name ].cooldown_time_attacker = cooldown_time_attacker; level.aat[ name ].cooldown_time_global = cooldown_time_global; level.aat[ name ].cooldown_time_global_start = 0; level.aat[ name ].occurs_on_death = occurs_on_death; level.aat[ name ].result_func = result_func; level.aat[ name ].damage_feedback_icon = damage_feedback_icon; level.aat[ name ].damage_feedback_sound = damage_feedback_sound; level.aat[ name ].validation_func = validation_func; level.aat[ name ].immune_trigger = []; level.aat[ name ].immune_result_direct = []; level.aat[ name ].immune_result_indirect = []; } /@ "Name: register( , , , , )" "Summary: Register an AAT "Module: AAT" "MandatoryArg: Unique name to identify the AAT. "MandatoryArg: Archetype of enemy this immunity is registered for. "MandatoryArg: Boolean that determines if the AAT can be triggered by shooting the Archetype. True == Archetype is immune "MandatoryArg: Boolean that determines if direct AAT effects "MandatoryArg: Cooldown time across all entities where we don't check for the same result from the same player/AAT combo. To prevent particularly intense AATs from spamming. 0 is a valid value "Example: level aat::immunity_register( "burn_furnace", ARCHETYPE_MARGWA, false, true, true );" "SPMP: both" @/ function register_immunity( name, archetype, immune_trigger, immune_result_direct, immune_result_indirect ) { // Waits for AAT's to complete initialization, then begin immunity registration while ( level.aat_initializing !== false ) { wait .05; } // ASSERTS assert( isdefined( name ), "aat::register(): name must be defined" ); assert( isdefined( archetype ), "aat::register(): archetype must be defined" ); assert( isdefined( immune_trigger ), "aat::register(): immune_trigger must be defined" ); assert( isdefined( immune_result_direct ), "aat::register(): immune_result_direct must be defined" ); assert( isdefined( immune_result_indirect ), "aat::register(): immune_result_indirect must be defined" ); if ( !isdefined( level.aat[ name ].immune_trigger ) ) { level.aat[ name ].immune_trigger = []; } if ( !isdefined( level.aat[name].immune_result_direct ) ) { level.aat[ name ].immune_result_direct = []; } if ( !isdefined( level.aat[name].immune_result_indirect ) ) { level.aat[ name ].immune_result_indirect = []; } level.aat[ name ].immune_trigger[ archetype ] = immune_trigger; level.aat[ name ].immune_result_direct[ archetype ] = immune_result_direct; level.aat[ name ].immune_result_indirect[ archetype ] = immune_result_indirect; } function finalize_clientfields() { /#println( "AAT server registrations:" );#/ if ( level.aat.size > 1 ) { array::alphabetize( level.aat ); i = 0; foreach ( aat in level.aat ) { aat.clientfield_index = i; i++; /#println( " " + aat.name );#/ } n_bits = GetMinBitCountForNum( level.aat.size - 1 ); clientfield::register( "toplayer", "aat_current", 1, n_bits, "int" ); } level.aat_initializing = false; } // Initializes weapon exemptions function register_aat_exemption( weapon ) { weapon = get_nonalternate_weapon( weapon ); level.aat_exemptions[ weapon ] = true; } // Checks if weapon is exempt from gaining an AAT. Exemptions defined in array level.aat_exemptions function is_exempt_weapon( weapon ) { weapon = get_nonalternate_weapon( weapon ); return isdefined( level.aat_exemptions[ weapon ] ); } /@ "Name: register_reroll( , , , )" "Summary: Register an AAT "Module: AAT" "MandatoryArg: Unique name to identify the AAT. "MandatoryArg: Int value the number of rerolls. "MandatoryArg: Function pointer to run to test if the player has the reroll currently active. "MandatoryArg: Name of the icon to use for damage_feedback in addition to the AAT's icon, this signifies the AAT occurred thanks to this reroll. "Example: level aat::register_reroll( "lucky_crit", 2, &_zm_bgb_lucky_crit::active, "t7_hud_zm_aat_bgb" );" "SPMP: both" @/ function register_reroll( name, count, active_func, damage_feedback_icon ) { assert( IsDefined( name ), "aat::register_reroll (): name must be defined" ); assert( "none" != name, "aat::register_reroll(): name cannot be '" + "none" + "', that name is reserved as an internal sentinel value" ); assert( !IsDefined( level.aat[name] ), "aat::register_reroll(): AAT Reroll'" + name + "' has already been registered" ); assert( IsDefined( count ), "aat::register_reroll(): AAT Reroll '" + name + "': count must be defined" ); assert( 0 < count, "aat::register_reroll(): AAT Reroll '" + name + "': count must be greater than 0" ); assert( IsDefined( active_func ), "aat::register_reroll(): AAT Reroll '" + name + "': active_func must be defined" ); assert( IsDefined( damage_feedback_icon ), "aat::register_reroll(): AAT Reroll '" + name + "': damage_feedback_icon must be defined" ); assert( IsString( damage_feedback_icon ), "aat::register_reroll(): AAT Reroll '" + name + "': damage_feedback_icon must be a string" ); level.aat_reroll[name] = SpawnStruct(); level.aat_reroll[name].name = name; level.aat_reroll[name].count = count; level.aat_reroll[name].active_func = active_func; level.aat_reroll[name].damage_feedback_icon = damage_feedback_icon; } function getAATOnWeapon(weapon) //self == player { weapon = get_nonalternate_weapon( weapon ); if ( weapon == level.weaponNone || !( isdefined( level.aat_in_use ) && level.aat_in_use ) || is_exempt_weapon( weapon ) || (!isDefined(self.aat) || !isDefined(self.aat[weapon])) || !isDefined( level.aat[self.aat[weapon]]) ) { return undefined; } return level.aat[self.aat[weapon]]; } /@ "Name: acquire( , )" "Summary: The player acquires the specified AAT on the specified weapon. If a specific AAT is not supplied, a random AAT is selected by the system, unless one alredy exists for that weapon, in which case no action is taken "Module: AAT" "MandatoryArg: Weapon object to receive the AAT. "OptionalArg: Unique name to identify the AAT to receive, will be randomly selected if undefined. "Example: self aat::acquire( ar_standard_upgraded_object );" "SPMP: both" @/ function acquire( weapon, name ) { if ( !( isdefined( level.aat_in_use ) && level.aat_in_use ) ) { return; } assert( IsDefined( weapon ), "aat::acquire(): weapon must be defined" ); assert( weapon != level.weaponNone, "aat::acquire(): weapon must not be level.weaponNone" ); weapon = get_nonalternate_weapon( weapon ); if ( is_exempt_weapon( weapon ) ) { return; } if ( IsDefined( name ) ) { assert( "none" != name, "aat::acquire(): name cannot be '" + "none" + "', that name is reserved as an internal sentinel value" ); assert( IsDefined( level.aat[name] ), "aat::acquire(): AAT '" + name + "' was never registered" ); self.aat[weapon] = name; } else { keys = GetArrayKeys( level.aat ); ArrayRemoveValue( keys, "none" ); // If weapon has AAT, remove current AAT from possible rerolls if ( IsDefined( self.aat[ weapon ] ) ) { ArrayRemoveValue( keys, self.aat[ weapon ] ); } rand = RandomInt( keys.size ); self.aat[weapon] = keys[rand]; } if ( weapon == self GetCurrentWeapon() ) { self clientfield::set_to_player( "aat_current", level.aat[self.aat[weapon]].clientfield_index ); } } /@ "Name: remove( )" "Summary: Removes the AAT from the specified weapon for the player "Module: AAT" "MandatoryArg: Weapon object to remove the AAT from. "Example: self aat::remove( ar_standard_upgraded_object );" "SPMP: both" @/ function remove( weapon ) { if ( !( isdefined( level.aat_in_use ) && level.aat_in_use ) ) { return; } assert( IsDefined( weapon ), "aat::remove(): weapon must be defined" ); assert( weapon != level.weaponNone, "aat::remove(): weapon must not be level.weaponNone" ); weapon = get_nonalternate_weapon( weapon ); self.aat[weapon] = undefined; } // Monitors weapon changes to detect AAT names, then sends the names to the clientside for HUD changes function watch_weapon_changes() { self endon( "disconnect" ); self endon( "entityshutdown" ); while ( isdefined( self ) ) { self waittill( "weapon_change", weapon ); weapon = get_nonalternate_weapon( weapon ); name = "none"; if ( IsDefined( self.aat[weapon] ) ) { name = self.aat[weapon]; } self clientfield::set_to_player( "aat_current", level.aat[name].clientfield_index ); } }