#include maps\mp\_utility; #include maps\mp\gametypes\_hud_util; #include common_scripts\utility; #include maps\mp\gametypes\_hostmigration; #include maps\mp\agents\_agent_utility; #include maps\mp\bots\_bots_util; #include maps\mp\bots\_bots_strategy; CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_GAME = 5; CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_PLAYER = 3; CONST_AGENT_TYPE = "beastmen"; CONST_AGENT_HEALTH = 500; // -------------------------------------------------------------- // Crate and general killstreak setup // -------------------------------------------------------------- init() { // TODO: Setup any FX related to the agents } setupCallbacks() { level.agent_funcs[CONST_AGENT_TYPE] = level.agent_funcs["squadmate"]; level.agent_funcs[CONST_AGENT_TYPE]["spawn"] = ::spawn_agent_beast; level.agent_funcs[CONST_AGENT_TYPE]["think"] = ::squadmate_agent_think; level.agent_funcs[CONST_AGENT_TYPE]["on_killed"] = ::on_agent_squadmate_killed; } // create agent tryUseAgentKillstreak( lifeId, streakName ) { // self == player with killstreak setupCallbacks(); self.beastCount = 0; self thread delayedSpawnBeast( 5 ); return true; } spawnBeast() { // self == player with killstreak agent = createSquadmate(); if ( IsDefined( agent ) ) { self.beastCount++; if ( self.beastCount < CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_PLAYER ) { self thread delayedSpawnBeast( 0.5 ); } return true; } return false; } delayedSpawnBeast( delayTime ) { self endon ( "disconnect" ); level endon ( "game_ended" ); wait( delayTime ); self spawnBeast(); } createSquadmate( spawnOverride ) { // // limit the number of active "squadmate" agents allowed per game // if( getNumActiveAgents( "squadmate" ) >= CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_GAME ) // { // self iPrintLnBold( &"KILLSTREAKS_AGENT_MAX" ); // return undefined; // } // // // limit the number of active agents allowed per player // if( getNumOwnedActiveAgents( self ) >= CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_PLAYER ) // { // self iPrintLnBold( &"KILLSTREAKS_AGENT_MAX" ); // return undefined; // } // Find a spawn location from the provided structs spawnOrigin = findSpawnLocation(); // This is used when we are teleporting the beast man to another position if ( IsDefined( spawnOverride ) ) spawnOrigin = spawnOverride; spawnAngles = VectorToAngles( self.origin - spawnOrigin ); agent = maps\mp\agents\_agents::add_humanoid_agent( CONST_AGENT_TYPE, self.team, "reconAgent", spawnOrigin, spawnAngles, self, false, false, "veteran" ); if( !IsDefined( agent ) ) { self iPrintLnBold( &"KILLSTREAKS_AGENT_MAX" ); return false; } agent.killStreakType = "agent"; return agent; } spawn_agent_beast( optional_spawnOrigin, optional_spawnAngles, optional_owner, use_randomized_personality, respawn_on_death, difficulty ) { self endon("disconnect"); while( !IsDefined(level.getSpawnPoint) ) { waitframe(); } if( self.hasDied ) { wait( RandomIntRange(6, 10) ); } self initPlayerScriptVariables( true ); // allow killstreaks to pass in specific spawn locations if( IsDefined(optional_spawnOrigin) && IsDefined(optional_spawnAngles) ) { spawnOrigin = optional_spawnOrigin; spawnAngles = optional_spawnAngles; self.lastSpawnPoint = SpawnStruct(); self.lastSpawnPoint.origin = spawnOrigin; self.lastSpawnPoint.angles = spawnAngles; } else { spawnPoint = self [[level.getSpawnPoint]](); spawnOrigin = spawnpoint.origin; spawnAngles = spawnpoint.angles; // Player specific variables needed in damage processing self.lastSpawnPoint = spawnpoint; } self activateAgent(); self.lastSpawnTime = GetTime(); self.spawnTime = GetTime(); phys_trace_start = spawnOrigin + (0,0,25); phys_trace_end = spawnOrigin; newSpawnOrigin = PlayerPhysicsTrace(phys_trace_start, phys_trace_end); if ( DistanceSquared( newSpawnOrigin, phys_trace_start ) > 1 ) { // If the result from the physics trace wasn't immediately in solid, then use it instead spawnOrigin = newSpawnOrigin; } // called from code when an agent is done initializing after AddAgent is called // this should set up any state specific to this agent and game self SpawnAgent( spawnOrigin, spawnAngles ); self maps\mp\bots\_bots_util::bot_set_personality( "cqb" ); if ( IsDefined( difficulty ) ) self maps\mp\bots\_bots_util::bot_set_difficulty( difficulty ); // ? self maps\mp\agents\_agents::initPlayerClass(); self maps\mp\agents\_agent_common::set_agent_health( CONST_AGENT_HEALTH ); if ( IsDefined(respawn_on_death) && respawn_on_death ) self.respawn_on_death = true; // must set the team after SpawnAgent to fix a bug with weapon crosshairs and nametags if( IsDefined(optional_owner) ) self set_agent_team( optional_owner.team, optional_owner ); if( isDefined( self.owner ) ) self thread maps\mp\agents\_agents::destroyOnOwnerDisconnect( self.owner ); self thread maps\mp\_flashgrenades::monitorFlash(); // switch to agent bot mode and wipe all AI info clean self EnableAnimState( false ); self [[level.onSpawnPlayer]](); self maps\mp\gametypes\_class::giveLoadout( self.team, self.class, true ); self customizeSquadmate(); // use this to give loadout instead self thread maps\mp\bots\_bots::bot_think_watch_enemy( true ); // self thread maps\mp\bots\_bots::bot_think_crate(); //self thread maps\mp\bots\_bots::bot_think_level_actions(); self thread maps\mp\bots\_bots_strategy::bot_think_tactical_goals(); self thread [[ self agentFunc("think") ]](); if ( !self.hasDied ) self maps\mp\gametypes\_spawnlogic::addToParticipantsArray(); self.hasDied = false; self thread maps\mp\gametypes\_weapons::onPlayerSpawned(); self thread maps\mp\gametypes\_healthoverlay::playerHealthRegen(); level notify( "spawned_agent_player", self ); level notify( "spawned_agent", self ); self notify( "spawned_player" ); // Start surrounding FX self.environmentState = "outdoors"; self thread delaySoundFX( "zerosub_monster_breath_only_lp", 0.05 ); self thread delaySoundFX( "zerosub_monster_steps_only_ext_lp", 0.10 ); self thread delayPlayFXOnTag( level._effect[ "vfx_yeti_snowcover_upflip" ], "tag_origin", 0.05, 0.5 ); self playEyeFX(); self thread watchBeastMovement(); self thread watchKillstreakEnd(); } squadmate_agent_think() { self endon( "death" ); level endon( "game_ended" ); if ( IsDefined( self.owner ) ) { self endon( "owner_disconnect" ); } self BotSetFlag( "force_sprint", true ); } on_agent_squadmate_killed(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration) { self maps\mp\agents\_agents::on_humanoid_agent_killed_common(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration, false); body = self GetCorpseEntity(); // Play Despawn FX PlayFx( level._effect[ "vfx_yeti_snowcover_dissolve" ], self.origin ); self PlaySound( "mp_zerosub_monster_death" ); if ( sMeansOfDeath == "MOD_MELEE" ) // for some reason, I can't delete the body right away on a melee death { wait (0.75); } else { wait (0.5); } self maps\mp\agents\_agent_utility::deactivateAgent(); // award XP for killing agents if( IsPlayer( eAttacker ) && IsDefined(self.owner) && eAttacker != self.owner ) { self maps\mp\gametypes\_damage::onKillstreakKilled( eAttacker, sWeapon, sMeansOfDeath, iDamage, "destroyed_ks_beast_man" ); } body delete(); } customizeSquadmate() { // Set up visuals for squadmate self SetModel( "mp_fullbody_beast_man" ); if( IsDefined( self.headModel ) ) { self Detach( self.headModel, "" ); self.headModel = undefined; } // Play Spawn FX PlayFx( level._effect[ "vfx_yeti_snowcover_upflip" ], self.origin ); // Set up weapons mainWeapon = "iw6_knifeonlybeast_mp"; self TakeAllWeapons(); self GiveWeapon( mainWeapon ); self SwitchToWeapon( mainWeapon ); self BotSetFlag( "prefer_melee", true ); // Set perks for squadmates so they don't show names, same as the player self givePerk( "specialty_spygame", false ); self givePerk( "specialty_coldblooded", false ); self givePerk( "specialty_noscopeoutline", false); self givePerk( "specialty_heartbreaker", false ); self givePerk( "specialty_quieter", false ); // don't let ghosts rustle around // Regive Blind Eye after they are removed from the agent self thread watchRemovePerks(); self.health = CONST_AGENT_HEALTH; // Make sure when they take melee damage, that they only take 100 max self.customMeleeDamageTaken = 100; self SetSurfaceType( "snow" ); // !!! Hack-ish. see fx/maps/mp_battery3/iw_impacts.csv maps\mp\gametypes\_battlechatter_mp::disableBattleChatter( self ); } watchRemovePerks() { self endon( "death" ); level endon( "game_over" ); self waittill( "starting_perks_unset" ); self givePerk( "specialty_blindeye", false ); } delaySoundFX( sound, delayTime ) { self endon ( "death" ); level endon ( "game_ended" ); wait ( delayTime ); self PlayLoopSound( sound ); } delayPlayFXOnTag( FX, tag, delayTime, intervalTime ) { self endon ( "death" ); level endon ( "game_ended" ); wait ( delayTime ); while ( true ) { PlayFXOnTag( FX, self, tag ); if ( IsDefined( intervalTime ) ) wait( intervalTime ) ; else break; } } playEyeFX() { forwardVector = AnglesToForward( self.angles ) * 30; rightVector = AnglesToRight( self.angles ) * 7; // Setup the left eye of the beast vertOffset = ( 0, 0, 65 ); self thread createEyeFX( "left", self.origin + forwardVector + rightVector + vertOffset ); // Setup the right eye of the beast self thread createEyeFX( "right", self.origin + forwardVector - rightVector + vertOffset ); } createEyeFX( eye, fxPos ) { self endon( "death" ); level endon( "game_ended" ); if ( eye == "left" ) { self.leftEyeObj = Spawn( "script_model", fxPos ); self.leftEyeObj SetModel( "tag_origin" ); self.leftEyeObj LinkTo( self ); self.leftEyeObj delayPlayFXOnTag( level.zerosub_fx[ "beast" ][ "eyeglow" ], "tag_origin", 0.05, 0.5 ); } else { self.rightEyeObj = Spawn( "script_model", fxPos ); self.rightEyeObj SetModel( "tag_origin" ); self.rightEyeObj LinkTo( self ); self.rightEyeObj delayPlayFXOnTag( level.zerosub_fx[ "beast" ][ "eyeglow" ], "tag_origin", 0.05, 0.5 ); } } watchBeastMovement() { level endon ( "game_ended" ); level endon ( "frost_clear" ); while ( true ) { if ( !self maps\mp\mp_zerosub::isOutside() ) { if ( !level.beastAllowedIndoors ) { // Everytime to go inside, they will "die" and "respawn" in another location newSpawnLocation = findSpawnLocation( self.origin ); if ( IsDefined( newSpawnLocation ) ) { // Despawn self DoDamage( 10000, self.origin ); wait ( 1 ); // Respawn level.zerosub_killstreak_user createSquadmate( newSpawnLocation ); break; } } // Make sure we stop all sounds on the beast, and play their interior sounds if ( self.environmentState != "indoors" ) { self.environmentState = "indoors"; self StopSounds(); wait ( 0.3 ); self thread delaySoundFX( "zerosub_monster_breath_only_lp", 0.05 ); self thread delaySoundFX( "zerosub_monster_steps_only_int_lp", 0.10 ); } } else { // We only want to reset the beast sounds if they transition from indoors to outdoors if ( self.environmentState != "outdoors" ) { self.environmentState = "outdoors"; self StopSounds(); wait ( 0.3 ); self thread delaySoundFX( "zerosub_monster_breath_only_lp", 0.05 ); self thread delaySoundFX( "zerosub_monster_steps_only_ext_lp", 0.10 ); } } waitframe(); } } findSpawnLocation( oldPosition ) { // Look through the structs of beast men spawn locations ( zerosub_beast_spawn ) and find the furthest one from its current location spawnPoint = undefined; locations = getStructArray( "zerosub_beast_spawn", "targetname" ); if ( !IsDefined ( locations ) || locations.size == 0 ) { AssertMsg( "This should never happen. Locations should be defined for the beast men" ); return undefined; } // If we already have an old location, that means the beast man hit the indoor area, which will reset them back outside to another spawn if ( isDefined ( oldPosition ) ) { furthestDist = undefined; foreach ( loc in locations ) { newDist = Distance2DSquared( oldPosition, loc.origin ); if ( !IsDefined( furthestDist ) || furthestDist < newDist ) { furthestDist = newDist; spawnPoint = loc.origin; } } } else { // If this is the first time spawning in, then randomly choose one of the available locations randomIndex = RandomInt( locations.size ); spawnPoint = locations[ randomIndex ].origin; } return spawnPoint; } watchKillstreakEnd() { self endon ( "death" ); level endon ( "game_ended" ); level waittill ( "frost_clear" ); self DoDamage( 10000, self.origin ); self.leftEyeObj Delete(); self.rightEyeObj Delete(); }