#using scripts\codescripts\struct; #using scripts\shared\callbacks_shared; #using scripts\shared\challenges_shared; #using scripts\shared\clientfield_shared; #using scripts\shared\damagefeedback_shared; #using scripts\shared\scoreevents_shared; #using scripts\shared\system_shared; #using scripts\shared\util_shared; #using scripts\shared\weapons\_hacker_tool; #using scripts\shared\weapons\_weaponobjects; #precache( "fx", "_t6/misc/fx_equip_tac_insert_light_grn" ); #precache( "fx", "_t6/misc/fx_equip_tac_insert_light_red" ); #precache( "fx", "_t6/misc/fx_equip_tac_insert_exp" ); #namespace tacticalinsertion; function init_shared() { if( level.gametype == "infect" ) { level.weaponTacticalInsertion = GetWeapon( "trophy_system" ); } else { level.weaponTacticalInsertion = GetWeapon( "tactical_insertion" ); } level._effect["tacticalInsertionFizzle"] = "_t6/misc/fx_equip_tac_insert_exp"; clientfield::register( "scriptmover", "tacticalinsertion", 1, 1, "int" ); callback::on_spawned( &on_player_spawned ); } function on_player_spawned() { self thread begin_other_grenade_tracking(); } function isTacSpawnTouchingCrates( origin, angles ) { crate_ents = GetEntArray( "care_package", "script_noteworthy" ); mins = ( -17, -17, -40 ); maxs = ( 17, 17, 40 ); for ( i = 0 ; i < crate_ents.size ; i++ ) { if ( crate_ents[i] IsTouchingVolume( origin + (0,0,40), mins, maxs ) ) { return true; } } return false; } function overrideSpawn(isPredictedSpawn) { if ( !isdefined( self.tacticalInsertion ) ) return false; origin = self.tacticalInsertion.origin; angles = self.tacticalInsertion.angles; team = self.tacticalInsertion.team; if (!isPredictedSpawn) self.tacticalInsertion destroy_tactical_insertion(); if ( team != self.team ) return false; if ( isTacSpawnTouchingCrates( origin ) ) return false; if (!isPredictedSpawn) { self.tacticalInsertionTime = getTime(); self spawn( origin, angles, "tactical insertion" ); self SetSpawnClientFlag( "SCDFL_DISABLE_LOGGING" ); self AddWeaponStat( level.weaponTacticalInsertion, "used", 1 ); } return true; } function waitAndDelete( time ) { self endon( "death" ); // Wait a server frame to let missile fully enter the world {wait(.05);}; // Delete grenade entity self delete(); } function watch( player ) { // Remove existing tactical insertion, if it exists if ( isdefined( player.tacticalInsertion ) ) { player.tacticalInsertion destroy_tactical_insertion(); } // Spawn tactical insertion player thread spawnTacticalInsertion(); // Delete grenade entity self waitAndDelete( 0.05 ); } function watchUseTrigger( trigger, callback, playerSoundOnUse, npcSoundOnUse ) { self endon( "delete" ); while ( true ) { trigger waittill( "trigger", player ); if ( !isAlive( player ) ) continue; if ( !player isOnGround() ) continue; if ( isdefined( trigger.triggerTeam ) && ( player.team != trigger.triggerTeam ) ) continue; if ( isdefined( trigger.triggerTeamIgnore ) && ( player.team == trigger.triggerTeamIgnore ) ) continue; if ( isdefined( trigger.claimedBy ) && ( player != trigger.claimedBy ) ) continue; if ( player useButtonPressed() && !player.throwingGrenade && !player meleeButtonPressed() ) { if ( isdefined( playerSoundOnUse ) ) player playLocalSound( playerSoundOnUse ); if ( isdefined( npcSoundOnUse ) ) player playSound( npcSoundOnUse ); self thread [[callback]]( player ); } } } function watchDisconnect() { self.tacticalInsertion endon( "delete" ); self waittill( "disconnect" ); self.tacticalInsertion thread destroy_tactical_insertion(); } function destroy_tactical_insertion(attacker) { self.owner.tacticalInsertion = undefined; self notify( "delete" ); self.owner notify( "tactical_insertion_destroyed" ); self.friendlyTrigger delete(); self.enemyTrigger delete(); // "destroyed_explosive" notify, for challenges if ( isdefined( attacker ) && isdefined( attacker.pers["team"] ) && isdefined( self.owner ) && isdefined( self.owner.pers["team"] ) ) { if ( level.teambased ) { if ( attacker.pers["team"] != self.owner.pers["team"] ) { attacker notify("destroyed_explosive"); attacker challenges::destroyedEquipment(); attacker challenges::destroyedTacticalInsert(); scoreevents::processScoreEvent( "destroyed_tac_insert", attacker ); } } else { if ( attacker!= self.owner ) { attacker notify("destroyed_explosive"); attacker challenges::destroyedEquipment(); attacker challenges::destroyedTacticalInsert(); scoreevents::processScoreEvent( "destroyed_tac_insert", attacker ); } } } self delete(); } function fizzle( attacker ) { if ( isdefined( self.fizzle ) && self.fizzle ) return; self.fizzle = true; PlayFX( level._effect["tacticalInsertionFizzle"], self.origin ); self playsound ("dst_tac_insert_break"); //Notify player that their tact insert was destroyed if ( isdefined( attacker ) && attacker != self.owner ) { if( isdefined( level.globallogic_audio_dialog_on_player_override ) )//TODO T7 - remove once globallogic_audio is shared { self.owner [[level.globallogic_audio_dialog_on_player_override]]( "tact_destroyed", "item_destroyed" ); } } self destroy_tactical_insertion( attacker ); } function pickUp( attacker ) { player = self.owner; self destroy_tactical_insertion(); player GiveWeapon( level.weaponTacticalInsertion ); player setWeaponAmmoClip( level.weaponTacticalInsertion, 1 ); } function spawnTacticalInsertion() // self == player { self endon( "disconnect" ); trace = BulletTrace( self.origin, self.origin + (0,0,-30), false, self ); trace["position"] += ( 0, 0, 1 ); if( !self IsOnGround() && BulletTracePassed( self.origin, self.origin + (0,0,-30), false, self ) ) { self GiveWeapon( level.weaponTacticalInsertion ); self setWeaponAmmoClip( level.weaponTacticalInsertion, 1 ); return; } // Setup model self.tacticalInsertion = spawn( "script_model", trace["position"] ); self.tacticalInsertion setModel( "wpn_t7_trophy_system" ); self.tacticalInsertion.origin = self.origin + ( 0, 0, 1 ); self.tacticalInsertion.angles = self.angles; self.tacticalInsertion.team = self.team; self.tacticalInsertion setTeam( self.team ); self.tacticalInsertion.owner = self; self.tacticalInsertion setOwner( self ); self.tacticalInsertion setWeapon( level.weaponTacticalInsertion ); self.tacticalInsertion endon( "delete" ); self.tacticalInsertion hacker_tool::registerWithHackerTool( level.equipmentHackerToolRadius, level.equipmentHackerToolTimeMs ); // Make usable triggerHeight = 64; triggerRadius = 128; self.tacticalInsertion.friendlyTrigger = spawn( "trigger_radius_use", self.tacticalInsertion.origin + ( 0, 0, 3 ) ); self.tacticalInsertion.friendlyTrigger SetCursorHint( "HINT_NOICON", self.tacticalInsertion ); self.tacticalInsertion.friendlyTrigger SetHintString( &"MP_TACTICAL_INSERTION_PICKUP" ); if ( level.teamBased ) { self.tacticalInsertion.friendlyTrigger SetTeamForTrigger( self.team ); self.tacticalInsertion.friendlyTrigger.triggerTeam = self.team; } self ClientClaimTrigger( self.tacticalInsertion.friendlyTrigger ); self.tacticalInsertion.friendlyTrigger.claimedBy = self; self.tacticalInsertion.enemyTrigger = spawn( "trigger_radius_use", self.tacticalInsertion.origin + ( 0, 0, 3 ) ); self.tacticalInsertion.enemyTrigger SetCursorHint( "HINT_NOICON", self.tacticalInsertion ); self.tacticalInsertion.enemyTrigger SetHintString( &"MP_TACTICAL_INSERTION_DESTROY" ); self.tacticalInsertion.enemyTrigger SetInvisibleToPlayer( self ); if ( level.teamBased ) { self.tacticalInsertion.enemyTrigger SetExcludeTeamForTrigger( self.team ); self.tacticalInsertion.enemyTrigger.triggerTeamIgnore = self.team; } self.tacticalInsertion clientfield::set( "tacticalinsertion", 1 ); self thread watchDisconnect(); watcher = weaponobjects::getWeaponObjectWatcherByWeapon( level.weaponTacticalInsertion ); self.tacticalInsertion thread watchUseTrigger( self.tacticalInsertion.friendlyTrigger,&pickUp, watcher.pickUpSoundPlayer, watcher.pickUpSound ); self.tacticalInsertion thread watchUseTrigger( self.tacticalInsertion.enemyTrigger,&fizzle ); if ( isdefined( self.tacticalInsertionCount ) ) self.tacticalInsertionCount++; else self.tacticalInsertionCount = 1; self.tacticalInsertion SetCanDamage( true ); self.tacticalInsertion.health = 1; while ( true ) { self.tacticalInsertion waittill( "damage", damage, attacker, direction, point, type, tagName, modelName, partname, weapon, iDFlags ); if ( level.teamBased && ( !isdefined( attacker ) || !isPlayer( attacker ) || attacker.team == self.team ) && attacker != self ) continue; if( attacker != self ) { attacker challenges::destroyedEquipment( weapon ); attacker challenges::destroyedTacticalInsert(); scoreevents::processScoreEvent( "destroyed_tac_insert", attacker ); } // most equipment should be flash/concussion-able, so it'll disable for a short period of time // check to see if the equipment has been flashed/concussed and disable it (checking damage < 5 is a bad idea, so check the weapon name) // we're currently allowing the owner/teammate to flash their own // do damage feedback if ( watcher.stunTime > 0 && weapon.doStun ) { self thread weaponobjects::stunStart( watcher, watcher.stunTime ); } if ( weapon.doDamageFeedback ) { // if we're not on the same team then show damage feedback if ( level.teambased && self.tacticalInsertion.owner.team != attacker.team ) { if ( damagefeedback::doDamageFeedback( weapon, attacker ) ) attacker damagefeedback::update(); } // for ffa just make sure the owner isn't the same else if ( !level.teambased && self.tacticalInsertion.owner != attacker ) { if ( damagefeedback::doDamageFeedback( weapon, attacker ) ) attacker damagefeedback::update(); } } //Notify player that their tact insert was destroyed if ( isdefined( attacker ) && attacker != self ) { if( isdefined( level.globallogic_audio_dialog_on_player_override ) )//TODO T7 - remove once globallogic_audio is shared { self [[level.globallogic_audio_dialog_on_player_override]]( "tact_destroyed", "item_destroyed" ); } } self.tacticalInsertion thread fizzle(); } } function cancel_button_think() { if ( !isdefined( self.tacticalInsertion ) ) { return; } text = cancel_text_create(); self thread cancel_button_press(); event = self util::waittill_any_return( "tactical_insertion_destroyed", "disconnect", "end_killcam", "abort_killcam", "tactical_insertion_canceled", "spawned" ); if ( event == "tactical_insertion_canceled" ) { self.tacticalInsertion destroy_tactical_insertion(); } if ( isdefined( text ) ) { text Destroy(); } } function cancelTackInsertionButton() { if( level.console ) return self changeSeatButtonPressed(); else return self jumpButtonPressed(); } function cancel_button_press() { self endon( "disconnect" ); self endon( "end_killcam" ); self endon( "abort_killcam" ); while( true ) { wait( .05 ); if ( self cancelTackInsertionButton() ) break; } self notify( "tactical_insertion_canceled" ); } function cancel_text_create() { text = NewClientHudElem( self ); text.archived = false; text.y = -100; text.alignX = "center"; text.alignY = "middle"; text.horzAlign = "center"; text.vertAlign = "bottom"; text.sort = 10; // force to draw after the bars text.font = "small"; text.foreground = true; text.hideWhenInMenu = true; if ( self IsSplitscreen() ) { text.y = -80; text.fontscale = 1.2; } else { text.fontscale = 1.6; } text setText( &"PLATFORM_PRESS_TO_CANCEL_TACTICAL_INSERTION" ); text.alpha = 1; return text; } function getTacticalInsertions() { tac_inserts = []; foreach( player in level.players ) { if ( isdefined( player.tacticalInsertion )) { tac_inserts[ tac_inserts.size ] = player.tacticalInsertion; } } return tac_inserts; } function tacticalInsertionDestroyedByTrophySystem( attacker, trophySystem ) // self == tac insert { owner = self.owner; if ( isdefined( attacker )) { attacker challenges::destroyedEquipment( trophySystem.name ); attacker challenges::destroyedTacticalInsert(); } self thread fizzle(); if( isdefined( owner )) { owner endon( "death" ); owner endon( "disconnect" ); wait (.05); // prevents clash with the player killed/lead change leader dialog(s) if( isdefined( level.globallogic_audio_dialog_on_player_override ) )//TODO T7 - remove once globallogic_audio is shared { owner [[level.globallogic_audio_dialog_on_player_override]]( "tact_destroyed", "item_destroyed" ); } } } function begin_other_grenade_tracking() { self endon( "death" ); self endon( "disconnect" ); self notify( "insertionTrackingStart" ); self endon( "insertionTrackingStart" ); for (;;) { self waittill ( "grenade_fire", grenade, weapon, cookTime ); if ( grenade util::isHacked() ) { continue; } if ( weapon == level.weaponTacticalInsertion ) { if( level.gametype == "infect" && self.team == game["defenders"] ) return; grenade thread watch( self ); } } }