#include common_scripts\utility; //#include maps\_hud_util; #using_animtree( "sentry_gun" ); /*QUAKED script_model_pickup_sentry_gun (1 0 0) (-32 -16 0) (32 16 24) ORIENT_LOD NO_SHADOW NO_STATIC_SHADOWS defaultmdl="sentry_gun_folded" default:"model" "sentry_gun_folded" */ /*QUAKED script_model_pickup_sentry_minigun (1 0 0) (-32 -16 0) (32 16 24) ORIENT_LOD NO_SHADOW NO_STATIC_SHADOWS defaultmdl="sentry_minigun_folded" default:"model" "sentry_minigun_folded" */ /* code support: -physics on turrets todo: -make hint print while in placement mode -hit max number of turrets at 32, but I could limit the number allowed -get behind turret and change team */ /* Constants */ // default sentry_updateTime = 0.05; shielded_sentry_health = 350;// direct hit from an RPG shielded_sentry_bullet_armor = 2000; minigun_sentry_health = 190;// frag grenade does 200 inner damage minigun_sentry_bullet_armor = 1200; minigun_sentry_bullet_armor_enemy = 0; // mp shielded_sentry_bullet_armor_mp = 300; minigun_sentry_bullet_armor_mp = 300; sentry_mode_name_on = "sentry"; sentry_mode_name_off = "sentry_offline"; main() { precacheModel( "sentry_minigun" ); precacheModel( "sentry_minigun_obj" ); precacheModel( "sentry_minigun_obj_red" ); precacheModel( "sentry_minigun_folded_obj" ); precacheModel( "sentry_minigun_destroyed" ); precacheModel( "sentry_gun" ); precacheModel( "sentry_gun_obj" ); precacheModel( "sentry_gun_obj_red" ); precacheModel( "sentry_gun_folded_obj" ); precacheModel( "sentry_gun_destroyed" ); precacheModel( "tag_laser" ); if ( isSP() ) { precacheTurret( "sentry_gun" ); precacheTurret( "sentry_minigun" ); precacheTurret( "sentry_minigun_enemy" ); } else { precacheTurret( "sentry_gun_mp" ); precacheTurret( "sentry_minigun_mp" ); } // LANG_ENGLISH Press and hold ^3&&1^7 to move the turret." precacheString( &"SENTRY_MOVE" ); // Press and hold ^3&&1^7 to pick up the turret. precacheString( &"SENTRY_PICKUP" ); precacheString( &"SENTRY_PLACE" ); precacheString( &"SENTRY_CANNOT_PLACE" ); level._effect[ "sentry_turret_overheat_smoke_sp" ] = loadfx( "smoke/sentry_turret_overheat_smoke_sp" ); level._effect[ "sentry_turret_explode" ] = loadfx( "explosions/sentry_gun_explosion" ); level._effect[ "sentry_turret_explode_smoke" ] = loadfx( "smoke/car_damage_blacksmoke" ); level.sentry_settings = []; level.sentry_settings[ "sentry_gun" ] = spawnStruct(); sentry_gun_default_settings( "sentry_gun" ); level.sentry_settings[ "sentry_minigun" ] = spawnStruct(); sentry_minigun_default_settings( "sentry_minigun" ); if ( isSP() ) { // sentry overheat override settings level.sentry_overheating_speed = 1; // 1 heat points per second level.sentry_cooling_speed = 1; // 1 heat points cooling per second if ( !isdefined( level.sentry_fire_time ) ) level.sentry_fire_time = 8; // seconds of continous fire ( aka heat points ) if ( !isdefined( level.sentry_cooldown_time ) ) level.sentry_cooldown_time = 4; // seconds of continous fire ( aka heat points ) } level.sentryTurretSettings[ "easy" ][ "convergencePitchTime" ] = 2.5; level.sentryTurretSettings[ "easy" ][ "convergenceYawTime" ] = 2.5; level.sentryTurretSettings[ "easy" ][ "suppressionTime" ] = 3.0; level.sentryTurretSettings[ "easy" ][ "aiSpread" ] = 2.0; level.sentryTurretSettings[ "easy" ][ "playerSpread" ] = 0.5; // for pre-placed guns guns = getentarray( "sentry_gun", "targetname" ); mini_guns = getentarray( "sentry_minigun", "targetname" ); foreach( gun in guns ) { gun sentry_init( undefined, "sentry_gun" ); } foreach( minigun in mini_guns ) { minigun sentry_init( undefined, "sentry_minigun" ); } array_thread( getentarray( "script_model_pickup_sentry_gun", "classname" ), ::sentry_pickup_init, "sentry_gun" ); array_thread( getentarray( "script_model_pickup_sentry_minigun", "classname" ), ::sentry_pickup_init, "sentry_minigun" ); } sentry_gun_default_settings( type ) { level.sentry_settings[ type ].burst_shots_min = 10; level.sentry_settings[ type ].burst_shots_max = 35; level.sentry_settings[ type ].burst_pause_min = 0.2; level.sentry_settings[ type ].burst_pause_max = 0.8; level.sentry_settings[ type ].model = "sentry_gun"; level.sentry_settings[ type ].destroyedModel = "sentry_gun_destroyed"; level.sentry_settings[ type ].pickupModel = "sentry_gun_folded"; level.sentry_settings[ type ].pickupModelObj = "sentry_gun_folded_obj"; level.sentry_settings[ type ].placementmodel = "sentry_gun_obj"; level.sentry_settings[ type ].placementmodelfail = "sentry_gun_obj_red"; level.sentry_settings[ type ].health = shielded_sentry_health; if ( isSP() ) { level.sentry_settings[ type ].damage_smoke_time = 15; level.sentry_settings[ type ].weaponInfo = "sentry_gun"; level.sentry_settings[ type ].targetname = "sentry_gun"; } else { level.sentry_settings[ type ].damage_smoke_time = 5; level.sentry_settings[ type ].weaponInfo = "sentry_gun_mp"; level.sentry_settings[ type ].targetname = "sentry_gun_mp"; } } sentry_minigun_default_settings( type ) { level.sentry_settings[ type ].burst_shots_min = 20; level.sentry_settings[ type ].burst_shots_max = 60; level.sentry_settings[ type ].burst_pause_min = 0.5; level.sentry_settings[ type ].burst_pause_max = 1.3; level.sentry_settings[ type ].model = "sentry_minigun"; level.sentry_settings[ type ].destroyedModel = "sentry_minigun_destroyed"; level.sentry_settings[ type ].pickupModel = "sentry_minigun_folded"; level.sentry_settings[ type ].pickupModelObj = "sentry_minigun_folded_obj"; level.sentry_settings[ type ].placementmodel = "sentry_minigun_obj"; level.sentry_settings[ type ].placementmodelfail = "sentry_minigun_obj_red"; level.sentry_settings[ type ].health = minigun_sentry_health; if ( isSP() ) { level.sentry_settings[ type ].damage_smoke_time = 15; level.sentry_settings[ type ].anim_loop = %minigun_spin_loop; level.sentry_settings[ type ].weaponInfo = "sentry_minigun"; level.sentry_settings[ type ].targetname = "sentry_minigun"; } else { level.sentry_settings[ type ].damage_smoke_time = 5; level.sentry_settings[ type ].weaponInfo = "sentry_minigun_mp"; level.sentry_settings[ type ].targetname = "sentry_minigun_mp"; } } sentry_pickup_init( sentryType ) { assert( isdefined( sentryType ) ); assert( isdefined( level.sentry_settings[ sentryType ] ) ); self setModel( self.model ); self.sentryType = sentryType; self setCursorHint( "HINT_NOICON" ); // Press and hold ^3&&1^7 to pick up the turret. self setHintString( &"SENTRY_PICKUP" ); self makeUsable(); self thread folded_sentry_use_wait( sentryType ); } giveSentry( sentryType ) { assert( isdefined( level.sentry_settings ) ); assert( isdefined( level.sentry_settings[ sentryType ] ) ); self.last_sentry = sentryType; self thread spawn_and_place_sentry( sentryType ); } sentry_init( team, sentryType, owner ) { if ( !isdefined( team ) ) { assert( isdefined( self.script_team ) ); if ( !isdefined( self.script_team ) ) self.script_team = "axis"; team = self.script_team; } assert( isDefined( team ) ); assert( isDefined( sentryType ) ); self setTurretModeChangeWait( true ); self makeSentrySolid(); self makeTurretInoperable(); self SentryPowerOn(); self setCanDamage( true ); self setDefaultDropPitch( -89.0 ); // setting this mainly prevents Turret_RestoreDefaultDropPitch() from running if ( isSP() || level.teambased ) self setTurretTeam( team ); self.sentryType = sentryType; self.isSentryGun = true; self.kill_reward_money = 350; self.kill_melee_reward_money = 400; self.sentry_battery_timer = 60; // sec //bullet armor acts as an extra pool of health for bullet damage. //once its removed bullet damage affects the sentry like other kinds of damage. if ( isSP() ) { if ( self.weaponinfo == "sentry_gun" )// sentry_minigun and sentry_minigun_enemy get the same settings self.bullet_armor = shielded_sentry_bullet_armor; else { self.bullet_armor = minigun_sentry_bullet_armor; } } else { if ( self.weaponinfo == "sentry_gun" ) self.bullet_armor = shielded_sentry_bullet_armor_mp; else self.bullet_armor = minigun_sentry_bullet_armor_mp; } if ( isSP() ) { self call [[ level.makeEntitySentient_func ]]( team ); self self_func( "useanimtree", #animtree ); if ( isdefined( self.script_team ) && self.script_team == "axis" ) self thread enemy_sentry_difficulty_settings(); } self.health = level.sentry_settings[ sentryType ].health; self sentry_badplace_create(); self thread sentry_beep_sounds(); self thread sentry_enemy_wait(); self thread sentry_death_wait(); if ( !isSP() ) { self thread sentry_emp_wait(); self thread sentry_emp_damage_wait(); } self thread sentry_health_monitor(); self thread sentry_player_use_wait(); if ( !isdefined( owner ) ) { if( isSP() ) owner = level.player; } assert( isdefined( owner ) ); self sentry_set_owner( owner ); self thread sentry_destroy_on_owner_leave( owner ); if ( !isdefined( self.damage_functions ) ) self.damage_functions = []; if ( getdvar( "money_enable", "0" ) == "1" && self.team == "axis" ) { if ( isdefined( level.sentry_money_init_func ) ) self thread [[ level.sentry_money_init_func ]](); } } sentry_death_wait() { self endon( "deleted" ); //self waittill_player_or_sentry_death(); self waittill( "death", attacker, cause ); if ( isdefined( level.stat_track_kill_func ) && isdefined( attacker ) ) attacker [[ level.stat_track_kill_func ]]( self, cause ); if ( !isSP() ) { self removeFromTurretList(); self thread sentry_place_mode_reset(); } self thread sentry_burst_fire_stop(); if ( isdefined( level.laserOff_func ) ) self call [[ level.laserOff_func ]](); assert( isdefined( level.sentry_settings[ self.sentryType ] ) ); assert( isdefined( level.sentry_settings[ self.sentryType ].destroyedModel ) ); self setmodel( level.sentry_settings[ self.sentryType ].destroyedModel ); self SentryPowerOff(); if ( isSP() ) self call [[ level.freeEntitySentient_func ]](); if ( !isSP() && isDefined( attacker ) && isPlayer( attacker ) ) { if ( isDefined( self.owner ) ) self.owner thread [[level.leaderDialogOnPlayer_func]]( "destroy_sentry", "sentry_status" ); attacker thread [[ level.onXPEvent ]]( "kill" ); } self setSentryCarried( false ); self SetCanDamage( true ); self.ignoreMe = false; self makeUnusable(); self SetSentryOwner( undefined ); self SetTurretMinimapVisible( false ); self playsound( "sentry_explode" ); playfxOnTag( getfx( "sentry_turret_explode" ), self, "tag_aim" ); if ( isSP() ) self setContents( 0 ); wait 1.5; self playsound( "sentry_explode_smoke" ); timeToSteam = level.sentry_settings[ self.sentryType ].damage_smoke_time * 1000; startTime = getTime(); for ( ;; ) { playfxOnTag( getfx( "sentry_turret_explode_smoke" ), self, "tag_aim" ); wait .4; if ( getTime() - startTime > timeToSteam ) break; } if ( !isSP() ) self thread removeDeadSentry(); } handle_sentry_on_carrier_death( sentry ) { level endon( "game_ended" ); self endon( "sentry_placement_finished" ); self waittill( "death" ); if ( isSp() ) { sentry notify( "death" ); return; } if ( !self.canPlaceEntity ) { sentry sentry_place_mode_reset(); sentry notify( "deleted" ); waittillframeend; sentry delete(); return; } if ( !isSp() ) { self thread place_sentry( sentry ); } } kill_sentry_on_carrier_disconnect( sentry ) { level endon( "game_ended" ); self endon( "sentry_placement_finished" ); self waittill( "disconnect" ); sentry notify( "death" ); } sentry_player_use_wait() { level endon( "game_ended" ); self endon( "death" ); assert( isDefined( self.sentryType ) ); if ( self.health <= 0 ) return; for ( ;; ) { self waittill( "trigger", player ); if ( isDefined( player.placingSentry ) ) continue; // only owner of sentry can move sentry in MP if ( !isSP() ) { // Checked through code now; Assert left for reference. assert( isDefined( self.owner ) ); assert( player == self.owner ); } break; } player thread handle_sentry_on_carrier_death( self ); player thread kill_sentry_on_carrier_disconnect( self ); player thread sentry_placement_endOfLevel_cancel_monitor( self ); if ( !isSP() && !isAlive( player ) ) return; if ( !isSP() ) self sentry_team_hide_icon(); self SentryPowerOff();// makes the turret non - operational while being moved player.placingSentry = self; self setSentryCarried( true ); self.ignoreMe = true; self SetCanDamage( false ); player _disableWeapon(); //player _disableUsability(); self makeSentryNotSolid(); self sentry_badplace_delete(); player thread move_sentry_wait( self ); player thread updateSentryPositionThread( self ); } sentry_badplace_create() { if ( !isSP() ) return; self.badplace_name = "" + getTime(); call [[ level.badplace_cylinder_func ]]( self.badplace_name, 0, self.origin, 32, 128, self.team, "neutral" ); } sentry_badplace_delete() { if ( !isSP() ) return; assert( isdefined( self.badplace_name ) ); call [[ level.badplace_delete_func ]]( self.badplace_name ); self.badplace_name = undefined; } move_sentry_wait( sentry ) { level endon( "game_ended" ); sentry endon( "death" ); sentry endon( "deleted" ); self endon( "death" ); self endon( "disconnect" ); assert( isdefined( sentry ) ); for ( ;; ) { //debounce self waitActivateButton( false ); // wait for button press self waitActivateButton( true ); updateSentryPosition( sentry ); if ( self.canPlaceEntity ) break; } place_sentry( sentry ); } place_sentry( sentry ) { if ( !isSP() ) { self endon( "death" ); level endon( "end_game" ); } self.placingSentry = undefined; sentry setSentryCarried( false ); sentry SetCanDamage( true ); sentry.ignoreMe = false; if ( !maps\_utility::is_coop() || !maps\_utility::is_player_down_and_out( self ) ) { self _enableWeapon(); //self _enableUsability(); } else { // Manually decrease the disabledWeapon count because we didn't actually call _enableWeapon() // This is necessary because co-op revive needs to prevent the enable from happening, but sentries need to // still keep count as if they did get re-enabled so that the next time you place a turret it works. self.disabledWeapon--; } sentry makeSentrySolid(); sentry setmodel( level.sentry_settings[ sentry.sentryType ].model ); sentry sentry_badplace_create(); assert( isdefined( sentry.contents ) ); sentry setContents( sentry.contents ); self notify( "sentry_placement_finished", sentry ); sentry notify( "sentry_carried" ); sentry.overheated = false; self sentry_placement_hint_hide(); if ( !isSP() ) sentry sentry_team_show_icon(); sentry SentryPowerOn(); thread play_sound_in_space( "sentry_gun_plant", sentry.origin ); //debounce self waitActivateButton( false ); sentry thread sentry_player_use_wait(); } sentry_enemy_wait() { level endon( "game_ended" ); self endon( "death" ); self thread sentry_overheat_monitor(); for ( ;; ) { self waittill_either( "turretstatechange", "cooled" ); if ( self isFiringTurret() ) { self thread sentry_burst_fire_start(); if ( isdefined( level.laserOn_func ) ) self call [[ level.laserOn_func ]](); } else { self thread sentry_burst_fire_stop(); if ( isdefined( level.laserOff_func ) ) self call [[ level.laserOff_func ]](); } } } // Sentry overheat behavoir for SP ==================================================== // Note: To enable for mp, take out the isSP() check in main() function for overheat override variables. // However, there might be some unseen behavoiral conflicts with battery timer, currently SP only - Julian // Note 2: Turrets now have a code ersion of doing this, we probably don't want to mix and match both, so this should be // cleaned up / removed after MW2 sentry_overheat_monitor() { self endon( "death" ); assert( isDefined( self ) ); assert( isDefined( self.sentryType ) ); if ( self.sentryType != "sentry_minigun" ) return; if ( !isdefined( level.sentry_overheating_speed ) ) return; self.overheat = 0; self.overheated = false; if ( getdvarint( "sentry_overheat_debug" ) == 1 ) self thread sentry_overheat_debug(); while ( true ) { if ( self.overheat >= ( level.sentry_fire_time * 10 ) ) { self thread sentry_overheat_deactivate(); self waittill_either( "cooled", "sentry_carried" ); } if ( self IsFiringTurret() ) { self.overheat += 1; } else { if ( self.overheat > 0 ) self.overheat -= 1; } wait 0.1/level.sentry_overheating_speed; } } sentry_cooling() { self endon( "death" ); while ( self.overheated ) { if ( self.overheat > 0 ) self.overheat -= 1; wait 0.1/level.sentry_overheating_speed; } } sentry_overheat_debug() { self endon( "death" ); while( true ) { overheat_value = self.overheat / (level.sentry_fire_time*10); overheat_print_l = "[ "; overheat_print_r = " ]"; if( self.overheated ) { overheat_print_l = "{{{ "; overheat_print_r = " }}}"; } print3d( self.origin + ( 0,0,45 ), overheat_print_l + self.overheat + " / " + level.sentry_fire_time*10 + overheat_print_r, ( 0+overheat_value, 1-overheat_value, 1-overheat_value ), 1, 0.35, 4 ); wait 0.2; } } sentry_overheat_deactivate() { self endon( "death" ); self notify( "overheated" ); self.overheated = true; self sentry_burst_fire_stop(); self thread sentry_overheat_reactivate(); } sentry_overheat_reactivate() { self endon( "death" ); self endon( "sentry_carried" ); self thread sentry_cooling(); wait level.sentry_cooldown_time; self notify( "cooled" ); self.overheat = 0; self.overheated = false; } // END of sentry overheat behavoir for SP ================================================= sentry_burst_fire_start() { self endon( "death" ); level endon( "game_ended" ); if ( isdefined( self.overheated ) && self.overheated ) return; self thread fire_anim_start(); self endon( "stop_shooting" ); self notify( "shooting" ); assert( isdefined( self.weaponinfo ) ); fireTime = weaponFireTime( self.weaponinfo ); assert( isdefined( fireTime ) && fireTime > 0 ); for ( ;; ) { self turret_start_anim_wait(); numShots = randomintrange( level.sentry_settings[ self.sentryType ].burst_shots_min, level.sentry_settings[ self.sentryType ].burst_shots_max ); for ( i = 0 ; i < numShots ; i++ ) { if ( self canFire() ) self shootTurret(); wait fireTime; } wait randomfloatrange( level.sentry_settings[ self.sentryType ].burst_pause_min, level.sentry_settings[ self.sentryType ].burst_pause_max ); } } sentry_allowFire( bAllow, timeOut ) { self notify( "allowFireThread" ); self endon( "allowFireThread" ); self endon( "death" ); self.taking_damage = bAllow; if ( isdefined( timeOut ) && !bAllow ) { wait timeOut; if ( isdefined( self ) ) self thread sentry_allowFire( true ); } } canFire() { if ( !isdefined( self.taking_damage ) ) return true; return self.taking_damage; } sentry_burst_fire_stop() { self thread fire_anim_stop(); self notify( "stop_shooting" ); self thread sentry_steam(); } sentry_steam() { self endon( "shooting" ); self endon( "deleted" ); wait randomfloatrange( 0.0, 1.0 ); timeToSteam = 6 * 1000; startTime = getTime(); // temp sound fx if ( isdefined( self ) ) self playsound( "sentry_steam" ); while ( isdefined( self ) ) { playfxOnTag( getfx( "sentry_turret_overheat_smoke_sp" ), self, "tag_flash" ); wait .3; if ( getTime() - startTime > timeToSteam ) break; } } turret_start_anim_wait() { if ( isdefined( self.allow_fire ) && self.allow_fire == false ) self waittill( "allow_fire" ); } fire_anim_start() { self notify( "anim_state_change" ); self endon( "anim_state_change" ); self endon( "stop_shooting" ); self endon( "deleted" ); level endon( "game_ended" ); self endon( "death" ); if ( !isdefined( level.sentry_settings[ self.sentryType ].anim_loop ) ) return; self.allow_fire = false; //ramp up the animation from 0.1 speed to 1.0 speed over time if ( !isdefined( self.momentum ) ) self.momentum = 0; self thread fire_sound_spinup(); for ( ;; ) { if ( self.momentum >= 1.0 ) break; self.momentum += 0.1; self.momentum = cap_value( self.momentum, 0.0, 1.0 ); if ( isSP() ) self self_func( "setanim", level.sentry_settings[ self.sentryType ].anim_loop, 1.0, 0.2, self.momentum ); wait 0.2; } self.allow_fire = true; self notify( "allow_fire" ); } delete_sentry_turret() { self notify( "deleted" ); wait .05; self notify( "death" ); if ( isDefined( self.obj_overlay ) ) self.obj_overlay delete(); if ( isDefined( self.cam ) ) self.cam delete(); self delete(); } fire_anim_stop() { self notify( "anim_state_change" ); self endon( "anim_state_change" ); if ( !isdefined( level.sentry_settings[ self.sentryType ].anim_loop ) ) return; self thread fire_sound_spindown(); self.allow_fire = false; for ( ;; ) { if ( !isdefined( self.momentum ) ) break; if ( self.momentum <= 0.0 ) break; self.momentum -= 0.1; self.momentum = cap_value( self.momentum, 0.0, 1.0 ); if ( isSP() ) self self_func( "setanim", level.sentry_settings[ self.sentryType ].anim_loop, 1.0, 0.2, self.momentum ); wait 0.2; } } fire_sound_spinup() { self notify( "sound_state_change" ); self endon( "sound_state_change" ); self endon( "deleted" ); if ( self.momentum < 0.25 ) { self playsound( "sentry_minigun_spinup1" ); wait 0.6; self playsound( "sentry_minigun_spinup2" ); wait 0.5; self playsound( "sentry_minigun_spinup3" ); wait 0.5; self playsound( "sentry_minigun_spinup4" ); wait 0.5; } else if ( self.momentum < 0.5 ) { self playsound( "sentry_minigun_spinup2" ); wait 0.5; self playsound( "sentry_minigun_spinup3" ); wait 0.5; self playsound( "sentry_minigun_spinup4" ); wait 0.5; } else if ( self.momentum < 0.75 ) { self playsound( "sentry_minigun_spinup3" ); wait 0.5; self playsound( "sentry_minigun_spinup4" ); wait 0.5; } else if ( self.momentum < 1 ) { self playsound( "sentry_minigun_spinup4" ); wait 0.5; } thread fire_sound_spinloop(); } fire_sound_spinloop() { self endon( "death" ); self notify( "sound_state_change" ); self endon( "sound_state_change" ); while ( 1 ) { self playsound( "sentry_minigun_spin" ); wait 2.5; } } fire_sound_spindown() { self notify( "sound_state_change" ); self endon( "sound_state_change" ); self endon( "deleted" ); if ( !isdefined( self.momentum ) ) return; if ( self.momentum > 0.75 ) { self stopsounds(); self playsound( "sentry_minigun_spindown4" ); wait 0.5; self playsound( "sentry_minigun_spindown3" ); wait 0.5; self playsound( "sentry_minigun_spindown2" ); wait 0.5; self playsound( "sentry_minigun_spindown1" ); wait 0.65; } else if ( self.momentum > 0.5 ) { self playsound( "sentry_minigun_spindown3" ); wait 0.5; self playsound( "sentry_minigun_spindown2" ); wait 0.5; self playsound( "sentry_minigun_spindown1" ); wait 0.65; } else if ( self.momentum > 0.25 ) { self playsound( "sentry_minigun_spindown2" ); wait 0.5; self playsound( "sentry_minigun_spindown1" ); wait 0.65; } else { self playsound( "sentry_minigun_spindown1" ); wait 0.65; } } sentry_beep_sounds() { self endon( "death" ); for ( ;; ) { wait randomfloatrange( 3.5, 4.5 ); self thread play_sound_in_space( "sentry_gun_beep", self.origin + ( 0, 0, 40 ) ); } } spawn_and_place_sentry( sentryType ) { level endon( "game_ended" ); assert( self.classname == "player" ); assert( isdefined( sentryType ) ); assert( isdefined( level.sentry_settings[ sentryType ] ) ); assert( isdefined( level.sentry_settings[ sentryType ].placementmodel ) ); assert( isdefined( level.sentry_settings[ sentryType ].placementmodelfail ) ); if ( isdefined( self.placingSentry ) ) return; self _disableWeapon(); //self _disableUsability(); self notify( "placingSentry" ); assert( isdefined( level.sentry_settings[ sentryType ] ) ); assert( isdefined( level.sentry_settings[ sentryType ].weaponInfo ) ); assert( isdefined( level.sentry_settings[ sentryType ].model ) ); assert( isdefined( level.sentry_settings[ sentryType ].targetname ) ); sentry_gun = spawnTurret( "misc_turret", self.origin, level.sentry_settings[ sentryType ].weaponInfo ); sentry_gun setmodel( level.sentry_settings[ sentryType ].placementModel ); sentry_gun.weaponinfo = level.sentry_settings[ sentryType ].weaponInfo; sentry_gun.targetname = level.sentry_settings[ sentryType ].targetname; sentry_gun.weaponName = level.sentry_settings[ sentryType ].weaponInfo; sentry_gun.angles = self.angles; sentry_gun.team = self.team; sentry_gun.attacker = self; sentry_gun.sentryType = sentryType; sentry_gun makeTurretInoperable(); sentry_gun sentryPowerOff(); sentry_gun setCanDamage( false ); sentry_gun sentry_set_owner( self ); sentry_gun setDefaultDropPitch( -89.0 ); // setting this mainly prevents Turret_RestoreDefaultDropPitch() from running self.placingSentry = sentry_gun; sentry_gun setSentryCarried( true ); sentry_gun SetCanDamage( false ); sentry_gun.ignoreMe = true; if ( !isSP() ) sentry_gun addToTurretList(); // wait to delete the sentry when cancelled self thread sentry_placement_cancel_monitor( sentry_gun ); // wait to delete the sentry on end of level self thread sentry_placement_endOfLevel_cancel_monitor( sentry_gun ); // wait until the player plants the sentry self thread sentry_placement_initial_wait( sentry_gun ); // keep the indicator model positioned with traces forever until the thread is ended self thread updateSentryPositionThread( sentry_gun ); // wait until the turret placement has been finished or canceled if ( !isSP() ) self waittill_any( "sentry_placement_finished", "sentry_placement_canceled", "death" ); else self waittill_any( "sentry_placement_finished", "sentry_placement_canceled" ); self sentry_placement_hint_hide(); if ( !maps\_utility::is_coop() || !maps\_utility::is_player_down_and_out( self ) ) { self _enableWeapon(); //self _enableUsability(); } else { // Manually decrease the disabledWeapon count because we didn't actually call _enableWeapon() // This is necessary because co-op revive needs to prevent the enable from happening, but sentries need to // still keep count as if they did get re-enabled so that the next time you place a turret it works. self.disabledWeapon--; } self.placingSentry = undefined; sentry_gun setSentryCarried( false ); self SetCanDamage( true ); sentry_gun.ignoreMe = false; } sentry_placement_cancel_monitor( sentry_gun ) { self endon ( "sentry_placement_finished" ); if ( !isSP() ) self waittill_any( "sentry_placement_canceled", "death", "disconnect"); else self waittill_any( "sentry_placement_canceled" ); waittillframeend; sentry_gun delete(); } sentry_placement_endOfLevel_cancel_monitor( sentry_gun ) { self endon ( "sentry_placement_finished" ); if ( isSP() ) return; level waittill( "game_ended" ); if ( !isDefined( sentry_gun ) ) return; //sentry_gun notify( "deleted" ); if ( !self.canPlaceEntity ) { sentry_gun notify( "deleted" ); waittillframeend; sentry_gun delete(); return; } self thread place_sentry( sentry_gun ); } sentry_restock_wait() { level endon( "game_ended" ); self endon( "disconnect" ); self endon( "restock_reset" ); // Cancel/restock on death or when toggling the killstreak self notifyOnPlayerCommand( "cancel sentry", "+actionslot 4" ); self waittill_any( "death", "cancel sentry" ); assert( isdefined( self.last_sentry ) ); self notify( "sentry_placement_canceled" ); } sentry_placement_initial_wait( sentry_gun ) { level endon( "game_ended" ); self endon( "sentry_placement_canceled" ); if ( !isSP() ) { self endon( "disconnect" ); //self endon( "death" ); sentry_gun thread sentry_reset_on_owner_death(); self thread sentry_restock_wait(); } //debounce from picking up the gun while ( self useButtonPressed() ) wait 0.05; for ( ;; ) { // couldn't place entity so wait until the buttons are unpressed before trying again self waitActivateButton( false ); // wait until the button is pressed self waitActivateButton( true ); updateSentryPosition( sentry_gun ); if ( self.canPlaceEntity ) break; } if ( !isSP() ) //&& isAlive( self ) ) self notify( "restock_reset" ); if ( !isSP() ) { sentry_gun.lifeId = self.lifeId; self sentry_team_setup( sentry_gun ); } thread play_sound_in_space( "sentry_gun_plant", sentry_gun.origin ); assert( isdefined( self.team ) ); sentry_gun setmodel( level.sentry_settings[ sentry_gun.sentryType ].model ); sentry_gun thread sentry_init( self.team, sentry_gun.sentryType, self ); self notify( "sentry_placement_finished", sentry_gun ); waittillframeend; // wait so self.placingSentry can get cleared before notifying script that we can give the player another turret if ( !isSP() ) sentry_gun thread sentry_die_on_batteryout(); } updateSentryPositionThread( sentry_entity ) { level endon( "game_ended" ); sentry_entity notify( "sentry_placement_started" ); self endon( "sentry_placement_canceled" ); self endon( "sentry_placement_finished" ); sentry_entity endon( "death" ); sentry_entity endon( "deleted" ); if ( !isSP() ) { self endon( "disconnect" ); self endon( "death" ); } for ( ;; ) { updateSentryPosition( sentry_entity ); wait sentry_updateTime; } } updateSentryPosition( sentry_entity ) { placement = self canPlayerPlaceSentry(); sentry_entity.origin = placement[ "origin" ]; sentry_entity.angles = placement[ "angles" ]; self.canPlaceEntity = self isonground() && placement[ "result" ]; self sentry_placement_hint_show( self.canPlaceEntity ); if ( self.canPlaceEntity ) sentry_entity setModel( level.sentry_settings[ sentry_entity.sentryType ].placementmodel ); else sentry_entity setModel( level.sentry_settings[ sentry_entity.sentryType ].placementmodelfail ); } sentry_placement_hint_show( hint_valid ) { assert( isDefined( self ) ); assert( isDefined( hint_valid ) ); // return if not changed if ( isdefined( self.forced_hint ) && (self.forced_hint == hint_valid) ) return; self.forced_hint = hint_valid; if ( self.forced_hint ) self ForceUseHintOn( &"SENTRY_PLACE" ); else self ForceUseHintOn( &"SENTRY_CANNOT_PLACE" ); } sentry_placement_hint_hide() { assert( isDefined( self ) ); // return if hidden already if ( !isdefined( self.forced_hint ) ) return; self ForceUseHintOff(); self.forced_hint = undefined; } folded_sentry_use_wait( sentryType ) { // spawn another copy of the model so that it's not translucent self.obj_overlay = spawn( "script_model", self.origin ); self.obj_overlay.angles = self.angles; self.obj_overlay setModel( level.sentry_settings[ sentryType ].pickupModelObj ); for ( ;; ) { self waittill( "trigger", player ); if ( !isdefined( player ) ) continue; if ( isDefined( player.placingSentry ) ) continue; if ( !isSP() ) { assert( isdefined( self.owner ) ); if ( player != self.owner ) continue; } break; } self thread play_sound_in_space( "sentry_pickup" ); self.obj_overlay delete(); self delete(); // put the player into placement mode player thread spawn_and_place_sentry( sentryType ); } sentry_health_monitor() { self.healthbuffer = 20000; self.health += self.healthbuffer; self.currenthealth = self.health; attacker = undefined; type = undefined; while ( self.health > 0 ) { self waittill( "damage", amount, attacker, direction_vec, point, type, modelName, tagName ); if ( !isSP() && isdefined( attacker ) && isplayer( attacker ) && attacker sentry_attacker_is_friendly( self ) ) { self.health = self.currenthealth; return; } if ( isdefined( level.stat_track_damage_func ) && isdefined( attacker ) ) attacker [[ level.stat_track_damage_func ]](); assertex( isdefined( level.func[ "damagefeedback" ] ), "damagefeedback display function is undefined" ); if ( isdefined( attacker ) && isplayer( attacker ) ) { if ( !isSP() ) attacker [[ level.func[ "damagefeedback" ] ]]( "false" ); /* no more hit indicator in SP, commenting this out and replacing with the line above for MP only if ( isSP() ) attacker [[ level.func[ "damagefeedback" ] ]]( self ); else attacker [[ level.func[ "damagefeedback" ] ]]( "false" ); */ self thread sentry_allowFire( false, 2.0 ); } if ( self sentry_hit_bullet_armor( type ) ) { //damage was to bullet armor, restore health and decrement bullet armor. self.health = self.currenthealth; self.bullet_armor -= amount; } else self.currenthealth = self.health; if ( self.health < self.healthbuffer ) break; } if ( !isSP() && attacker sentry_attacker_can_get_xp( self ) ) attacker thread [[ level.onXPEvent ]]( "kill" ); self notify( "death", attacker, type ); } sentry_hit_bullet_armor( type ) { if ( self.bullet_armor <= 0 ) return false; if ( !( isdefined( type ) ) ) return false; if ( ! issubstr( type, "BULLET" ) ) return false; else return true; } enemy_sentry_difficulty_settings() { difficulty = "easy"; self SetConvergenceTime( level.sentryTurretSettings[ difficulty ][ "convergencePitchTime" ], "pitch" ); self SetConvergenceTime( level.sentryTurretSettings[ difficulty ][ "convergenceYawTime" ], "yaw" ); self SetSuppressionTime( level.sentryTurretSettings[ difficulty ][ "suppressionTime" ] ); self SetAiSpread( level.sentryTurretSettings[ difficulty ][ "aiSpread" ] ); self SetPlayerSpread( level.sentryTurretSettings[ difficulty ][ "playerSpread" ] ); self.maxrange = 1100; self.bullet_armor = minigun_sentry_bullet_armor_enemy; } waitActivateButton( bCheck ) { if ( !isSP() ) { self endon( "death" ); self endon( "disconnect" ); } assert( isdefined( bCheck ) ); if ( bCheck == true ) { while ( !self attackButtonPressed() && !self useButtonPressed() ) wait 0.05; } else if ( bCheck == false ) { while ( self attackButtonPressed() || self useButtonPressed() ) wait 0.05; } } makeSentrySolid() { self makeTurretSolid(); } makeSentryNotSolid() { self.contents = self setContents( 0 ); } SentryPowerOn() { self setMode( sentry_mode_name_on ); self.battery_usage = true; } SentryPowerOff() { self setMode( sentry_mode_name_off ); self.battery_usage = false; } // ============================================================================= // MP functions: // ============================================================================= // MP sentry team and head icons sentry_team_setup( sentry_gun ) { // self == player assert( isDefined( sentry_gun ) ); assert( isDefined( sentry_gun.sentryType ) ); if ( isdefined( self.pers[ "team" ] ) ) sentry_gun.pers[ "team" ] = self.pers[ "team" ]; sentry_gun sentry_team_show_icon(); } sentry_team_show_icon() { assert( isdefined( level.func[ "setTeamHeadIcon" ] ) ); sentry_headicon_offset = ( 0, 0, 65 ); if ( self.sentryType == "sentry_gun" ) sentry_headicon_offset = ( 0, 0, 75 ); self [[ level.func[ "setTeamHeadIcon" ] ]]( self.pers[ "team" ], sentry_headicon_offset ); } // MP clear team and head icons sentry_team_hide_icon() { assert( isdefined( level.func[ "setTeamHeadIcon" ] ) ); self [[ level.func[ "setTeamHeadIcon" ] ]]( "none", (0, 0, 0) ); } // resets sentry placement mode when owner carrying sentry dies sentry_place_mode_reset() { if ( !isDefined(self.owner) ) return; if ( isDefined( self.owner.placingSentry ) && (self.owner.placingSentry == self) ) { self.owner notify( "sentry_placement_canceled" ); self.owner _enableWeapon(); //self.owner _enableUsability(); self.owner.placingSentry = undefined; self setSentryCarried( false ); self SetCanDamage( true ); self.ignoreMe = false; } } sentry_set_owner( owner ) { assert( isdefined( owner ) ); assert( isPlayer( owner ) ); // don't need to set it twice. will happen for non-static sentries if ( isDefined ( self.owner ) && self.owner == owner ) return; owner.debug_sentry = self;// for debug self.owner = owner; self SetSentryOwner( owner ); self SetTurretMinimapVisible( true ); } sentry_destroy_on_owner_leave( owner ) { level endon( "game_ended" ); self endon( "death" ); owner waittill_any( "disconnect", "joined_team", "joined_spectators" ); self notify( "death" ); } // battery monitor, batter only used while sentry is on sentry_die_on_batteryout() { level endon( "game_ended" ); self endon( "death" ); self endon( "deleted" ); // only one instance self notify( "battery_count_started" ); self endon( "battery_count_started" ); while ( self.sentry_battery_timer >= 0 ) { if ( self.battery_usage ) self.sentry_battery_timer -= 1; wait 1; } self notify( "death" ); } removeDeadSentry() { self playsound( "sentry_explode" ); playfxOnTag( getfx( "sentry_turret_explode" ), self, "tag_aim" ); self delete_sentry_turret(); } sentry_reset_on_owner_death() { // self is sentry assert( isDefined( self ) ); self endon( "death" ); self endon( "deleted" ); assert( isdefined( self.owner ) ); self.owner waittill_any( "death", "disconnect" ); if ( isDefined( self.owner.placingSentry ) && (self.owner.placingSentry == self) ) { self.owner.placingSentry = undefined; self setSentryCarried( false ); self SetCanDamage( true ); self.ignoreMe = false; self notify( "death" ); } } sentry_attacker_can_get_xp( sentry ) { assert( isdefined( sentry.owner ) ); // defensive much? if ( !isdefined( self ) ) return false; if ( !isPlayer( self ) ) return false; if ( !isdefined( level.onXPEvent ) ) return false; if ( !isdefined( self.pers[ "team" ] ) ) return false; if ( !isdefined( sentry.team ) ) return false; if ( !level.teambased && self == sentry.owner ) return false; if ( level.teambased && ( self.pers[ "team" ] == sentry.team ) ) return false; return true; } sentry_attacker_is_friendly( sentry ) { assert( isdefined( sentry.owner ) ); // defensive much? if ( !isdefined( self ) ) return false; if ( !isPlayer( self ) ) return false; if ( !level.teamBased ) return false; if ( self == sentry.owner ) return false; if ( self.team != sentry.team ) return false; return true; } sentry_emp_damage_wait() { self endon( "deleted" ); self endon( "death" ); for ( ;; ) { self waittill( "emp_damage", attacker, duration ); // TODO: friendly fire check here self thread sentry_burst_fire_stop(); if ( isdefined( level.laserOff_func ) ) self call [[ level.laserOff_func ]](); self SentryPowerOff(); playfxOnTag( getfx( "sentry_turret_explode" ), self, "tag_aim" ); wait( duration ); self SentryPowerOn(); } } sentry_emp_wait() { self endon( "deleted" ); self endon( "death" ); for ( ;; ) { level waittill( "emp_update" ); // TODO: make this work in FFA if ( level.teamEMPed[self.team] ) { self thread sentry_burst_fire_stop(); if ( isdefined( level.laserOff_func ) ) self call [[ level.laserOff_func ]](); self SentryPowerOff(); playfxOnTag( getfx( "sentry_turret_explode" ), self, "tag_aim" ); } else { self SentryPowerOn(); } } } addToTurretList() { level.turrets[self getEntityNumber()] = self; } removeFromTurretList() { level.turrets[self getEntityNumber()] = undefined; } dual_waittill( ent1, msg1, ent2, msg2 ) { ent1 endon ( msg1 ); ent2 endon ( msg2 ); level waittill ( "hell_freezes_over_AND_THEN_thaws_out" ); }