#using scripts\codescripts\struct; #using scripts\shared\callbacks_shared; #using scripts\shared\challenges_shared; #using scripts\shared\clientfield_shared; #using scripts\shared\scoreevents_shared; #using scripts\shared\system_shared; #using scripts\shared\weapons\_weaponobjects; #using scripts\shared\util_shared; #precache( "fx", "weapon/fx_prox_grenade_scan_blue" ); #precache( "fx", "weapon/fx_prox_grenade_wrn_grn" ); #precache( "fx", "weapon/fx_prox_grenade_scan_orng" ); #precache( "fx", "weapon/fx_prox_grenade_wrn_red" ); #precache( "fx", "weapon/fx_prox_grenade_impact_player_spwner" ); #precache( "fx", "weapon/fx_prox_grenade_elec_jump" ); #namespace proximity_grenade; function init_shared() { level._effect["prox_grenade_friendly_default"] = "weapon/fx_prox_grenade_scan_blue"; level._effect["prox_grenade_friendly_warning"] = "weapon/fx_prox_grenade_wrn_grn"; level._effect["prox_grenade_enemy_default"] = "weapon/fx_prox_grenade_scan_orng"; level._effect["prox_grenade_enemy_warning"] = "weapon/fx_prox_grenade_wrn_red"; level._effect["prox_grenade_player_shock"] = "weapon/fx_prox_grenade_impact_player_spwner"; level._effect["prox_grenade_chain_bolt"] = "weapon/fx_prox_grenade_elec_jump"; level.proximityGrenadeDetectionRadius = GetDvarInt( "scr_proximityGrenadeDetectionRadius", 180 ); level.proximityGrenadeDuration = GetDvarFloat( "scr_proximityGrenadeDuration", 1.2 ); level.proximityGrenadeGracePeriod = GetDvarFloat( "scr_proximityGrenadeGracePeriod", 0.05 ); level.proximityGrenadeDOTDamageAmount = GetDvarInt( "scr_proximityGrenadeDOTDamageAmount", 1 ); level.proximityGrenadeDOTDamageAmountHardcore = GetDvarInt( "scr_proximityGrenadeDOTDamageAmountHardcore", 1 ); level.proximityGrenadeDOTDamageTime = GetDvarFloat( "scr_proximityGrenadeDOTDamageTime", 0.2 ); level.proximityGrenadeDOTDamageInstances = GetDvarInt( "scr_proximityGrenadeDOTDamageInstances", 4 ); level.proximityGrenadeActivationTime = GetDvarFloat( "scr_proximityGrenadeActivationTime", .1 ); level.proximityChainDebug = GetDvarInt( "scr_proximityChainDebug", 0 ); level.proximityChainGracePeriod = GetDvarInt( "scr_proximityChainGracePeriod", 2500 ); level.proximityChainBoltSpeed = GetDvarFloat( "scr_proximityChainBoltSpeed", 400.0 ); level.proximityGrenadeProtectedTime = GetDvarFloat( "scr_proximityGrenadeProtectedTime", 0.45 ); level.poisonFXDuration = 6; level thread register(); callback::on_spawned( &on_player_spawned ); callback::add_weapon_damage( GetWeapon( "proximity_grenade" ), &on_damage ); /# level thread updateDvars(); #/ } //****************************************************************** // *s // * //****************************************************************** function register() { clientfield::register( "toplayer", "tazered", 1, 1, "int" ); } function updateDvars() { while(1) { level.proximityGrenadeDetectionRadius = GetDvarInt( "scr_proximityGrenadeDetectionRadius", level.proximityGrenadeDetectionRadius ); level.proximityGrenadeDuration = GetDvarFloat( "scr_proximityGrenadeDuration", 1.5 ); level.proximityGrenadeGracePeriod = GetDvarFloat( "scr_proximityGrenadeGracePeriod", level.proximityGrenadeGracePeriod ); level.proximityGrenadeDOTDamageAmount = GetDvarInt( "scr_proximityGrenadeDOTDamageAmount", level.proximityGrenadeDOTDamageAmount ); level.proximityGrenadeDOTDamageAmountHardcore = GetDvarInt( "scr_proximityGrenadeDOTDamageAmountHardcore", level.proximityGrenadeDOTDamageAmountHardcore ); level.proximityGrenadeDOTDamageTime = GetDvarFloat( "scr_proximityGrenadeDOTDamageTime", level.proximityGrenadeDOTDamageTime ); level.proximityGrenadeDOTDamageInstances = GetDvarInt( "scr_proximityGrenadeDOTDamageInstances", level.proximityGrenadeDOTDamageInstances ); level.proximityGrenadeActivationTime = GetDvarFloat( "scr_proximityGrenadeActivationTime", level.proximityGrenadeActivationTime ); level.proximityChainDebug = GetDvarInt( "scr_proximityChainDebug", level.proximityChainDebug ); level.proximityChainGracePeriod = GetDvarInt( "scr_proximityChainGracePeriod", level.proximityChainGracePeriod ); level.proximityChainBoltSpeed = GetDvarFloat( "scr_proximityChainBoltSpeed", level.proximityChainBoltSpeed ); level.proximityGrenadeProtectedTime = GetDvarFloat( "scr_proximityGrenadeProtectedTime", level.proximityGrenadeProtectedTime ); wait(1.0); } } function createProximityGrenadeWatcher() // self == player { watcher = self weaponobjects::createProximityWeaponObjectWatcher( "proximity_grenade", self.team ); watcher.watchForFire = true; watcher.hackable = true; watcher.hackerToolRadius = level.equipmentHackerToolRadius; watcher.hackerToolTimeMs = level.equipmentHackerToolTimeMs; watcher.headIcon = false; watcher.activateFx = true; watcher.ownerGetsAssist = true; watcher.ignoreDirection = true; watcher.immediateDetonation = true; watcher.detectionGracePeriod = level.proximityGrenadeGracePeriod; watcher.detonateRadius = level.proximityGrenadeDetectionRadius; watcher.onStun = &weaponobjects::weaponStun; watcher.stunTime = 1; watcher.onDetonateCallback =&proximityDetonate; watcher.activationDelay = level.proximityGrenadeActivationTime; watcher.activateSound = "wpn_claymore_alert"; watcher.immunespecialty = "specialty_immunetriggershock"; watcher.onSpawn =&onSpawnProximityGrenadeWeaponObject; } function createGadgetProximityGrenadeWatcher() // self == player { watcher = self weaponobjects::createProximityWeaponObjectWatcher( "gadget_sticky_proximity", self.team ); watcher.watchForFire = true; watcher.hackable = true; watcher.hackerToolRadius = level.equipmentHackerToolRadius; watcher.hackerToolTimeMs = level.equipmentHackerToolTimeMs; watcher.headIcon = false; watcher.activateFx = true; watcher.ownerGetsAssist = true; watcher.ignoreDirection = true; watcher.immediateDetonation = true; watcher.detectionGracePeriod = level.proximityGrenadeGracePeriod; watcher.detonateRadius = level.proximityGrenadeDetectionRadius; watcher.onStun = &weaponobjects::weaponStun; watcher.stunTime = 1; watcher.onDetonateCallback =&proximityDetonate; watcher.activationDelay = level.proximityGrenadeActivationTime; watcher.activateSound = "wpn_claymore_alert"; watcher.onSpawn =&onSpawnProximityGrenadeWeaponObject; } function onSpawnProximityGrenadeWeaponObject( watcher, owner ) // self == weapon object { self thread setupKillCamEnt(); owner AddWeaponStat( self.weapon, "used", 1 ); if ( IsDefined( self.weapon ) && self.weapon.proximityDetonation > 0 ) { watcher.detonateRadius = self.weapon.proximityDetonation; } weaponobjects::onSpawnProximityWeaponObject( watcher, owner ); self trackOnOwner( self.owner ); } function trackOnOwner( owner ) { if ( level.trackProximityGrenadesOnOwner === true ) { if ( !isdefined( owner ) ) return; if ( !isdefined( owner.activeProximityGrenades ) ) { owner.activeProximityGrenades = []; } else { ArrayRemoveValue( owner.activeProximityGrenades, undefined ); } owner.activeProximityGrenades[ owner.activeProximityGrenades.size ] = self; } } function setupKillCamEnt() // self == grenade { self endon( "death" ); self util::waitTillNotMoving(); self.killCamEnt = spawn( "script_model", self.origin + (0,0,8 ) ); self thread cleanupKillCamEntOnDeath(); } function cleanupKillCamEntOnDeath() // self == grenade { self waittill( "death" ); self.killCamEnt util::deleteAfterTime( 4 + level.proximityGrenadeDOTDamageTime * level.proximityGrenadeDOTDamageInstances ); } function proximityDetonate( attacker, weapon, target ) { if ( isdefined( weapon ) && weapon.isValid ) { if ( isdefined( attacker ) ) { if ( self.owner util::IsEnemyPlayer( attacker ) ) { attacker challenges::destroyedExplosive( weapon ); scoreevents::processScoreEvent( "destroyed_proxy", attacker, self.owner, weapon ); } } } weaponobjects::weaponDetonate( attacker, weapon ); } function proximityGrenadeDamagePlayer( eAttacker, eInflictor, killCamEnt, weapon, meansOfDeath, damage, proximityChain ) { // eAttacker is the owner of the charge // eInflictor is originally the charge, and when on a chain, it;s the player that passed on the chain to us // killCamEnt is the killCam placed at the charge self thread damagePlayerInRadius( eInflictor.origin, eAttacker, killCamEnt ); if ( weapon.chainEventRadius > 0 && !self hasPerk ("specialty_proximityprotection") ) { self thread proximityGrenadeChain( eAttacker, eInflictor, killCamEnt, weapon, meansOfDeath, damage, proximityChain, 0 ); } } function getProximityChain() { if ( !isdefined( level.proximityChains ) ) { level.proximityChains = []; } // we use level.proximityChains because we need them to survive players and the on_damage callback foreach( chain in level.proximityChains ) { if ( !chainIsActive( chain ) ) { return chain; } } chain = spawnstruct(); level.proximityChains[level.proximityChains.size] = chain; return chain; } function chainIsActive( chain ) { // a chain is active as long as it is still being used if ( isdefined( chain.activeEndTime ) && chain.activeEndTime > GetTime() ) { return true; } return false; } function cleanUpProximityChainEnt() // self is a temp entity to keep track of the original chain. it goes away when all chains are finished. { self.cleanUp = true; any_active = true; while( any_active ) { wait( 1 ); if ( !isdefined( self ) ) { return; } any_active = false; foreach( proximityChain in self.chains ) { if ( proximityChain.activeEndTime > GetTime() ) { any_active = true; break; } } } if ( isdefined( self ) ) { self delete(); } } function isInChain( player ) { player_num = player GetEntityNumber(); return isdefined( self.chain_players[player_num] ); } function addPlayerToChain( player ) { player_num = player GetEntityNumber(); self.chain_players[player_num] = player; } function proximityGrenadeChain( eAttacker, eInflictor, killCamEnt, weapon, meansOfDeath, damage, proximityChain, delay ) { self endon( "disconnect" ); self endon( "death" ); eAttacker endon( "disconnect" ); if ( !isdefined( proximityChain ) ) { // this is a new chain started at the on_damage callback proximityChain = getProximityChain(); proximityChain.chainEventNum = 0; if ( !isdefined( eInflictor.proximityChainEnt ) ) { // this is the first chain from proximity grenade explosion started at the on_damage callback eInflictor.proximityChainEnt = spawn( "script_origin", self.origin ); eInflictor.proximityChainEnt.chains = []; eInflictor.proximityChainEnt.chain_players = []; } proximityChain.proximityChainEnt = eInflictor.proximityChainEnt; proximityChain.proximityChainEnt.chains[proximityChain.proximityChainEnt.chains.size] = proximityChain; } proximityChain.chainEventNum += 1; if ( proximityChain.chainEventNum >= weapon.chainEventMax ) { // this chain is maxed return; } chainEventRadiusSq = weapon.chainEventRadius * weapon.chainEventRadius; endTime = GetTime() + weapon.chainEventTime; proximityChain.proximityChainEnt addPlayerToChain( self ); proximityChain.activeEndTime = endTime + (delay * 1000) + level.proximityChainGracePeriod; // allow an interval to avoid reusing this proximityChain, in case it's still being used. if ( delay > 0 ) { // yield after incrementing chainEventNum, and updating the chain active state wait( delay ); } if( !isdefined( proximityChain.proximityChainEnt.cleanUp ) ) { proximityChain.proximityChainEnt thread cleanUpProximityChainEnt(); } // we just been chain shocked, we will look for other players to continue the chain while( 1 ) { currentTime = GetTime(); if ( endTime < currentTime ) { return; } closestPlayers = ArraySort( level.players, self.origin, true ); foreach( player in closestPlayers ) { {wait(.05);}; if ( proximityChain.chainEventNum >= weapon.chainEventMax ) { // this chain is maxed return; } if ( !isdefined( player ) || !IsAlive( player ) || player == self ) { continue; } if ( player.sessionstate != "playing" ) { continue; } distanceSq = DistanceSquared( player.origin, self.origin ); if ( distanceSq > chainEventRadiusSq ) { break; } if ( proximityChain.proximityChainEnt isInChain( player ) ) { continue; } if ( level.proximityChainDebug || weaponobjects::friendlyFireCheck( eAttacker, player ) ) { if ( level.proximityChainDebug || !player hasPerk ("specialty_proximityprotection") ) { // found a player to pass the chain to self thread chainPlayer( eAttacker, killCamEnt, weapon, meansOfDeath, damage, proximityChain, player, distanceSq ); } } } {wait(.05);}; } } function chainPlayer( eAttacker, killCamEnt, weapon, meansOfDeath, damage, proximityChain, player, distanceSq ) { waitTime = 0.25; speedSq = level.proximityChainBoltSpeed * level.proximityChainBoltSpeed; if ( speedSq > 100 && distanceSq > 1 ) { waitTime = distanceSq / speedSq; } player thread proximityGrenadeChain( eAttacker, self, killCamEnt, weapon, meansOfDeath, damage, proximityChain, waitTime ); {wait(.05);}; if ( level.proximityChainDebug ) { /# color = (1, 1, 1); alpha = 1; depth = 0; time = 200; util::debug_line(self.origin + (0,0,50), player.origin + (0,0,50), color, alpha, depth, time ); #/ } self tesla_play_arc_fx( player, waitTime ); player thread damagePlayerInRadius( self.origin, eAttacker, killCamEnt ); } function tesla_play_arc_fx( target, waitTime ) { if ( !IsDefined( self ) || !IsDefined( target ) ) { return; } tag = "J_SpineUpper"; target_tag = "J_SpineUpper"; origin = self GetTagOrigin( tag ); target_origin = target GetTagOrigin( target_tag ); distance_squared = 128 * 128; if ( DistanceSquared( origin, target_origin ) < distance_squared ) { //( "TESLA: Not playing arcing FX. Enemies too close." ); return; } fxOrg = spawn( "script_model", origin ); fxOrg SetModel( "tag_origin" ); fx = PlayFxOnTag( level._effect["prox_grenade_chain_bolt"], fxOrg, "tag_origin" ); playsoundatposition( "wpn_tesla_bounce", fxOrg.origin ); fxOrg MoveTo( target_origin, waitTime ); fxOrg waittill( "movedone" ); fxOrg delete(); } /# function debugChainSphere() { util::debug_sphere( self.origin + (0,0,50), 20, (1,1,1), 1, 0 ); } #/ function watchProximityGrenadeHitPlayer( owner ) // self = grenade { self endon( "death" ); self SetOwner( owner ); self SetTeam( owner.team ); while( 1 ) { self waittill("grenade_bounce", pos, normal, ent, surface); if ( isdefined(ent) && isplayer( ent ) && surface != "riotshield" ) { if ( ( level.teambased && ent.team == self.owner.team )) { continue; } self proximityDetonate(self.owner, self.weapon ); return; } } } function performHudEffects( position, distanceToGrenade ) { forwardVec = VectorNormalize( AnglesToForward( self.angles ) ); rightVec = VectorNormalize( AnglesToRight( self.angles ) ); explosionVec = VectorNormalize( position - self.origin ); fDot = VectorDot( explosionVec, forwardVec ); rDot = VectorDot( explosionVec, rightVec ); fAngle = ACos( fDot ); rAngle = ACos( rDot ); } function damagePlayerInRadius( position, eAttacker, killCamEnt ) // self = player in radius { self notify( "proximityGrenadeDamageStart" ); self endon( "proximityGrenadeDamageStart" ); self endon( "disconnect" ); self endon( "death" ); eAttacker endon( "disconnect" ); PlayFxOnTag( level._effect["prox_grenade_player_shock"], self, "J_SpineUpper" ); g_time = GetTime(); if ( self util::mayApplyScreenEffect() ) { if ( !self hasPerk ("specialty_proximityprotection") ) { self.lastShockedBy = eAttacker; self.shockEndTime = getTime() + ( level.proximityGrenadeDuration * 1000 ); self shellshock("proximity_grenade", level.proximityGrenadeDuration, false ); } self clientfield::set_to_player( "tazered", 1 ); } self playrumbleonentity("proximity_grenade"); self PlaySound( "wpn_taser_mine_zap" ); if ( !self hasPerk ("specialty_proximityprotection") ) { self thread watch_death(); if ( !isdefined( killCamEnt ) ) { killCamEnt = spawn( "script_model", position + (0,0,8) ); } killCamEnt.soundMod = "taser_spike"; killCamEnt util::deleteAfterTime( 3 + level.proximityGrenadeDOTDamageTime * level.proximityGrenadeDOTDamageInstances); self util::show_hud( 0 ); damage = level.proximityGrenadeDOTDamageAmount; if( level.hardcoreMode ) { damage = level.proximityGrenadeDOTDamageAmountHardcore; } for ( i = 0; i < level.proximityGrenadeDOTDamageInstances; i++ ) { assert( isdefined( eAttacker ) ); if ( !isdefined( killCamEnt ) ) { killCamEnt = spawn( "script_model", position + (0,0,8) ); killCamEnt.soundMod = "taser_spike"; killCamEnt util::deleteAfterTime( 3 + level.proximityGrenadeDOTDamageTime * ( level.proximityGrenadeDOTDamageInstances - i ) ); } self DoDamage( damage, position, eAttacker, killCamEnt, "none", "MOD_GAS", 0, GetWeapon( "proximity_grenade_aoe" ) ); wait( level.proximityGrenadeDOTDamageTime ); } if (GetTime() - g_time < (level.proximityGrenadeDuration*1000) ) { wait ( ( GetTime() - g_time) / 1000); } //self shellshock("proximity_grenade_exit", 0.6, false ); self util::show_hud( 1 ); } else { wait( level.proximityGrenadeProtectedTime ); } self clientfield::set_to_player( "tazered", 0 ); } function proximityDeathWait( owner ) { self waittill( "death" ); self notify( "deleteSound" ); } function deleteEntOnOwnerDeath( owner ) { self thread deleteEntOnTimeout(); self thread deleteEntAfterTime(); self endon( "delete" ); owner waittill( "death" ); self notify( "deleteSound" ); } function deleteEntAfterTime() { self endon( "delete" ); wait( 10.0 ); self notify( "deleteSound" ); } function deleteEntOnTimeout() { self endon( "delete" ); self waittill( "deleteSound" ); self delete(); } function watch_death() // self == player { self endon( "disconnect" ); self notify( "proximity_cleanup" ); self endon( "proximity_cleanup" ); // fail safe stuff for if the player dies self waittill("death"); self StopRumble( "proximity_grenade" ); self setblur(0,0); self util::show_hud( 1 ); self clientfield::set_to_player( "tazered", 0 ); //self setEMPJammed( false ); } function on_player_spawned() { self thread createProximityGrenadeWatcher(); self thread createGadgetProximityGrenadeWatcher(); self thread begin_other_grenade_tracking(); } function begin_other_grenade_tracking() { self endon( "death" ); self endon( "disconnect" ); self notify( "proximityTrackingStart" ); self endon( "proximityTrackingStart" ); for (;;) { self waittill ( "grenade_fire", grenade, weapon, cookTime ); if ( grenade util::isHacked() ) { continue; } if ( weapon.rootWeapon.name == "proximity_grenade" ) { grenade thread watchProximityGrenadeHitPlayer( self ); } } } function on_damage( eAttacker, eInflictor, weapon, meansOfDeath, damage ) { self thread proximityGrenadeDamagePlayer( eAttacker, eInflictor, eInflictor.killCamEnt, weapon, meansOfDeath, damage, undefined ); }