#include maps\mp\_utility; #include common_scripts\utility; /* Portable AOE generator * 2013-03-25 wsh * Class of equipment that can be placed and moved in the world * that affects all targeted players in an area around the deployed equipment * examples: scrambler, portable radar * Adapted from old scrambler code. */ init() { if ( !IsDefined( level.portableAOEgeneratorSettings ) ) { level.portableAOEgeneratorSettings = []; level.generators = []; } // in your specific implementation, need to set these config vars /* config = SpawnStruct(); config.generatorType = GENERATOR_TYPE; config.weaponName = "scrambler_mp"; config.targetType = "enemy"; // valid types { "friendly", "enemy", "all" } // callbacks are in the format: generator callbackFunc( player, generatorType ) config.onDeployCallback = ::onCreateScrambler; config.onDestroyCallback = ::onDestroyScrambler; // callbacks are in the format: player callbackFunc() config.onEnterCallback = ::onEnterScrambler; config.onExitCallback = ::onExitScrambler; // config.timeLimit = undefined; config.health = 100; config.placementZTolerance = 40; config.placedModel = "weapon_jammer"; config.bombSquadModel = "weapon_jammer_bombsquad"; config.damageFeedback = "scrambler"; config.useHintString = &"MP_PATCH_PICKUP_SCRAMBLER"; config.headIconHeight = 20; config.useSound = "scavenger_pack_pickup"; config.aoeRadius = 512; */ } // make sure that killstreaks don't get ammo refills setWeapon( generatorType ) // self == player { config = level.portableAOEgeneratorSettings[ generatorType ]; self SetOffhandSecondaryClass( "flash" ); self _giveWeapon( config.weaponName, 0 ); self giveStartAmmo( config.weaponName ); if ( !IsDefined( self.deployedGenerators ) ) { self.deployedGenerators = []; } self thread monitorGeneratorUse( generatorType ); } unsetWeapon( generatorType ) // self == player { self notify( "end_monitorUse_" + generatorType ); } deleteGenerator( generator, generatorType ) // self == player { if ( !IsDefined( generator ) ) { return; } foreach ( player in level.players ) { if ( IsDefined( player ) && IsDefined( player.inGeneratorAOE ) ) { // need to check if this array is defined // or create it // !!! player.inGeneratorAOE[ generatorType ] = undefined; } } // remove all references to this generator self registerGenerator( generator, generatorType, undefined ); generator notify( "death" ); generator Delete(); } // bRegister = true to register // = undefined to unregister registerGenerator( generator, generatorType, bRegister ) // self == player { if ( IsDefined( bRegister ) && bRegister ) { self.deployedGenerators[ generatorType ] = generator; } else { self.deployedGenerators[ generatorType ] = undefined; bRegister = undefined; // just to make sure people aren't passing in false } allGeneratorsOfThisType = level.generators[ generatorType ]; if ( !IsDefined( allGeneratorsOfThisType ) ) { level.generators[ generatorType ] = []; allGeneratorsOfThisType = level.generators[ generatorType ]; } id = getID( generator ); allGeneratorsOfThisType[ id ] = bRegister; } monitorGeneratorUse( generatorType ) // self == player { self notify( "end_monitorUse_" + generatorType ); self endon( "end_monitorUse_" + generatorType ); self endon( "disconnect" ); level endon( "game_ended" ); config = level.portableAOEgeneratorSettings[ generatorType ]; while ( true ) { self waittill( "grenade_fire", grenade, weapName ); // grenade is the entity spawned by the G_FireGrenade() since we want this to be // script controlled, we won't actually use this entity if ( weapName == config.weaponName || weapName == generatorType ) { if ( !IsAlive( self ) ) { grenade delete(); return; } if ( checkGeneratorPlacement( grenade, config.placementZTolerance ) ) { previousGenerator = self.deployedGenerators[ generatorType ]; if ( IsDefined( previousGenerator ) ) { deleteGenerator( previousGenerator, generatorType ); } generator = self spawnNewGenerator( generatorType, grenade.origin ); // For moving platforms. parent = grenade GetLinkedParent(); if ( IsDefined( parent ) ) { generator LinkTo( parent ); } if( IsDefined( grenade ) ) { grenade Delete(); } } else { self SetWeaponAmmoStock( config.weaponName, self GetWeaponAmmoStock( "trophy_mp" ) + 1 ); } } } } checkGeneratorPlacement( grenade, maxZDistance ) // self == player { // need to see if this is being placed far away from the player and not let it do that // this will fix a legacy bug where you can stand on a ledge and plant a claymore down on the ground far below you grenade Hide(); grenade waittill( "missile_stuck", stuckTo ); if( maxZDistance * maxZDistance < DistanceSquared( grenade.origin, self.origin ) ) { secTrace = bulletTrace( self.origin, self.origin - (0, 0, maxZDistance), false, self ); if( secTrace["fraction"] == 1 ) { // there's nothing under us so don't place the grenade up in the air grenade delete(); return false; } // move the grenade to a reasonable place grenade.origin = secTrace["position"]; } grenade Show(); return true; } spawnNewGenerator( generatorType, origin ) // self == player { config = level.portableAOEgeneratorSettings[ generatorType ]; generator = spawn( "script_model", origin ); generator.health = config.health; generator.team = self.team; generator.owner = self; generator SetCanDamage( true ); generator SetModel( config.placedModel ); // setup icons for item so friendlies see it if ( level.teamBased ) generator maps\mp\_entityheadIcons::setTeamHeadIcon( self.team , (0,0,config.headIconHeight) ); else generator maps\mp\_entityheadicons::setPlayerHeadIcon( self, (0,0,config.headIconHeight) ); generator thread watchOwner( self, generatorType ); generator thread watchDamage( self, generatorType ); generator thread watchUse( self, generatorType ); // 2013-03-25 wsh: // not sure why we need to set notUsableForJoiningPlayers generator thread notUsableForJoiningPlayers( self ); if ( IsDefined( config.onDeployCallback ) ) { generator [[ config.onDeployCallback ]]( self, generatorType ); } generator thread maps\mp\gametypes\_weapons::createBombSquadModel( config.bombSquadModel, "tag_origin", self ); self registerGenerator( generator, generatorType, true ); // need to clear the changing weapon because it'll get stuck on c4_mp and player will stop spawning because we get locked in isChangingWeapon() loop when a killstreak is earned self.changingWeapon = undefined; wait(0.05); // allows for the plant sound to play if( IsDefined(generator) && (generator touchingBadTrigger()) ) { generator notify( "death" ); } return generator; } watchOwner( owner, generatorType ) // self == generator { self endon( "death" ); level endon ( "game_ended" ); if ( bot_is_fireteam_mode() ) { owner waittill( "killstreak_disowned" ); } else { owner waittill_either( "killstreak_disowned", "death" ); } owner thread deleteGenerator( self, generatorType ); } watchDamage( owner, generatorType ) // self == generator { self.generatorType = generatorType; config = level.portableAOEgeneratorSettings[ generatorType ]; self maps\mp\gametypes\_damage::monitorDamage( config.health, config.damageFeedback, ::handleDeathDamage, ::modifyDamage, false // isKillstreak ); } // we should just use the default one modifyDamage( attacker, weapon, type, damage ) { modifiedDamage = damage; modifiedDamage = self maps\mp\gametypes\_damage::handleMeleeDamage( weapon, type, modifiedDamage ); modifiedDamage = self maps\mp\gametypes\_damage::handleEmpDamage( weapon, type, modifiedDamage ); modifiedDamage = self maps\mp\gametypes\_damage::handleMissileDamage( weapon, type, modifiedDamage ); modifiedDamage = self maps\mp\gametypes\_damage::handleGrenadeDamage( weapon, type, modifiedDamage ); modifiedDamage = self maps\mp\gametypes\_damage::handleAPDamage( weapon, type, modifiedDamage, attacker ); return modifiedDamage; } handleDeathDamage( attacker, weapon, type ) { owner = self.owner; config = level.portableAOEgeneratorSettings[ self.generatorType ]; if ( isDefined( owner ) && attacker != owner ) { attacker notify( "destroyed_equipment" ); // count towards SitRep Pro challenge } if ( IsDefined( config.onDestroyCallback ) ) { owner [[ config.onDestroyCallback ]]( self, self.generatorType ); } owner thread deleteGenerator( self, self.generatorType ); } watchUse( owner, generatorType ) // self == generator { self endon ( "death" ); level endon ( "game_ended" ); owner endon ( "disconnect" ); config = level.portableAOEgeneratorSettings[ generatorType ]; self setCursorHint( "HINT_NOICON" ); self setHintString( config.useHintString ); self setSelfUsable( owner ); while ( true ) { self waittill ( "trigger", player ); player playLocalSound( config.useSound ); // give item to user (only if they haven't restocked from scavenger pickup since dropping) if ( player getAmmoCount( config.weaponName ) == 0 && !player isJuggernaut() ) { player setWeapon( generatorType ); } player thread deleteGenerator( self, generatorType ); } } // called from _perks.gsc::spawnPlayer // because we want to accumlate the effects of all generators // before we determine whether or not to enable the effect // DOES NOT COUNT THE # OF EFFECTS ACTIVE, though it's easily changed generatorAOETracker() // self == player { self endon( "death" ); self endon( "disconnect" ); self endon( "faux_spawn" ); level endon( "game_ended" ); // wait a random amount so that all players aren't checking at the same time delay = RandomFloat( 0.5 ); wait( delay ); self.inGeneratorAOE = []; while ( true ) { wait ( 0.05 ); if ( level.generators.size > 0 || self.inGeneratorAOE.size > 0 ) { foreach ( config in level.portableAOEgeneratorSettings ) { self checkAllGeneratorsOfThisType( config.generatorType ); } } } } checkAllGeneratorsOfThisType( generatorType ) // self == player { generators = level.generators[ generatorType ]; if ( IsDefined( generators ) ) { config = level.portableAOEgeneratorSettings[ generatorType ]; maxDistSq = config.aoeRadius * config.aoeRadius; result = undefined; foreach ( generator in generators ) { // should I be cleaning the array? if ( IsDefined( generator ) && isReallyAlive( generator ) ) { if ( ( level.teamBased && matchesTargetTeam( generator.team, self.team, config.targetType ) ) || ( !level.teamBased && matchesOwner( generator.owner, self, config.targetType ) ) ) { // stop as soon as we find a valid candidate distSq = DistanceSquared( generator.origin, self.origin ); if ( distSq < maxDistSq ) { result = generator; break; } } } } isInThisGenerator = IsDefined(result); wasInGeneratorOfThisType = IsDefined( self.inGeneratorAOE[ generatorType ] ); if ( isInThisGenerator && !wasInGeneratorOfThisType ) { self [[ config.onEnterCallback ]](); } else if ( !isInThisGenerator && wasInGeneratorOfThisType ) { self [[ config.onExitCallback ]](); } self.inGeneratorAOE[ generatorType ] = result; } } matchesTargetTeam( myTeam, theirTeam, teamType ) { return ( ( teamType == "all" ) || ( teamType == "friendly" && myTeam == theirTeam ) || ( teamType == "enemy" && myTeam != theirTeam ) ); } matchesOwner( myOwner, player, teamType ) { return ( ( teamType == "all" ) || ( teamType == "friendly" && myOwner == player ) || ( teamType == "enemy" && myOwner != player ) ); } // since I can't use the generator itself as an index into the array // create some kind of unique ID to use as an index getID( generator ) { return generator.owner.guid + generator.birthtime; }