#include common_scripts\utility; #include maps\mp\alien\_persistence; #include maps\mp\_utility; #include maps\mp\alien\_perk_utility; #include maps\mp\alien\_utility; CONST_TRAP_OUTLINE_COLOR_INDEX = 3; CONST_TRAP_OUTLINE_RED_COLOR_INDEX = 1; CONST_FLARE_THREATBIAS = 1000; CONST_FLARE_TIME = 20; CONST_UPGRADED_FLARE_TIME = 30; CONST_FLARE_HEALTH = 900000; CONST_TURRET_OVERHEAT_TIME = 2; CONST_TURRET_COOLDOWN_TIME = 1; // traps go here! traps_init() { // setup parameter of fire that burns aliens when crossed level.outline_switch[ "traps_fire" ] = true; level thread fire_trap_init(); // electrofied puddles that shocks aliens when stepped into level.outline_switch[ "traps_puddle" ] = true; level thread electric_puddle_init(); // electric fences that shocks aliens when climbing level.outline_switch[ "traps_fence" ] = true; level thread electric_fence_init(); level.spawnGlow["enemy"] = loadfx( "fx/misc/flare_ambient" ); } // global trap activation condition can_activate_trap( trap ) { assertex( isdefined( trap ) && isdefined( trap.cost ), "[int] trap_struct.cost is not defined" ); // self is player return self player_has_enough_currency( int( trap.cost ) ); } //***************************************************************** // FIRE TRAP //***************************************************************** CONST_FIRE_TRAP_DURATION = 50; // seconds fire last CONST_FIRE_TRAP_DURATION_SMALL = 50; // seconds fire last(custom) CONST_FIRE_TRAP_DURATION_MED = 80; // seconds fire last(custom) CONST_FIRE_TRAP_DURATION_LARGE = 110; // seconds fire last(custom) CONST_FIRE_TRAP_DAMAGE_SMALL = 400; // total damage CONST_FIRE_TRAP_DAMAGE_MED = 600; // total damage CONST_FIRE_TRAP_DAMAGE_LARGE = 800; // total damage CONST_FIRE_TRAP_DAMAGE_TIME = 6; // total time for said damage to be applied CONST_FIRE_TRAP_DAMAGE_PLAYER = 33; // total damage CONST_FIRE_TRAP_DAMAGE_TIME_PLAYER = 3; // total time for said damage to be applied CONST_FIRE_TRAP_COST = 750; // currency cost to enable fence CONST_FIRE_TRAP_COST_SMALL = 300; // small CONST_FIRE_TRAP_COST_MED = 750; // med CONST_FIRE_TRAP_COST_LARGE = 1000; // large CONST_POURING_TIME = 2.5; // time it takes to pour the gas after buying fire_trap_init() { barrels = getentarray( "fire_trap_barrel", "targetname" ); if ( !isdefined( barrels ) || barrels.size == 0 ) return; // wait before players are ready, we decide how to setup usage based on match type while ( !isdefined( level.players ) || level.players.size < 1 ) wait 0.05; level.fire_traps = []; foreach ( barrel in barrels ) { barrel.damagefeedback = false; fire_trap = fire_trap_setup( barrel ); level.fire_traps[ level.fire_traps.size ] = fire_trap; fire_trap thread fire_trap_think(); } } // per trap setup ( 3 entities involved per trap ) // barrel_xmodel -> gas_scriptmodel -> activation_trigger -> damage_trigger -> fire_link_struct_1 -> fire_link_struct_2 etc... fire_trap_setup( barrel ) { fire_trap = SpawnStruct(); fire_trap.trap_type = "traps_fire"; // setup barrel for use fire_trap.barrel = barrel; // setup damage trigger fire_trap.burn_trig = getent( fire_trap.barrel.target, "targetname" ); //needed for tracking kills with the fire trap fire_trap.burn_trig.script_noteworthy = "fire_trap"; // setup fire fx locations fire_link_structs = []; cur_loc = fire_trap.burn_trig; while ( isdefined( cur_loc ) && isdefined( cur_loc.target ) ) { fire_loc = getstruct( cur_loc.target, "targetname" ); if ( !isdefined( fire_loc ) ) break; // break once we have gone full circle if ( isdefined( fire_link_structs[ 0 ] ) && fire_loc == fire_link_structs[ 0 ] ) { assertmsg( "Looping fire location structs is not allowed" ); break; } fire_link_structs[ fire_link_structs.size ] = fire_loc; cur_loc = fire_loc; } // setup points betweenlinked fire location structs to play actual fire fx with defined spacing interval_dist = 45; // fx spacing max_fire_fx = 15; // leak safe, max fx allowed per trap fire_fx_array = []; // list of fx spawned for ( i = 0; i < fire_link_structs.size - 1; i++ ) { dist_to_next = distance( fire_link_structs[ i ].origin, fire_link_structs[ i + 1 ].origin ); for ( j = 0; j < int( dist_to_next / interval_dist ); j++ ) { // position calculated between the next fire link struct position = VectorLerp( fire_link_structs[ i ].origin, fire_link_structs[ i + 1 ].origin, j/int( dist_to_next / interval_dist ) ); // track spawned fx for clean up later fire_fx_array[ fire_fx_array.size ] = position; // leak safe if ( fire_fx_array.size > max_fire_fx ) { break; } } // leak safe if ( fire_fx_array.size > max_fire_fx ) { break; } } // array of vectors where fire fx will play fire_trap.fire_fx_locs = fire_fx_array; // structs where fire fx will play fire_trap.fire_link_structs = fire_link_structs; // setup fire fx fire_trap.fire_fx = LoadFX( "vfx/gameplay/alien/vfx_alien_trap_fire" ); // setup fire trap initial values fire_trap.burning = false; fire_trap.duration = CONST_FIRE_TRAP_DURATION; fire_trap.base_duration = CONST_FIRE_TRAP_DURATION; fire_trap.DoT = CONST_FIRE_TRAP_DAMAGE_MED; fire_trap.damage_time = CONST_FIRE_TRAP_DAMAGE_TIME; fire_trap.DoT_player = CONST_FIRE_TRAP_DAMAGE_PLAYER; fire_trap.damage_time_player = CONST_FIRE_TRAP_DAMAGE_TIME_PLAYER; // setup different sizes that costs different fire_trap fire_trap_setup_sizes(); fire_trap.barrel SetCursorHint( "HINT_NOICON" ); fire_trap.barrel SetHintString( fire_trap.hintString ); fire_trap.barrel MakeUsable(); if ( alien_mode_has( "outline" ) ) maps\mp\alien\_outline_proto::add_to_outline_watch_list ( barrel, fire_trap.cost ); return fire_trap; } // cost override from radiant setup fire_trap_setup_sizes() { sizes = [ "small", "medium", "large" ]; custom_lifespan_array = []; custom_lifespan_array["small"] = CONST_FIRE_TRAP_DURATION_SMALL; custom_lifespan_array["medium"] = CONST_FIRE_TRAP_DURATION_MED; custom_lifespan_array["large"] = CONST_FIRE_TRAP_DURATION_LARGE; size_index = 1; // default medium if ( isdefined( self.barrel.script_noteworthy ) ) { if ( self.barrel.script_noteworthy == "small" ) size_index = 0; if ( self.barrel.script_noteworthy == "large" ) size_index = 2; //ex: "custom small large" for small cost, large lifespan if(IsSubStr( self.barrel.script_noteworthy, "custom")) { tokens = strtok(self.barrel.script_noteworthy, " "); switch ( tokens[1] ) { case "small": size_index = 0; break; case "medium": size_index = 1; break; case "large": size_index = 2; break; default: break; } self.custom = tokens[2]; } } if ( isPlayingSolo() ) size_index = int( max( 0, size_index - 1 ) ); if ( sizes[ size_index ] == "small" ) { self.DoT = CONST_FIRE_TRAP_DAMAGE_SMALL; self.cost = CONST_FIRE_TRAP_COST_SMALL; self.hintString = &"ALIEN_COLLECTIBLES_ACTIVATE_FIRE_TRAP_SMALL"; } if ( sizes[ size_index ] == "medium" ) { self.DoT = CONST_FIRE_TRAP_DAMAGE_MED; self.cost = CONST_FIRE_TRAP_COST_MED; self.hintString = &"ALIEN_COLLECTIBLES_ACTIVATE_FIRE_TRAP_MED"; } if ( sizes[ size_index ] == "large" ) { self.DoT = CONST_FIRE_TRAP_DAMAGE_LARGE; self.cost = CONST_FIRE_TRAP_COST_LARGE; self.hintString = &"ALIEN_COLLECTIBLES_ACTIVATE_FIRE_TRAP_LARGE"; } if(IsDefined(self.custom)) { self.base_duration = custom_lifespan_array[self.custom]; } } trap_BBprint( trap_type, owner, loc ) { // =========================== blackbox print [START] =========================== level.alienBBData[ "traps_used" ]++; owner maps\mp\alien\_persistence::eog_player_update_stat( "traps", 1 ); trapx = loc[ 0 ]; trapy = loc[ 1 ]; trapz = loc[ 2 ]; traptype = trap_type; ownername = ""; if ( isdefined( owner.name ) ) ownername = owner.name; /# if ( GetDvarInt( "alien_bbprint_debug" ) > 0 ) { IPrintLnBold( "^8bbprint: alientrap \n" + " traptype=" + traptype + " trapx,y,z=" + loc + " ownername=" + ownername ); } #/ bbprint( "alientrap", "traptype %s trapx %f trapy %f trapz %f ownername %s ", traptype, trapx, trapy, trapz, ownername ); // =========================== [END] blackbox print =========================== } // main loop fire_trap_think() { // self is fire_trap struct self endon( "disable_fire_trap" ); level endon( "game_ended" ); while ( isdefined( self ) ) { // [off] - wait for activate while ( !self.burning ) { self.barrel waittill( "trigger", owner ); if ( owner can_activate_trap( self ) ) { thread trap_BBprint( "fire", owner, self.barrel.origin ); level thread maps\mp\alien\_music_and_dialog::playVOForTrapActivation( owner, self.trap_type); self.owner = owner; self.barrel SetHintString( "" ); self.barrel MakeUnUsable(); // cost the activator price = int( self.cost * ( owner perk_GetTrapCostScalar() )); discount = ( self.cost - price ); owner take_player_currency( price, false, "trap" ); self.duration = int( self.base_duration * ( owner perk_GetTrapDurationScalar() ) ); //self thread fire_trap_burn( impact_loc ); self thread sounds_fire_trap( self.barrel.origin ); self thread fire_trap_burn( self.barrel.origin ); owner thread stop_firetrap_on_disconnect( self ); // break into [on] break; } else { wait 0.05; owner setLowerMessage( "no_money", &"ALIEN_COLLECTIBLES_NO_MONEY", 3 ); //owner iprintlnBold( "Fire Trap costs $" + self.cost ); continue; } } if ( alien_mode_has( "outline" ) ) maps\mp\alien\_outline_proto::remove_from_outline_watch_list( self.barrel ); self waittill( "fire_trap_exhausted" ); // padding wait 0.5; //self.owner = undefined; MarkDangerousNodesInTrigger( self.burn_trig, false ); self.burning = false; self.barrel SetHintString( self.hintString ); self.barrel MakeUsable(); if ( alien_mode_has( "outline" ) ) maps\mp\alien\_outline_proto::add_to_outline_watch_list( self.barrel, self.cost ); // ^ loop ^ } } sounds_fire_trap( barrel ) { sound_flame_burst = spawn( "script_origin", barrel ); sound_flame_burst playsound( "alien_incendiary_impact" ); self.sound_flames = spawn( "script_origin", barrel ); self.sound_flames playloopsound( "fire_trap_fire_lp" ); } // wait for trap ignition fire_trap_wait_for_impact() { level endon( "game_ended" ); self endon( "fire_trap_exhausted" ); while ( 1 ) { // wait for player activation by igniting the fire self.activation_trig waittill( "damage", damage, attacker, direction_vec, impact_loc, damage_type ); if ( !isPlayer( attacker ) ) { if ( isdefined( attacker.owner ) && isPlayer( attacker.owner ) ) { attacker = attacker.owner; } else { wait 0.05; continue; } } if ( !isdefined( damage_type ) ) { wait 0.05; continue; } // skip no fire igniting damages type = ToLower( damage_type ); switch( type ) { case "unknown": case "mod_impact": case "mod_melee": case "mod_crush": case "melee": wait 0.05; continue; default: return impact_loc; } return impact_loc; } return undefined; } // burn and do damage fire_trap_burn( fire_start_loc ) { level endon( "game_ended" ); self endon( "fire_trap_exhausted" ); self thread monitor_fire_trap_exhausted( fire_start_loc ); MarkDangerousNodesInTrigger( self.burn_trig, true ); wait 2; // padding so players don't get burned while ( true ) { // waittill someone touches fire self.burn_trig waittill( "trigger", victim ); if ( !isdefined( victim ) || !isReallyAlive( victim ) || ( !isplayer( victim ) && !IsAgent( victim ) ) || ( isDefined( victim.burning ) && victim.burning ) ) { continue; } self thread do_damage_over_time( victim ); } } do_damage_over_time( victim ) { // self is fire trap struct level endon( "game_ended" ); victim endon( "death" ); if( !isDefined( self.owner ) ) return; // only one instance of burn victim notify( "fire_trap_burning" ); victim endon( "fire_trap_burning" ); victim.burning = true; if ( isplayer( victim ) ) { duration = self.damage_time_player; DoT = self.DoT_player; } else { duration = self.damage_time * ( self.owner perk_GetTrapDamageScalar() ); DoT = self.DoT * level.alien_health_per_player_scalar[ level.players.size ] * ( self.owner perk_GetTrapDamageScalar() ); } should_use_fire_fx = victim is_alien_agent(); if ( should_use_fire_fx ) victim maps\mp\alien\_alien_fx::alien_fire_on(); interval_time = 1; damage_per_interval = DoT / ( duration / interval_time ); victim do_damage_until_timeout( damage_per_interval, duration, interval_time, self ); if ( should_use_fire_fx ) { victim maps\mp\alien\_alien_fx::alien_fire_off(); } victim.burning = undefined; } do_damage_until_timeout( damage, duration, interval, attacker ) { self endon( "death" ); attacker.owner endon( "disconnect" ); elapsed_time = 0; while ( elapsed_time < duration && isDefined( attacker.owner ) ) { // create marker for passing in damage type attacker.owner.burning_victim = true; if ( isplayer( self ) ) self DoDamage( damage, self.origin, undefined, attacker.burn_trig ); else if ( IsDefined( attacker ) && isDefined( attacker.owner ) ) self DoDamage( damage, self.origin, attacker.owner, attacker.burn_trig ); elapsed_time += 1.0; wait interval; } } // notifies when fire burns out, in predefined time monitor_fire_trap_exhausted( fire_start_loc ) { level endon( "game_ended" ); self endon( "owner_disconnected" ); // sort fire fx locs array by closest to fire start loc // this is to start fire chain originated from point of fire start, ex: bullet impact location fire_fx_locs = sort_vectors_by_distance( self.fire_fx_locs, fire_start_loc ); fx_interval = 0.25; // fx interval timing // play fx in sequence self.fire_fx_array = play_fire( fire_fx_locs, fx_interval ); // wait total burn duration wait self.duration; // end it self notify( "fire_trap_exhausted" ); if( isDefined( self.fire_fx_array ) ) self kill_fire( self.fire_fx_array ); } sort_vectors_by_distance( array, vec ) { sorted_array = []; while ( array.size ) { idx = get_closest_vec_index( array, vec ); sorted_array[ sorted_array.size ] = array[ idx ]; array = array_remove( array, array[idx] ); } return sorted_array; } get_closest_vec_index( array, vec ) { closest_key = 0; closest_element = array[ 0 ]; foreach ( key, element in array ) { if ( distance( element, vec ) <= distance( closest_element, vec ) ) { closest_key = key; closest_element = element; } } return closest_key; } // param 1 = array of locations to play fx, param 2 = delay per fx play_fire( fire_fx_locs, fx_interval ) { fire_fx_array = []; // list of fx spawned for ( i = 0; i < fire_fx_locs.size; i++ ) { // spawn fire fx per spacing gasFire = SpawnFx( self.fire_fx, fire_fx_locs[ i ] ); triggerFx( gasFire ); // track spawned fx for clean up later fire_fx_array[ fire_fx_array.size ] = gasFire; // wait delay between fx wait fx_interval; } return fire_fx_array; } // kill all fire fx kill_fire( fire_fx_array ) { if ( isDefined( self.sound_flames ) ) self thread sounds_kill_flames(); //clean up list of fx spawned if ( isDefined( fire_fx_array ) ) { foreach ( gasFire in fire_fx_array ) { if ( isDefined( gasFire ) ) gasFire delete(); } } } sounds_kill_flames() { self.sound_flames endon( "death" ); flames_end = spawn( "script_origin", self.sound_flames.origin ); flames_end playsound( "fire_trap_fire_end_lp" ); wait 0.5; self.sound_flames stopsounds(); wait 0.1; self.sound_flames delete(); } //***************************************************************** // ELECTRIC TRAPS: COMMON //***************************************************************** CONST_SHOCK_INTERVAL = 0.35; // time delay between shocks CONST_GENERATOR_POWER = 20; // number of shock attacks for max capacity // run electric generator run_generator() { self notify( "electric_trap_turned_on" ); self eletric_trap_asserts(); self.running = true; self.capacity = self.max_capacity; self.generator SetHintString( "" ); self.generator MakeUnUsable(); } // shocks victim! trap_shock( victim, damage_override, use_capacity ) { self endon( "electric_trap_turned_off" ); victim endon( "death" ); if( !isDefined( self.owner ) ) return; self eletric_trap_asserts(); shock_damage = self.shock_damage * level.alien_health_per_player_scalar[ level.players.size ] * (self.owner perk_GetTrapDamageScalar() ); if ( isdefined( damage_override ) ) shock_damage = damage_override; // self is trap struct victim.shocked = true; if ( !isalive( victim ) ) return; // fx on victim playfx( self.shock_fx[ "shock" ], victim.origin + ( 0, 0, 32 ) ); playfx( self.shock_fx[ "sparks" ], victim.origin + ( 0, 0, 32 ) ); debug_electric_trap_print( victim.origin, "hp:" + victim.health, ( 0.5, 0.5, 1 ), 0.75, 2, 1 ); victim.pain_registered = true; // no pain logic when shocked, for now wait 0.05; // allow damage of self if victim is player owner = self.owner; if ( isplayer( victim ) ) owner = self.generator; if ( isdefined( use_capacity ) && use_capacity ) { // remove one shock amount from total shock capacity self.capacity--; } victim DoDamage( shock_damage, victim.origin, owner, self.generator, "MOD_EXPLOSIVE" ); // sfx for trap shock playSoundAtPos( victim.origin, "alien_fence_shock" ); if ( !isalive( victim ) && IsAgent( victim ) ) { pos = victim wait_for_ragdoll_pos(); if ( isdefined( pos ) ) { wait 0.1; PhysicsExplosionSphere( pos, 300, 150, 5 ); playfx( self.shock_fx[ "shock" ], pos ); playfx( self.shock_fx[ "sparks" ], pos ); } } random_delay = RandomFloatRange( self.shock_interval/2, self.shock_interval*1.5 ); victim thread time_out_shocked_state( random_delay ); } // this will catch shock state reset in case trap times out during shock and kills trap_shock() time_out_shocked_state( delay ) { self endon( "death" ); self endon( "disconnect" ); wait delay; self.shocked = false; } wait_for_ragdoll_pos() { self endon( "ragdoll_timed_out" ); self thread ragdoll_timeout( 1 ); self waittill( "in_ragdoll", pos ); return pos; } ragdoll_timeout( time ) { wait time; if ( isdefined( self ) ) self notify( "ragdoll_timed_out" ); } debug_electric_trap_print( pos, string, color, alpha, scale, time ) { /# if ( GetDvarInt( "debug_trap" ) == 1 ) thread debug_electric_trap_print_raw( pos, string, color, alpha, scale, time ); #/ } debug_electric_trap_print_raw( pos, string, color, alpha, scale, time ) { level endon ( "game_ended" ); while ( time > 0 ) { Print3d( pos, string, color, alpha, scale, 1 ); time -= 0.05; wait( 0.05 ); } } // global electric trap think run_electric_trap( play_trap_on_fx, play_trap_off_fx, play_ambient_shocks ) { // self is trap struct self endon( "death" ); level endon( "game_ended" ); self eletric_trap_asserts(); // shows health of trap if ( IsDefined( play_ambient_shocks ) ) self thread [[ play_ambient_shocks ]](); while ( isdefined( self ) ) { // [off] - wait for activate while ( !self.running ) { self.generator waittill( "trigger", owner ); if ( owner can_activate_trap( self ) ) { thread trap_BBprint( "electric", owner, self.generator.origin ); level thread maps\mp\alien\_music_and_dialog::playVOForTrapActivation( owner, self.trap_type ); self.owner = owner; owner thread stop_electric_trap_on_disconnect( self ); if ( isdefined( play_trap_on_fx ) ) self thread [[ play_trap_on_fx ]](); // sfx loop for trap ambient hum self.shock_trig playLoopSound( "alien_fence_hum_lp" ); // sfx loop for generator running self.generator playLoopSound( "alien_fence_gen_lp" ); // cost the activator price = int( self.cost * ( owner perk_GetTrapCostScalar() ) ); discount = self.cost - price; owner take_player_currency( price, false, "trap" ); self.capacity = int( CONST_GENERATOR_POWER * owner perk_GetTrapDurationScalar() ); // break into [on] break; } else { wait 0.05; owner setLowerMessage( "no_money", &"ALIEN_COLLECTIBLES_NO_MONEY", 3 ); //owner iprintlnBold( trap_cost_string ); continue; } } if ( alien_mode_has( "outline" ) ) maps\mp\alien\_outline_proto::remove_from_outline_watch_list( self.generator ); // run generator visuals self thread run_generator(); on_time = gettime(); // [on] - wait for victim while ( self.running && self.capacity > 0 && isDefined( self.owner ) && IsSentient( self.owner ) ) { elapsed_time = max( 0, ( GetTime() - on_time ) / 1000 ); time_left = max( 5, self.life_span - elapsed_time ); // 5 seconds linger time for consecutive shocks victim = self wait_for_trigger_timeout( time_left * ( self.owner perk_GetTrapDurationScalar() ) ); // undefined if above wait earlied out due to time out if ( !isdefined( victim ) && ( isdefined( self.trap_timed_out ) && self.trap_timed_out ) ) break; if ( self.capacity <= 0 || !isDefined( self.owner ) ) { // break into [off] break; } if ( isAgent( victim ) && isalive( victim ) && !( isdefined( victim.shocked ) && victim.shocked ) ) self thread trap_shock( victim, undefined, true ); if ( isdefined( self.player_damage ) && isPlayer( victim ) && isAlive( victim ) && !( isdefined( victim.shocked ) && victim.shocked ) ) self thread trap_shock( victim, self.player_damage, false ); } if ( alien_mode_has( "outline" ) ) maps\mp\alien\_outline_proto::add_to_outline_watch_list( self.generator, self.cost ); self notify( "electric_trap_turned_off" ); if( isDefined( self.owner ) && isALive( self.owner ) ) { if ( self.trap_type == "traps_fence" ) self.owner setLowerMessage( "electric_fence_offline", &"ALIEN_COLLECTIBLES_ELECTRIC_FENCE_OFFLINE", 3 ); else self.owner setLowerMessage( "electric_fence_offline", &"ALIENS_PATCH_ELECTRIC_TRAP_OFFLINE", 3 ); } // padding wait 0.5; if ( isdefined( play_trap_off_fx ) ) self thread [[ play_trap_off_fx ]](); self.owner = undefined; self.running = false; self.generator SetHintString( self.hintString ); self.generator MakeUsable(); self.generator stopLoopSound( "alien_fence_gen_lp" ); self.generator playSound( "alien_fence_gen_off" ); self.shock_trig stopLoopSound( "alien_fence_hum_lp" ); // ^ loop ^ } } //self = the player who activatged the trap stop_electric_trap_on_disconnect( trap ) { trap endon( "electric_trap_turned_off" ); trap endon( "timed_out" ); self waittill( "disconnect" ); trap notify ( "electric_trap_turned_off" ); } stop_firetrap_on_disconnect( trap ) { trap endon( "timed_out" ); trap endon( "fire_trap_exhausted" ); self waittill( "disconnect" ); trap thread kill_fire( trap.fire_fx_array ); trap notify ( "owner_disconnected" ); trap notify( "fire_trap_exhausted" ); } wait_for_trigger_timeout( timeout ) { self endon( "electric_trap_turned_off" ); self thread timeout_watch( timeout ); self endon( "timed_out" ); self.shock_trig waittill( "trigger", victim ); return victim; } timeout_watch( timeout ) { self endon( "electric_trap_turned_off" ); self.trap_timed_out = false; wait timeout; self notify( "timed_out" ); self.trap_timed_out = true; } eletric_trap_asserts() { generic_msg = " is not defined in eletric trap setup function."; assertex( isdefined( self.cost ), "trap_struct.cost" + generic_msg ); assertex( isdefined( self.hintString ), "trap_struct.hintString" + generic_msg ); assertex( isdefined( self.running ), "trap_struct.running" + generic_msg ); assertex( isdefined( self.generator ), "trap_struct.generator" + generic_msg ); assertex( isdefined( self.shock_fx ), "trap_struct.shock_fx" + generic_msg ); assertex( isdefined( self.shock_interval ), "trap_struct.shock_interval"+ generic_msg ); assertex( isdefined( self.shock_damage ), "trap_struct.shock_damage" + generic_msg ); assertex( isdefined( self.shock_trig ), "trap_struct.shock_trig" + generic_msg ); assertex( isdefined( self.capacity ), "trap_struct.capacity" + generic_msg ); assertex( isdefined( self.max_capacity ), "trap_struct.max_capacity" + generic_msg ); assertex( isdefined( self.trap_type ), "trap_struct.trap_type" + generic_msg ); } //***************************************************************** // ELECTRIC TRAPS: PUDDLE //***************************************************************** CONST_PUDDLE_COST = 750; // currency cost to enable trap CONST_PUDDLE_COST_SMALL = 300; CONST_PUDDLE_COST_MED = 500; CONST_PUDDLE_COST_LARGE = 750; CONST_PUDDLE_LIFE_SPAN_SMALL = 90; // time out CONST_PUDDLE_LIFE_SPAN_MED = 120; CONST_PUDDLE_LIFE_SPAN_LARGE = 150; CONST_PUDDLE_SHOCK_DAMAGE = 200; // damage per shock CONST_PUDDLE_PLAYER_DAMAGE = 3; // damage done to player electric_puddle_init() { generators = getentarray( "puddle_generator", "targetname" ); if ( !isdefined( generators ) || generators.size == 0 ) return; // wait before players are ready, we decide how to setup usage based on match type while ( !isdefined( level.players ) || level.players.size < 1 ) wait 0.05; level.electric_puddles = []; foreach ( generator in generators ) { generator.damagefeedback = false; puddle = setup_electric_puddle( generator ); level.electric_puddles[ level.electric_puddles.size ] = puddle; puddle thread run_electric_trap( ::play_puddle_on_fx, ::play_puddle_off_fx, ::ambient_puddle_shocks ); } } setup_electric_puddle( generator ) { puddle = SpawnStruct(); puddle.trap_type = "traps_puddle"; // setup generator for use puddle.generator = generator; // setup damage trigger puddle.shock_trig = getent( generator.target, "targetname" ); // setup electro contact point between wire and puddle puddle.contact_points = []; cur_contact_point = getstruct( puddle.shock_trig.target, "targetname" ); puddle.contact_points[ 0 ] = cur_contact_point; while ( isdefined( cur_contact_point.target ) ) { contact_point = getstruct( cur_contact_point.target, "targetname" ); puddle.contact_points[ puddle.contact_points.size ] = contact_point; cur_contact_point = contact_point; } // setup shock fx puddle.shock_fx[ "shock" ] = LoadFX( "vfx/moments/alien/fence_lightning_shock" ); puddle.shock_fx[ "ambient_flash" ] = LoadFX( "vfx/moments/alien/fence_lightning_turn_on" ); puddle.shock_fx[ "sparks" ] = loadfx( "fx/explosions/transformer_sparks_f_sound" ); puddle.shock_fx[ "sparks_sm" ] = loadfx( "fx/explosions/transformer_sparks_b_sound" ); // setup electric trap initial values puddle.running = false; puddle.capacity = CONST_GENERATOR_POWER; puddle.max_capacity = CONST_GENERATOR_POWER; puddle.player_damage = CONST_PUDDLE_PLAYER_DAMAGE; puddle puddle_trap_setup_sizes(); puddle.generator SetCursorHint( "HINT_NOICON" ); puddle.generator SetHintString( puddle.hintString ); puddle.generator MakeUsable(); if ( alien_mode_has( "outline" ) ) maps\mp\alien\_outline_proto::add_to_outline_watch_list ( generator, puddle.cost ); return puddle; } puddle_trap_setup_sizes() { sizes = [ "small", "medium", "large" ]; custom_lifespan_array = []; custom_lifespan_array["small"] = CONST_PUDDLE_LIFE_SPAN_SMALL; custom_lifespan_array["medium"] = CONST_PUDDLE_LIFE_SPAN_MED; custom_lifespan_array["large"] = CONST_PUDDLE_LIFE_SPAN_LARGE; size_index = 1; // default medium if ( isdefined( self.generator.script_noteworthy ) ) { if ( self.generator.script_noteworthy == "small" ) size_index = 0; if ( self.generator.script_noteworthy == "large" ) size_index = 2; //ex: "custom small large" for small cost, large lifespan if(IsSubStr( self.generator.script_noteworthy, "custom")) { tokens = strtok(self.generator.script_noteworthy, " "); switch ( tokens[1] ) { case "small": size_index = 0; break; case "medium": size_index = 1; break; case "large": size_index = 2; break; default: break; } self.custom = tokens[2]; } } if ( isPlayingSolo() ) size_index = int( max( 0, size_index - 1 ) ); self.shock_damage = CONST_PUDDLE_SHOCK_DAMAGE; self.shock_interval = CONST_SHOCK_INTERVAL; if ( sizes[ size_index ] == "small" ) { self.cost = CONST_PUDDLE_COST_SMALL; self.life_span = CONST_PUDDLE_LIFE_SPAN_SMALL; self.hintString = &"ALIEN_COLLECTIBLES_ACTIVATE_PUDDLE_SMALL"; } if ( sizes[ size_index ] == "medium" ) { self.cost = CONST_PUDDLE_COST_MED; self.life_span = CONST_PUDDLE_LIFE_SPAN_MED; self.hintString = &"ALIEN_COLLECTIBLES_ACTIVATE_PUDDLE_MED"; } if ( sizes[ size_index ] == "large" ) { self.cost = CONST_PUDDLE_COST_LARGE; self.life_span = CONST_PUDDLE_LIFE_SPAN_LARGE; self.hintString = &"ALIEN_COLLECTIBLES_ACTIVATE_PUDDLE_LARGE"; } if(IsDefined(self.custom)) { self.life_span = custom_lifespan_array[self.custom]; } } play_puddle_off_fx() { //playfx( self.shock_fx[ "shock" ], self.contact_points[ 0 ].origin ); playfx( self.shock_fx[ "sparks" ], self.contact_points[ 0 ].origin ); count = 3; while ( count > 0 ) { //playfx( self.shock_fx[ "shock" ], self.generator.origin ); playfx( self.shock_fx[ "sparks" ], self.generator.origin ); count--; wait 0.2; } } play_puddle_on_fx() { //playfx( self.shock_fx[ "shock" ], self.contact_points[ 0 ].origin ); playfx( self.shock_fx[ "sparks" ], self.contact_points[ 0 ].origin ); } ambient_puddle_shocks() { // self is fence struct self endon( "death" ); level endon( "game_ended" ); shock = SpawnFx( self.shock_fx[ "sparks" ], self.contact_points[ 0 ].origin ); sparks = []; foreach ( contact_point in self.contact_points ) sparks[ sparks.size ] = SpawnFx( self.shock_fx[ "sparks" ], contact_point.origin ); ambient_on = false; while ( 1 ) { while ( !self.running ) { ambient_on = false; self waittill( "electric_trap_turned_on" ); } if ( !ambient_on ) ambient_on = true; triggerFx( shock ); wait 0.25; triggerFx( shock ); foreach ( idx, spark in sparks ) { // always play contact point spark, which is index 0 if ( idx == 0 ) { triggerFx( spark ); continue; } // 1/4th chance of playing extra sparks if( cointoss() ) triggerFx( spark ); } if ( !is_true ( level.skip_radius_damage_on_puddles ) ) RadiusDamage( self.contact_points[ 0 ].origin, 80, 20, 5 ); waittill_any_timeout( RandomIntRange( 3, 5 ), "electric_trap_turned_off" ); } } //***************************************************************** // ELECTRIC TRAPS: FENCE //***************************************************************** // These costs are likely to be replaced by some table lookup through some kind of init process that Colin is setting up CONST_FENCE_COST = 750; // currency cost to enable fence CONST_FENCE_COST_SMALL = 300; CONST_FENCE_COST_MED = 500; CONST_FENCE_COST_LARGE = 750; CONST_FENCE_LIFE_SPAN = 120; // time out CONST_FENCE_LIFE_SPAN_SMALL = 90; CONST_FENCE_LIFE_SPAN_MED = 120; CONST_FENCE_LIFE_SPAN_LARGE = 150; CONST_FENCE_SHOCK_DAMAGE = 200; // damage per shock CONST_FENCE_PLAYER_DAMAGE = 2; electric_fence_init() { generators = getentarray( "fence_generator", "targetname" ); if ( !isdefined( generators ) || generators.size == 0 ) return; // wait before players are ready, we decide how to setup usage based on match type while ( !isdefined( level.players ) || level.players.size < 1 ) wait 0.05; level.electric_fences = []; foreach ( generator in generators ) { generator.damagefeedback = false; fence = setup_electric_fence( generator ); level.electric_fences[ level.electric_fences.size ] = fence; fence thread run_electric_trap( ::play_fence_on_fx, ::play_fence_off_fx, ::ambient_fence_shocks ); } } setup_electric_fence( generator ) { fence = SpawnStruct(); fence.trap_type = "traps_fence"; // setup generator for use fence.generator = generator; // setup fence corners generator_targets = getstructarray( generator.target, "targetname" ); fence_sparks = []; top_left = generator_targets[ 0 ]; foreach ( generator_target in generator_targets ) { if ( isdefined( generator_target.script_noteworthy ) && generator_target.script_noteworthy == "fence_sparks" ) fence_sparks[ fence_sparks.size ] = generator_target; if ( isdefined( generator_target.script_noteworthy ) && generator_target.script_noteworthy == "fence_area" ) top_left = generator_target; } bottom_left = getstruct( top_left.target, "targetname" ); bottom_right = getstruct( bottom_left.target, "targetname" ); top_right = getstruct( bottom_right.target, "targetname" ); fence.fence_top_left_angles = top_left.angles; fence.fence_top_left = top_left.origin; fence.fence_top_right = top_right.origin; fence.fence_bottom_left = bottom_left.origin; fence.fence_bottom_right = bottom_right.origin; fence.fence_height = fence.fence_top_left[ 2 ] - fence.fence_bottom_left[ 2 ]; fence.fence_center = get_center( top_left.origin, top_right.origin, bottom_left.origin, bottom_right.origin ); fence.fence_sparks = fence_sparks; // setup damage trigger fence.shock_trig = getent( top_right.target, "targetname" ); fence.optimal_height = 100; // should be tweaked based on geo // setup fence fx fence.shock_fx[ "ambient" ] = LoadFX( "vfx/moments/alien/fence_lightning_ambient" ); fence.shock_fx[ "shock" ] = LoadFX( "vfx/moments/alien/fence_lightning_shock" ); fence.shock_fx[ "turn_on" ] = LoadFX( "vfx/moments/alien/fence_lightning_turn_on" ); fence.shock_fx[ "sparks" ] = loadfx( "fx/explosions/transformer_sparks_b_sound" ); fence.shock_fx[ "sparks_sm" ] = loadfx( "fx/explosions/transformer_sparks_f_sound" ); //fence.shock_fx[ "bar" ] = LoadFX( "vfx/gameplay/alien/vfx_alien_fence_bolt_horizontal" ); fence.shock_bar_fx_ent = spawn( "script_origin", fence.fence_center ); fence.shock_bar_fx_ent setmodel( "tag_origin" ); // setup fence initial values fence.running = false; fence.capacity = CONST_GENERATOR_POWER; fence.max_capacity = CONST_GENERATOR_POWER; fence.player_damage = CONST_FENCE_PLAYER_DAMAGE; fence fence_trap_setup_sizes(); fence.generator SetCursorHint( "HINT_NOICON" ); fence.generator SetHintString( fence.hintString ); fence.generator MakeUsable(); if ( alien_mode_has( "outline" ) ) maps\mp\alien\_outline_proto::add_to_outline_watch_list ( generator, fence.cost ); return fence; } fence_trap_setup_sizes() { sizes = [ "small", "medium", "large" ]; custom_lifespan_array = []; custom_lifespan_array["small"] = CONST_FENCE_LIFE_SPAN_SMALL; custom_lifespan_array["medium"] = CONST_FENCE_LIFE_SPAN_MED; custom_lifespan_array["large"] = CONST_FENCE_LIFE_SPAN_LARGE; size_index = 1; // default medium if ( isdefined( self.generator.script_noteworthy ) ) { if ( self.generator.script_noteworthy == "small" ) size_index = 0; if ( self.generator.script_noteworthy == "large" ) size_index = 2; //ex: "custom small large" for small cost, large lifespan if(IsSubStr( self.generator.script_noteworthy, "custom")) { tokens = strtok(self.generator.script_noteworthy, " "); switch ( tokens[1] ) { case "small": size_index = 0; break; case "medium": size_index = 1; break; case "large": size_index = 2; break; default: break; } self.custom = tokens[2]; } } if ( isPlayingSolo() ) size_index = int( max( 0, size_index - 1 ) ); // scaled based on height, shorter the fence the stronger the shock, sinse aliens touch it less self.shock_damage = int( min( 800, CONST_FENCE_SHOCK_DAMAGE * max ( 1, ( self.optimal_height / self.fence_height ) ) ) ); self.shock_interval = CONST_SHOCK_INTERVAL; if ( sizes[ size_index ] == "small" ) { self.cost = CONST_FENCE_COST_SMALL; self.life_span = CONST_FENCE_LIFE_SPAN_SMALL; self.hintString = &"ALIEN_COLLECTIBLES_ACTIVATE_FENCE_SMALL"; if ( isDefined( level.generic_electric_trap_check ) && self [[level.generic_electric_trap_check]]() ) self.hintString = &"ALIEN_COLLECTIBLES_ACTIVATE_PUDDLE_SMALL"; } if ( sizes[ size_index ] == "medium" ) { self.cost = CONST_FENCE_COST_MED; self.life_span = CONST_FENCE_LIFE_SPAN_MED; self.hintString = &"ALIEN_COLLECTIBLES_ACTIVATE_FENCE_MED"; if ( isDefined( level.generic_electric_trap_check ) && self [[level.generic_electric_trap_check]]() ) self.hintString = &"ALIEN_COLLECTIBLES_ACTIVATE_PUDDLE_MED"; } if ( sizes[ size_index ] == "large" ) { self.cost = CONST_FENCE_COST_LARGE; self.life_span = CONST_FENCE_LIFE_SPAN_LARGE; self.hintString = &"ALIEN_COLLECTIBLES_ACTIVATE_FENCE_LARGE"; if ( isDefined( level.generic_electric_trap_check ) && self [[level.generic_electric_trap_check]]() ) self.hintString = &"ALIEN_COLLECTIBLES_ACTIVATE_PUDDLE_LARGE"; } if(IsDefined(self.custom)) { self.life_span = custom_lifespan_array[self.custom]; } } get_center( vec0, vec1, vec2, vec3 ) { x = ( vec0[ 0 ] + vec1[ 0 ] + vec2[ 0 ] + vec3[ 0 ] ) / 4; y = ( vec0[ 1 ] + vec1[ 1 ] + vec2[ 1 ] + vec3[ 1 ] ) / 4; z = ( vec0[ 2 ] + vec1[ 2 ] + vec2[ 2 ] + vec3[ 2 ] ) / 4; return ( x, y, z ); } ambient_fence_shocks() { // self is fence struct self endon( "death" ); level endon( "game_ended" ); ambient_on = false; while ( 1 ) { while ( !self.running ) { ambient_on = false; StopFXOnTag( self.shock_fx[ "ambient" ], self.shock_bar_fx_ent, "tag_origin" ); self waittill( "electric_trap_turned_on" ); } if ( !ambient_on ) { PlayFXOnTag( self.shock_fx[ "ambient" ], self.shock_bar_fx_ent, "tag_origin" ); ambient_on = true; } fence_hp_ratio = self.capacity / self.max_capacity; height = self.fence_height * fence_hp_ratio; end_pos_left = self.fence_bottom_left + ( 0, 0, height ); end_pos_right = self.fence_bottom_right + ( 0, 0, height ); end_pos_center = ( self.fence_center[ 0 ], self.fence_center[ 1 ], end_pos_right[ 2 ] ); playfx( self.shock_fx[ "sparks_sm" ], end_pos_left ); wait 0.3; playfx( self.shock_fx[ "sparks_sm" ], end_pos_center ); wait 0.3; playfx( self.shock_fx[ "sparks_sm" ], end_pos_right ); if ( isdefined( self.fence_sparks ) ) { foreach ( spark in self.fence_sparks ) { playfx( self.shock_fx[ "sparks_sm" ], spark.origin ); wait 0.3; } } debug_electric_trap_print( self.generator.origin, "Power: " + fence_hp_ratio, ( 0.5, 0.5, 1 ), 0.75, 3, 3 ); waittill_any_timeout( RandomIntRange( 2, 3 ), "electric_trap_turned_off" ); } } play_fence_off_fx() { playfx( self.shock_fx[ "shock" ], self.fence_top_left ); playfx( self.shock_fx[ "sparks" ], self.fence_top_left ); playfx( self.shock_fx[ "shock" ], self.fence_top_right ); playfx( self.shock_fx[ "sparks" ], self.fence_top_right ); count = 3; while ( count > 0 ) { playfx( self.shock_fx[ "shock" ], self.generator.origin ); playfx( self.shock_fx[ "sparks" ], self.generator.origin ); count--; wait 0.2; } } play_fence_on_fx() { playfx( self.shock_fx[ "shock" ], self.fence_top_left ); playfx( self.shock_fx[ "sparks" ], self.fence_top_left ); playfx( self.shock_fx[ "shock" ], self.fence_top_right ); playfx( self.shock_fx[ "sparks" ], self.fence_top_right ); } //***************************************************************** // Turrets //***************************************************************** CONST_TURRET_COST = 750; // currency cost to use a turret CONST_TURRET_BULLET_LIMIT = 300; // turret will be disabled after this many bullets fired turret_monitorUse() { level endon( "game_ended" ); self SetCursorHint( "HINT_NOICON" ); self MakeUsable(); // wait till features are defined by level script wait 0.05; if ( alien_mode_has( "outline" ) ) maps\mp\alien\_outline_proto::add_to_outline_watch_list ( self, CONST_TURRET_COST ); disable_turret(); while ( true ) { self waittill ( "trigger", player ); if ( !isPlayer ( player ) ) continue; if ( player is_holding_deployable() ) { player setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HOLDING", 3 ); continue; } turret_cost = int( CONST_TURRET_COST * ( player perk_GetTrapCostScalar() ) ); if ( !is_turret_enabled() ) // player tries to buy the turret { if ( player can_activate_turret() ) { player take_player_currency( turret_cost, false, "trap" ); enable_turret(); level thread maps\mp\alien\_music_and_dialog::playVOForSentry( player, "minigun" ); self thread monitor_player_use(); // handles the ammo counter hud } else { player setLowerMessage( "no_money", &"ALIEN_COLLECTIBLES_NO_MONEY", 3 ); //player iprintlnBold( "Turret costs $" + turret_cost ); } } else // player uses the turret { self.owner = player; wait_for_disable_turret(); disable_turret(); } } } wait_for_disable_turret() { thread watch_bullet_fired(); self waittill ( "disable_turret" ); } watch_bullet_fired() { self endon ( "disable_turret" ); self notify( "stop_fire_monitor" ); self endon( "stop_fire_monitor" ); bullet_fired = 0; turret_ammo = int( CONST_TURRET_BULLET_LIMIT * self.owner perk_GetTrapDurationScalar() ); self.turret_ammo = turret_ammo; fireTime = weaponFireTime( self.weaponinfo ); while ( true ) { self waittill ( "turret_fire" ); self getturretowner() notify( "turret_fire" ); self.heatLevel += fireTime; bullet_fired++; self.cooldownWaitTime = fireTime; self.turret_ammo = ( turret_ammo - bullet_fired ); if ( bullet_fired > turret_ammo ) { self.turret_ammo = 0; break; } self.owner set_turret_ammocount( self.turret_ammo ); } if ( isDefined( self.owner ) && isAlive ( self.owner ) ) self.owner thread wait_for_player_to_dismount_turret(); self notify ( "disable_turret" ); } is_turret_enabled() { return self.enabled; } disable_turret() { self.enabled = false; self SetHintString( &"ALIEN_COLLECTIBLES_ACTIVATE_TURRET" ); self TurretFireDisable(); self makeTurretInoperable(); } enable_turret() { self.enabled = true; self SetHintString( "" ); self TurretFireEnable(); self makeTurretOperable(); } can_activate_turret() { // self is player return self player_has_enough_currency( CONST_TURRET_COST ); } //wait for players to jump on the turret and handle the ammo counter hud monitor_player_use() { self endon ( "turret_disabled" ); while ( 1 ) { self waittill( "trigger",user ); if ( !isDefined ( user ) || !isAlive ( user ) ) continue; if ( user is_holding_deployable() ) { user setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HOLDING", 3 ); continue; } if ( user IsUsingTurret() ) { self.owner = user; while ( !isDefined ( self.turret_ammo ) ) //wait for the ammo to be set on the turret { wait ( 0.05 ); } if( !is_chaos_mode() ) user disable_special_ammo(); user show_turret_icon( 2 ); user set_turret_ammocount( self.turret_ammo ); user SetClientOmnvar( "ui_alien_turret_overheat",0 ); self thread turret_overheat_monitor( user ); self thread turret_cooldown_monitor(); self thread clear_turret_ammo_counter_on_dismount( user ); self thread clear_turret_ammo_counter_on_death( user ); user setLowerMessage( "disengage_turret", &"ALIEN_COLLECTIBLES_DISENGAGE_TURRET", 4 ); } else { user hide_turret_icon(); if( !is_chaos_mode() ) user enable_special_ammo(); self.owner = undefined; user SetClientOmnvar( "ui_alien_turret_overheat",-1 ); user clearLowerMessage( "disengage_turret" ); } } } //self = a turret turret_cooldown_monitor() { self endon ( "death" ); self notify( "stop_cooldown_monitor" ); self endon( "stop_cooldown_monitor" ); self endon( "turret_disabled" ); while( true ) { if( self.heatLevel > 0 ) { if( self.cooldownWaitTime <= 0 ) { self.heatLevel = max( 0, self.heatLevel - 0.05 ); } else { self.cooldownWaitTime = max( 0, self.cooldownWaitTime - 0.05 ); } } wait( 0.05 ); } } //self = a turret turret_overheat_monitor( player ) { self notify( "overheat_monitor" ); self endon( "overheat_monitor" ); self endon( "turret_disabled" ); self.heatLevel = 0; self.cooldownWaitTime = CONST_TURRET_COOLDOWN_TIME; player endon( "disconnect" ); submitted_overheat_value = 0; for( ;; ) { // exceptions if ( !isReallyAlive( player ) ) { self.inUseBy = undefined; player SetClientOmnvar( "ui_alien_turret_overheat",-1 ); break; } if ( !player IsUsingTurret() ) { player SetClientOmnvar( "ui_alien_turret_overheat",-1 ); break; } if ( self.heatLevel >= CONST_TURRET_OVERHEAT_TIME ) barFrac = 1; else barFrac = self.heatLevel/CONST_TURRET_OVERHEAT_TIME; // omnvar throttle throttle = 5; new_value = int( barFrac * 100 ); if ( submitted_overheat_value != new_value ) { if ( new_value <= throttle || ( abs( abs( submitted_overheat_value ) - abs( new_value ) ) > throttle ) ) { player SetClientOmnvar( "ui_alien_turret_overheat" , new_value ); submitted_overheat_value = new_value; } } wait( 0.05 ); } player SetClientOmnvar( "ui_alien_turret_overheat",-1 ); } clear_turret_ammo_counter_on_death( user ) { self notify ( "clearammocounterondeath" ); self endon( "clearammocounterondeath" ); user endon( "disconnect" ); self waittill( "turret_disabled" ); user hide_turret_icon(); user clearLowerMessage( "disengage_turret" ); } clear_turret_ammo_counter_on_dismount( user ) { self notify( "dimountammocounter" ); self endon ( "dismountammocounter" ); user endon( "disconnect" ); while ( user IsUsingTurret() ) //wait for the player to get off the turret before clearing it's owner { wait ( .1 ); } user hide_turret_icon(); self.owner = undefined; user clearLowerMessage( "disengage_turret" ); //restore his wepon if he happened to have a riotshield that got destroyed if ( user GetCurrentWeapon() == "none" ) { user thread restore_last_valid_weapon(); } } //self = a player restore_last_valid_weapon() { weapons = self GetWeaponsListPrimaries(); foreach ( weapon in weapons ) { if ( isDefined ( weapon ) && weapon != "none" ) { self SwitchToWeapon ( weapon ); break; } } } //***************************************************************** // Flare Attractor //***************************************************************** //self = a player monitor_flare_use() { self endon ( "death" ); self endon ( "disconnect" ); level endon ( "game_ended" ); self endon ( "end_monitor_flare_use" ); for ( ;; ) { self waittill( "grenade_fire", flare, weapName ); if ( weapName == "iw6_aliendlc21_mp" ) //sticky flare { flare thread sticky_flare( self ); continue; } if ( weapName != "alienflare_mp" ) continue; flare make_entity_sentient_mp("allies"); flare.threatbias = CONST_FLARE_THREATBIAS; msg = flare waittill_notify_or_timeout_return("missile_stuck",7); if ( IsDefined( msg ) && msg == "timeout" ) { if ( isDefined( flare ) ) { flare delete(); } continue; } glowStick = spawn( "script_model", flare.origin ); flare delete(); glowstick setmodel("mil_emergency_flare_mp"); glowStick.angles = self.angles; glowStick.owner = self; if ( self maps\mp\alien\_persistence::is_upgrade_enabled( "master_scavenger_upgrade" ) ) glowStick thread create_flare(level.spawnGlow["enemy"] , self ); else glowStick thread create_flare(level.spawnGlow["friendly"] , self ); self TakeWeapon( "alienflare_mp" ); } } //self is the thrown flare sticky_flare( player ) { self endon( "death" ); self make_entity_sentient_mp("allies"); self.threatbias = CONST_FLARE_THREATBIAS; self.owner = player; self thread flare_out_of_playable_monitor(); //play the fx while flying through the air self thread create_flare( level._effect["sticky_flare"] , player ); // Play flare sfx self thread sfx_flare_lp( player ); self waittill ("missile_stuck", stuckto ); if ( isDefined( stuckto ) && stuckto is_alien_agent() ) //TODO - how to handle when this happens to a pet , or stuck to a friendly ? { stuckto enable_alien_scripted(); stuckto.stuck_by_flare = true; stuckto SetOrigin( stuckto.origin ); level thread wait_for_flare_finished( stuckto,self ); } else//play sticky loop if on static surface, not a an alien agent { //self thread create_flare( level._effect["sticky_flare_loop"] , player ); alien = undefined; level thread wait_for_flare_finished( alien ,self ); } } //self is a flare flare_out_of_playable_monitor() { self endon( "death" ); self endon ( "missile_stuck" ); wait ( 7 ); if ( isDefined( self ) ) { self delete(); } } //self is an alien wait_for_flare_finished( alien, flare ) { damageowner = flare.owner; flare waittill( "deleting_flare" , org); config = level.placeableConfigs[ "fuse_resin_tnt" ]; //sticky flare config //do explosion playfx ( level._effect[ "sticky_explode" ] ,org ); PlaySoundAtPos( org, "flare_explode_default" ); RadiusDamage( org,config.item_damage_radius,config.item_damage,config.item_damage_falloff,damageowner,"MOD_EXPLOSIVE","iw6_aliendlc22_mp" ); Earthquake( .35,.5,org,512 ); if ( isDefined( alien ) ) { alien disable_alien_scripted(); alien.stuck_by_flare = false; } } create_flare( showEffect, owner ) { self endon ( "death" ); // PlayFXOnTag fails if run on the same frame the parent entity was created wait ( 0.5 ); angles = self getTagAngles( "tag_fire_fx" ); PlayFXOnTag( showEffect, self, "tag_fire_fx" ); self playLoopSound( "emt_road_flare_burn" ); self.flareType = true; if ( owner maps\mp\alien\_persistence::is_upgrade_enabled( "master_scavenger_upgrade" ) ) { self thread flare_attract_aliens( CONST_UPGRADED_FLARE_TIME, owner ); wait( CONST_UPGRADED_FLARE_TIME ); } else { self thread flare_attract_aliens( CONST_FLARE_TIME, owner ); wait( CONST_FLARE_TIME ); } self notify( "deleting_flare", self.origin ); self delete(); } sfx_flare_lp( player ) { self endon( "death" ); beep_interval = 0.163; if ( player maps\mp\alien\_persistence::is_upgrade_enabled( "master_scavenger_upgrade" ) ) FlareEndTime = GetTime() + ( (CONST_UPGRADED_FLARE_TIME ) - 1.4) * 1000; else FlareEndTime = GetTime() + ( (CONST_FLARE_TIME ) - 1.4) * 1000; wait 0.2; while ( GetTime() < FlareEndTime && IsDefined( self ) ) { PlaySoundAtPos(self.origin, "flare_beep"); wait beep_interval; } // Final Beeps PlaySoundAtPos(self.origin, "flare_beep_end"); } FLARE_AFFECT_RADIUS = 512; FLARE_MAX_NUM_ALIEN_AFFECTED = 6; flare_attract_aliens( flare_time, owner ) { attractEndTime = GetTime() + flare_time * 1000; affectedAliens = []; while ( GetTime() < attractEndTime && IsDefined( self ) ) { currentAffectedAliens = []; foreach( alien in affectedAliens ) { if ( IsDefined( alien ) && IsAlive( alien ) ) currentAffectedAliens[currentAffectedAliens.size] = alien; } affectedAliens = currentAffectedAliens; if ( IsDefined( owner ) ) focal_point = owner.origin; else focal_point = self.origin; possibleFlareVictims = self get_possible_flare_victims( focal_point ); for ( alienIndex = 0; alienIndex < possibleFlareVictims.size && affectedAliens.size < FLARE_MAX_NUM_ALIEN_AFFECTED; alienIndex++ ) { alien = possibleFlareVictims[alienIndex]; if ( !alien should_attract_alien() ) continue; if ( IsDefined( alien.attractor_flare ) ) continue; alien maps\mp\agents\alien\_alien_think::handle_attractor_flare( self, true ); affectedAliens[affectedAliens.size] = alien; } wait 0.2; } foreach ( alien in affectedAliens ) alien maps\mp\agents\alien\_alien_think::handle_attractor_flare( self, false ); } get_possible_flare_victims( focal_point ) { maxOwnerVictimsPriorityRangeSq = FLARE_AFFECT_RADIUS * FLARE_AFFECT_RADIUS * 0.5; aliens = maps\mp\agents\_agent_utility::getActiveAgentsOfType( "alien" ); victimsAroundOwner = get_array_of_closest( focal_point, aliens, undefined, undefined, FLARE_AFFECT_RADIUS ); victimsAroundFlare = get_array_of_closest( self.origin, aliens, undefined, undefined, FLARE_AFFECT_RADIUS ); possibleVictims = []; for ( ownerRangeIndex = 0; ownerRangeIndex < victimsAroundOwner.size; ownerRangeIndex++ ) { victim = victimsAroundOwner[ownerRangeIndex]; if ( array_contains( possibleVictims, victim ) ) continue; distanceToOwner = DistanceSquared( focal_point, victim.origin ); if ( distanceToOwner <= maxOwnerVictimsPriorityRangeSq ) possibleVictims[possibleVictims.size] = victim; else break; } flareRangeIndex = 0; while ( ownerRangeIndex < victimsAroundOwner.size || flareRangeIndex < victimsAroundFlare.size ) { ownerDistance = undefined; ownerVictim = undefined; while ( ownerRangeIndex < victimsAroundOwner.size ) { ownerVictim = victimsAroundOwner[ownerRangeIndex]; if ( !array_contains( possibleVictims, ownerVictim ) ) { ownerDistance = DistanceSquared( focal_point, ownerVictim.origin ); break; } ownerRangeIndex++; } while ( flareRangeIndex < victimsAroundFlare.size ) { flareVictim = victimsAroundFlare[flareRangeIndex]; if ( array_contains( possibleVictims, flareVictim ) ) { flareRangeIndex++; continue; } flareDistance = DistanceSquared( self.origin, flareVictim.origin ); if ( !IsDefined( ownerDistance ) || flareDistance < ownerDistance ) possibleVictims[possibleVictims.size] = flareVictim; else break; flareRangeIndex++; } if ( IsDefined( ownerDistance ) ) { possibleVictims[possibleVictims.size] = ownerVictim; ownerRangeIndex++; } } return possibleVictims; } should_attract_alien() { if ( is_true ( self.stuck_by_flare ) ) //don't attract this guy to himself ( ! ) return false; switch ( self maps\mp\alien\_utility::get_alien_type() ) { case "elite": case "mammoth": case "gargoyle": return false; default: return true; } } deleteOnDeath( ent ) { self waittill( "death" ); if ( IsDefined( ent ) ) ent delete(); } // ========= EASTER EGG ACTIVATION ========= // via shooting the lodge sign spelling "lol" in under 3 seconds CONST_EASTER_EGG_LODGE_SIGN_ACTIVE_TIME = 120; // seconds for easter egg to last CONST_EASTER_EGG_LODGE_SIGN_TIMER = 5000; // miliseconds to complete the activation CONST_EASTER_EGG_LODGE_SIGN_ACTIVATOR_WEAPON = "iw6_alienvks_mp_alienvksscope"; // weapon used to activate easter_egg_lodge_sign() { level endon( "game_ended" ); level notify( "easter_egg_lodge_sign_reset" ); level endon( "easter_egg_lodge_sign_reset" ); letter_l = getent( "easter_egg_letter_l", "targetname" ); letter_o = getent( "easter_egg_letter_o", "targetname" ); letter_reset = getent( "easter_egg_letter_reset", "targetname" ); if ( !isdefined( letter_l ) || !isdefined( letter_o ) || !isdefined( letter_reset ) ) return; letter_reset thread watch_for_letter_reset(); while ( 1 ) { letter_l waittill( "damage", damage, attacker, direction_vec, point, type ); timer = gettime(); if ( !is_letter_valid_hit( attacker, type ) ) continue; //iprintlnBold( "^5L" ); letter_o waittill( "damage", damage, attacker, direction_vec, point, type ); if ( !is_letter_valid_hit( attacker, type ) ) continue; //iprintlnBold( "^6O" ); letter_l waittill( "damage", damage, attacker, direction_vec, point, type ); if ( !is_letter_valid_hit( attacker, type ) || ( gettime() - timer ) > CONST_EASTER_EGG_LODGE_SIGN_TIMER ) continue; //iprintlnBold( "^5L" ); wait 1; iprintlnBold( "^5L^6O^5L" ); level.easter_egg_lodge_sign_active = true; wait CONST_EASTER_EGG_LODGE_SIGN_ACTIVE_TIME; // time for easter egg to stay active level.easter_egg_lodge_sign_active = false; } } is_letter_valid_hit( attacker, type ) { // self is sign letter if ( !isdefined( attacker ) || !isPlayer( attacker ) ) return false; attacker_weapon = attacker GetCurrentWeapon(); if ( !isdefined( attacker_weapon ) || attacker_weapon != CONST_EASTER_EGG_LODGE_SIGN_ACTIVATOR_WEAPON ) return false; type = ToLower( type ); if ( !isdefined( type ) || type != "mod_rifle_bullet" ) return false; return true; } watch_for_letter_reset() { while ( 1 ) { self waittill( "damage", damage, attacker, direction_vec, point, type, modelName, tagName, partName, iDFlags, weapon ); if ( !isdefined( attacker ) || !isPlayer( attacker ) ) continue; if ( isdefined( level.easter_egg_lodge_sign_active ) && level.easter_egg_lodge_sign_active ) continue; //iprintlnBold( "^5>_<" ); wait 1; return easter_egg_lodge_sign(); } }