#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\util_shared; #using scripts\shared\weapons\_weaponobjects; #precache( "fx", "weapon/fx_betty_exp_destroyed" ); #precache( "fx", "weapon/fx_betty_light_blue" ); #precache( "fx", "weapon/fx_betty_light_orng" ); #precache( "model", "wpn_t7_grenade_incendiary_world" ); #using_animtree ( "bouncing_betty" ); #namespace bouncingbetty; function init_shared() { level.bettyDestroyedFX = "weapon/fx_betty_exp_destroyed"; level._effect["fx_betty_friendly_light"] = "weapon/fx_betty_light_blue"; level._effect["fx_betty_enemy_light"] = "weapon/fx_betty_light_orng"; level.bettyMinDist = 20; level.bettyStunTime = 1; bettyExplodeAnim = %o_spider_mine_detonate; bettyDeployAnim = %o_spider_mine_deploy; level.bettyRadius = getDvarInt( "betty_detect_radius", 180 ); level.bettyActivationDelay = getDvarFloat( "betty_activation_delay", 1 ); level.bettyGracePeriod = getDvarFloat( "betty_grace_period", 0.0 ); level.bettyDamageRadius = getDvarInt( "betty_damage_radius", 180 ); level.bettyDamageMax = getDvarInt( "betty_damage_max", 180 ); level.bettyDamageMin = getDvarInt( "betty_damage_min", 70 ); level.bettyDamageHeight = getDvarInt( "betty_damage_cylinder_height", 200 ); level.bettyJumpHeight = getDvarInt( "betty_jump_height_onground", 55 ); level.bettyJumpHeightWall = getDvarInt( "betty_jump_height_wall", 20 ); level.bettyJumpHeightWallAngle = getDvarInt( "betty_onground_angle_threshold", 30 ); level.bettyJumpHeightWallAngleCos = cos( level.bettyJumpHeightWallAngle ); level.bettyJumpTime = getDvarFloat( "betty_jump_time", 0.7 ); level.bettyBombletSpawnDistance = 20; level.bettyBombletCount = 4; level thread register(); /# level thread bouncingBettyDvarUpdate(); #/ callback::add_weapon_watcher( &createBouncingBettyWatcher ); } function register() { clientfield::register( "missile", "bouncingbetty_state", 1, 2, "int" ); clientfield::register( "scriptmover", "bouncingbetty_state", 1, 2, "int" ); } /# function bouncingBettyDvarUpdate() { for(;;) { level.bettyRadius = getDvarInt( "betty_detect_radius", level.bettyRadius ); level.bettyActivationDelay = getDvarFloat( "betty_activation_delay", level.bettyActivationDelay ); level.bettyGracePeriod = getDvarFloat( "betty_grace_period", level.bettyGracePeriod ); level.bettyDamageRadius = getDvarInt( "betty_damage_radius", level.bettyDamageRadius ); level.bettyDamageMax = getDvarInt( "betty_damage_max", level.bettyDamageMax ); level.bettyDamageMin = getDvarInt( "betty_damage_min", level.bettyDamageMin ); level.bettyDamageHeight = getDvarInt( "betty_damage_cylinder_height", level.bettyDamageHeight ); level.bettyJumpHeight = getDvarInt( "betty_jump_height_onground", level.bettyJumpHeight ); level.bettyJumpHeightWall = getDvarInt( "betty_jump_height_wall", level.bettyJumpHeightWall ); level.bettyJumpHeightWallAngle = getDvarInt( "betty_onground_angle_threshold", level.bettyJumpHeightWallAngle ); level.bettyJumpHeightWallAngleCos = cos( level.bettyJumpHeightWallAngle ); level.bettyJumpTime = getDvarFloat( "betty_jump_time", level.bettyJumpTime ); wait( 3 ); } } #/ //****************************************************************** // * // * //****************************************************************** function createBouncingBettyWatcher() // self == player { watcher = self weaponobjects::createProximityWeaponObjectWatcher( "bouncingbetty", self.team ); watcher.onSpawn =&onSpawnBouncingBetty; watcher.watchForFire = true; watcher.onDetonateCallback =&bouncingBettyDetonate; //Eckert - playing sound later //watcher.activateSound = "fly_betty_exp"; watcher.activateSound = "wpn_betty_alert"; watcher.hackable = true; watcher.hackerToolRadius = level.equipmentHackerToolRadius; watcher.hackerToolTimeMs = level.equipmentHackerToolTimeMs; watcher.ownerGetsAssist = true; watcher.ignoreDirection = true; watcher.immediateDetonation = true; watcher.immunespecialty = "specialty_immunetriggerbetty"; watcher.detectionMinDist = level.bettyMinDist; watcher.detectionGracePeriod = level.bettyGracePeriod; watcher.detonateRadius = level.bettyRadius; watcher.onFizzleOut = &bouncingbetty::onBouncingBettyFizzleOut; watcher.stun =&weaponobjects::weaponStun; watcher.stunTime = level.bettyStunTime; watcher.activationDelay = level.bettyActivationDelay; } function onBouncingBettyFizzleOut() { if ( isdefined( self.mineMover ) ) { if ( isdefined( self.mineMover.killcament ) ) { self.mineMover.killcament delete(); } self.mineMover delete(); } self delete(); } //****************************************************************** // * // * //****************************************************************** function onSpawnBouncingBetty( watcher, owner ) // self == betty ent { weaponobjects::onSpawnProximityWeaponObject( watcher, owner ); self.originalOwner = owner; self thread spawnMineMover(); self trackOnOwner( owner ); self thread trackUsedStatOnDeath(); self thread doNoTrackUsedStatOnPickup(); self thread trackUsedOnHack(); } function trackUsedStatOnDeath() { self endon( "do_not_track_used" ); self waittill( "death" ); waittillframeend; // to compensate for timiing issues with notifies and death if ( isdefined( self.owner ) ) { self.owner trackBouncingBettyAsUsed(); // since betties can be picked up or hacked, we track them as used on death } self notify( "end_doNoTrackUsedOnPickup" ); self notify( "end_doNoTrackUsedOnHacked" ); } function doNoTrackUsedStatOnPickup() // self == betty ent { self endon( "end_doNoTrackUsedOnPickup" ); self waittill( "picked_up" ); self notify( "do_not_track_used" ); } function trackUsedOnHack() // self == betty ent { self endon( "end_doNoTrackUsedOnHacked" ); self waittill( "hacked" ); self.originalOwner trackBouncingBettyAsUsed(); // since betties can be picked up or hacked, we track them as used on hack too self notify( "do_not_track_used" ); // remove old one as it was hacked } function trackBouncingBettyAsUsed() { if ( IsPlayer( self ) ) { self AddWeaponStat( GetWeapon( "bouncingbetty" ), "used", 1 ); } } function trackOnOwner( owner ) { if ( level.trackBouncingBettiesOnOwner === true ) { if ( !isdefined( owner ) ) return; if ( !isdefined( owner.activeBouncingBetties ) ) { owner.activeBouncingBetties = []; } else { ArrayRemoveValue( owner.activeBouncingBetties, undefined ); } owner.activeBouncingBetties[ owner.activeBouncingBetties.size ] = self; } } //****************************************************************** // * // * //****************************************************************** function spawnMineMover() // self == betty ent { self endon( "death" ); self util::waitTillNotMoving(); self clientfield::set( "bouncingbetty_state", 2 ); self UseAnimTree( #animtree ); self SetAnim( %o_spider_mine_deploy, 1.0, 0.0, 1.0 ); mineMover = spawn( "script_model", self.origin ); mineMover.angles = self.angles; mineMover SetModel( "tag_origin" ); mineMover.owner = self.owner; mineUp = AnglesToUp( mineMover.angles ); z_offset = GetDvarFloat( "scr_bouncing_betty_killcam_offset", 18.0 ); mineMover EnableLinkTo(); mineMover LinkTo( self ); mineMover.killCamOffset = VectorScale( mineUp, z_offset ); mineMover.weapon = self.weapon; mineMover playsound ("wpn_betty_arm"); killcamEnt = spawn( "script_model", mineMover.origin + mineMover.killCamOffset ); killcamEnt.angles = ( 0,0,0 ); killcamEnt SetModel( "tag_origin" ); killcamEnt SetWeapon( self.weapon ); mineMover.killcamEnt = killcamEnt; self.mineMover = mineMover; self thread killMineMoverOnPickup(); } //****************************************************************** // * // * //****************************************************************** function killMineMoverOnPickup() // self == betty ent { self.mineMover endon( "death" ); self util::waittill_any( "picked_up", "hacked" ); self killMineMover(); } //****************************************************************** // * // * //****************************************************************** function killMineMover() // self == betty ent { if ( isdefined( self.mineMover )) { if ( isdefined( self.mineMover.killcamEnt ) ) { self.mineMover.killcamEnt delete(); } self.mineMover delete(); } } //****************************************************************** // * // * //****************************************************************** function bouncingBettyDetonate( attacker, weapon, target ) // self == betty { if ( IsDefined( weapon ) && weapon.isValid ) { self.destroyedBy = attacker; if ( isdefined( attacker ) ) { if ( self.owner util::IsEnemyPlayer( attacker ) ) { attacker challenges::destroyedExplosive( weapon ); scoreevents::processScoreEvent( "destroyed_bouncingbetty", attacker, self.owner, weapon ); } } self bouncingBettyDestroyed(); } else if ( isdefined( self.mineMover )) { self.mineMover.ignore_team_kills = true; self.mineMover SetModel( self.model ); self.mineMover thread bouncingBettyJumpAndExplode(); self delete(); } else { // tagTMR: special case where betty hasn't settled yet but something has triggered a detonate // i.e. the moving platforms doors on drone, etc self bouncingBettyDestroyed(); } } //****************************************************************** // * // * //****************************************************************** function bouncingBettyDestroyed( ) // self == betty { PlayFX( level.bettyDestroyedFX, self.origin ); PlaySoundAtPosition ( "dst_equipment_destroy", self.origin ); if ( isdefined( self.trigger ) ) { self.trigger delete(); } self killMineMover(); self RadiusDamage( self.origin, 128, 110, 10, self.owner, "MOD_EXPLOSIVE", self.weapon ); self delete(); } //****************************************************************** // * // * //****************************************************************** function bouncingBettyJumpAndExplode() // self == script mover spawned at weaponobject location { jumpDir = VectorNormalize( AnglesToUp( self.angles ) ); if ( jumpDir[2] > level.bettyJumpHeightWallAngleCos ) { jumpHeight = level.bettyJumpHeight; } else { jumpHeight = level.bettyJumpHeightWall; } explodePos = self.origin + jumpDir * jumpHeight; self.killCamEnt MoveTo( explodePos + self.killCamOffset, level.bettyJumpTime, 0, level.bettyJumpTime ); self clientfield::set( "bouncingbetty_state", 1 ); wait( level.bettyJumpTime ); self thread mineExplode( jumpDir, explodePos ); } function mineExplode( explosionDir, explodePos ) { if ( !isdefined( self ) || !isdefined( self.owner ) ) return; self playsound( "wpn_betty_explo" ); self clientfield::set( "sndRattle", 1 ); {wait(.05);}; // needed or the effect doesn't play if ( !isdefined( self ) || !isdefined(self.owner) ) return; self CylinderDamage( explosionDir * level.bettyDamageHeight, explodePos, level.bettyDamageRadius, level.bettyDamageRadius, level.bettyDamageMax, level.bettyDamageMin, self.owner, "MOD_EXPLOSIVE", self.weapon ); self ghost(); wait( 0.1 ); if ( !isdefined( self ) || !isdefined(self.owner) ) return; if ( isdefined( self.trigger ) ) self.trigger delete(); self.killCamEnt delete(); self delete(); }