iw6-scripts-dev/maps/mp/mp_pirate_ghost.gsc
2024-12-11 11:28:08 +01:00

594 lines
16 KiB
Plaintext

#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_KILLSTREAK_NAME = "pirate_ghostcrew";
CONST_DEBUG_NAME = "Pirate Ghosts";
CONST_CRATE_WEIGHT = 85;
CONST_AGENT_TYPE = "pirate";
CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_GAME = 5;
CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_PLAYER = 2;
CONST_GHOST_HAT_MODEL = "pirate_hat_iw6_ghost";
CONST_GHOST_HEALTH = 300;
CONST_MAX_GHOSTS = 10;
CONST_GHOST_RESPAWN_TIMER = 1; // in seconds
// add to csv
// this script
// vehicle
// vehicle model
// ghost effect
// add killstreak and crate functions
// --------------------------------------------------------------
// Crate and general killstreak setup
// --------------------------------------------------------------
init()
{
level._effect[ "ghost_spawn" ] = LoadFX( "vfx/moments/mp_pirate/vfx_pirate_ghost_vapor" );
level._effect[ "ghost_trail" ] = LoadFX( "vfx/moments/mp_pirate/vfx_pirate_ghost_vapor_trail" );
level._effect[ "ghost_blink" ] = LoadFX( "vfx/moments/mp_pirate/vfx_ghost_power_drain" );
}
setupCallbacks()
{
level.agent_funcs[CONST_AGENT_TYPE] = level.agent_funcs["squadmate"];
level.agent_funcs[CONST_AGENT_TYPE]["spawn"] = ::spawn_agent_ghost;
level.agent_funcs[CONST_AGENT_TYPE]["think"] = ::squadmate_agent_think;
level.agent_funcs[CONST_AGENT_TYPE]["on_killed"] = ::on_agent_squadmate_killed;
}
customCrateFunc()
{
if(!IsDefined(game["player_holding_level_killstrek"]))
game["player_holding_level_killstrek"] = false;
if(!allowLevelKillstreaks() || game["player_holding_level_killstrek"])
return;
maps\mp\killstreaks\_airdrop::addCrateType( "airdrop_assault",
CONST_KILLSTREAK_NAME,
CONST_CRATE_WEIGHT,
maps\mp\killstreaks\_airdrop::killstreakCrateThink,
maps\mp\killstreaks\_airdrop::get_friendly_crate_model(),
maps\mp\killstreaks\_airdrop::get_enemy_crate_model(),
&"MP_PIRATE_GHOSTCREW_USE"
);
level thread watchForCrateUse();
}
watchForCrateUse()
{
while( true )
{
level waittill("createAirDropCrate", dropCrate);
if( IsDefined( dropCrate ) && IsDefined( dropCrate.crateType ) && dropCrate.crateType == CONST_KILLSTREAK_NAME )
{
maps\mp\killstreaks\_airdrop::changeCrateWeight("airdrop_assault", CONST_KILLSTREAK_NAME, 0);
captured = wait_for_capture(dropCrate);
if(!captured)
{
//reEnable warhawk mortars care packages if it expires with out anyone picking it up
maps\mp\killstreaks\_airdrop::changeCrateWeight( "airdrop_assault", CONST_KILLSTREAK_NAME, CONST_CRATE_WEIGHT );
}
else
{
//Once its picked up it needs to remain off.
game["player_holding_level_killstrek"] = true;
break;
}
}
}
}
//death and capture are sent on the same frame but death is processed first :(
wait_for_capture(dropCrate)
{
result = watch_for_air_drop_death(dropCrate);
return !IsDefined(result); //If !isdefined the captured notify was also sent.
}
watch_for_air_drop_death(dropCrate)
{
dropCrate endon("captured");
dropCrate waittill("death");
waittillframeend;
return true;
}
customKillstreakFunc()
{
AddDebugCommand("devgui_cmd \"MP/Killstreak/Level Event:5/Care Package/" + CONST_DEBUG_NAME + "\" \"set scr_devgivecarepackage " + CONST_KILLSTREAK_NAME + "; set scr_devgivecarepackagetype airdrop_assault\"\n");
AddDebugCommand("devgui_cmd \"MP/Killstreak/Level Event:5/" + CONST_DEBUG_NAME + "\" \"set scr_givekillstreak " + CONST_KILLSTREAK_NAME + "\"\n");
level.killStreakFuncs[ CONST_KILLSTREAK_NAME ] = ::tryUseAgentKillstreak;
level.killstreakWeildWeapons[ "pirate_agent_mp" ] = CONST_KILLSTREAK_NAME;
}
cusomBotKillstreakFunc()
{
AddDebugCommand("devgui_cmd \"MP/Bots(Killstreak)/Level Events:5/" + CONST_DEBUG_NAME + "\" \"set scr_testclients_givekillstreak " + CONST_KILLSTREAK_NAME + "\"\n");
maps\mp\bots\_bots_ks::bot_register_killstreak_func( CONST_KILLSTREAK_NAME, maps\mp\bots\_bots_ks::bot_killstreak_simple_use );
}
// create agent
tryUseAgentKillstreak( lifeId, streakName ) // self == player with killstreak
{
setupCallbacks();
self.ghostCount = 0;
if ( self spawnGhost() )
{
thread playGhostMusic();
// give the player 2 to start
self thread delayedSpawnGhost();
self thread teamPlayerCardSplash( "used_" + streakName, self );
return true;
}
else
{
return false;
}
}
// this is almost identical to the version in agent_killstreak, except we use a different "pirate" type because we need different responses in the callbacks.
createSquadmate( victim )
{
// 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;
}
nearestPathNode = self getValidSpawnPathNodeNearPlayer( false, true );
if( !IsDefined(nearestPathNode) )
{
return undefined;
}
spawnOrigin = nearestPathNode.origin;
spawnAngles = VectorToAngles( self.origin - nearestPathNode.origin );
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;
}
// This is adapted from _agents.gsc::spawn_agent_player, except:
// 1. don't go through normal class / loadout pipeline, because that adds lots of random perks
// 2. force cqb personality
// 3. increase default health
// 4. don't allow crate usage
spawn_agent_ghost( 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( 200 );
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]]();
// give this perk before the loadout, because we don't want it to be taken away later (all spawned players have a few seconds of immunity from killstreaks)
self givePerk( "specialty_blindeye", false );
self maps\mp\gametypes\_class::giveLoadout( self.team, self.class, true );
isMelee = IsDefined( self.owner ) && IsDefined( self.owner.ghostCount ) && self.owner.ghostCount % 2 != 0;
self customizeSquadmate( isMelee ); // 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(); // don't allow ghosts to use crates
// 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();
// no battle chatter from ghost
// self thread maps\mp\gametypes\_battlechatter_mp::onPlayerSpawned();
level notify( "spawned_agent_player", self );
level notify( "spawned_agent", self );
self notify( "spawned_player" );
}
squadmate_agent_think()
{
self endon( "death" );
level endon( "game_ended" );
if ( IsDefined( self.owner ) )
{
self endon( "owner_disconnect" );
}
while(1)
{
self BotSetFlag( "cautious", true );
handled_by_gametype = self [[ self agentFunc("gametype_update") ]]();
if ( !handled_by_gametype )
{
if ( !self bot_is_guarding_player( self.owner ) )
self bot_guard_player( self.owner, 350 );
}
wait(0.05);
}
}
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);
// award XP for killing agents
/*
if( IsPlayer( eAttacker ) && IsDefined(self.owner) && eAttacker != self.owner )
{
self.owner leaderDialogOnPlayer( "squad_killed" );
self maps\mp\gametypes\_damage::onKillstreakKilled( eAttacker, sWeapon, sMeansOfDeath, iDamage, "destroyed_squad_mate" );
}
*/
pos = self GetTagOrigin( "j_mainroot" );
angles = AnglesToForward( self.angles );
body = self GetCorpseEntity();
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 );
}
if ( IsDefined( self.hat ) )
{
self.hat Delete();
}
PlayFx( getfx( "ghost_spawn" ), pos, angles );
body PlaySound( "pir_ghost_death" );
body delete();
}
customizeSquadmate( isMelee )
{
// Set up visuals for squadmate
if ( isMelee )
{
self SetModel( "pirate_ghost_2" );
}
else
{
self SetModel( "pirate_ghost_1" );
}
if( IsDefined( self.headModel ) )
{
self Detach( self.headModel, "" );
self.headModel = undefined;
}
self PlaySound( "pir_ghost_reappear" );
pos = self GetTagOrigin( "j_mainroot" );
PlayFx( getfx( "ghost_spawn" ), pos, AnglesToForward( self.angles ) );
self thread playTrailFx();
// Set up weapons
self TakeAllWeapons();
if ( isMelee )
{
self GiveWeapon( "iw6_piratehook_mp" );
self SwitchToWeapon( "iw6_piratehook_mp" );
self BotSetFlag( "prefer_melee", true );
}
else
{
self GiveWeapon( "iw6_pirategun_mp_akimbo" );
self SwitchToWeapon( "iw6_pirategun_mp_akimbo" );
}
// 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
self giveHat();
self.health = CONST_GHOST_HEALTH;
self SetSurfaceType( "snow" ); // !!! Hack-ish. see fx/maps/mp_battery3/iw_impacts.csv
maps\mp\gametypes\_battlechatter_mp::disableBattleChatter( self );
self thread watchBlink2();
self thread ghostPlayVO( self.owner.ghostCount );
}
giveHat()
{
hat = Spawn( "script_model", self.origin );
hat SetModel( CONST_GHOST_HAT_MODEL );
hat LinkTo( self, "j_head", (4, 0, 0), (90, 0, 0) );
// hat LinkTo( self, "j_helmet" );
self.hat = hat;
}
playTrailFx()
{
self endon ( "death" );
level endon( "game_ended" );
wait ( 0.25 );
PlayFXOnTag( getfx( "ghost_trail" ), self, "j_mainroot" );
}
playGhostMusic()
{
PlaySoundAtPos( (878.641, 1408.98, 203), "mus_drunk_sailor" );
}
spawnGhost()
{
agent = createSquadmate( self );
if ( IsDefined( agent ) )
{
self.ghostCount++;
if ( self.ghostCount >= CONST_MAX_GHOSTS )
{
level notify( "ghost_end" );
}
return true;
}
return false;
}
delayedSpawnGhost()
{
wait( 0.5 );
self spawnGhost();
}
ghostCloak()
{
self.isCloaked = true;
// play effect
pos = self GetTagOrigin( "j_mainroot" );
PlayFx( getfx( "ghost_blink" ), pos, AnglesToForward( self.angles ) );
// play sound
self PlaySound( "pir_ghost_disappear" );
// store old model
self.oldModel = self.model;
// swap model
self SetModel( self.oldModel + "_cloak" );
// fix hat
self.hat SetModel( CONST_GHOST_HAT_MODEL + "_cloak" );
}
ghostUncloak()
{
// play effect
pos = self GetTagOrigin( "j_mainroot" );
PlayFx( getfx( "ghost_blink" ), pos, AnglesToForward( self.angles ) );
// play sound
self PlaySound( "pir_ghost_reappear" );
// swap model
self SetModel( self.oldModel );
// fix hat
self.hat SetModel( CONST_GHOST_HAT_MODEL );
self.isCloaked = undefined;
}
watchBlink2()
{
self endon( "death" );
while ( true )
{
self waittill( "damage" );
if ( !IsDefined( self.isCloaked ) )
{
self ghostCloak();
}
self thread ghostWaitForUncloak();
}
}
ghostWaitForUncloak()
{
self notify( "ghostDamageTimer" );
self endon( "ghostDamageTimer" );
self endon( "death" );
wait( 4.0 );
self ghostUncloak();
}
GHOST_VO_COOLDOWN = 2000; // in ms INCREASE IN SHIP
ghostPlayVO( voIndex )
{
self endon( "death" );
level endon( "game_ended" );
// want to try to make sure each line is unique
voSets = [ "mp_pirate_prt1_ghost", "mp_pirate_prt2_ghost", "mp_pirate_cpt_ghost" ];
if ( !IsDefined( voIndex ) )
{
voIndex = RandomInt( 0, voSets.size );
}
else
{
voIndex = voIndex % voSets.size;
}
voAlias = voSets[ voIndex ];
self.owner.ghostVoTimeStamp = GetTime() + 1000; // make sure the ghosts don't speak right away
while ( IsDefined( self.owner ) )
{
self.owner waittill( "killed_enemy", victim, sWeapon, sMeansOfDeath );
if ( sWeapon == "pirate_agent_mp" && GetTime() > self.owner.ghostVoTimeStamp )
{
self.owner.ghostVoTimeStamp = GetTime() + GHOST_VO_COOLDOWN;
self PlaySound( voAlias );
}
}
}
// debug
/*
testGhost()
{
level endon( "game_ended" );
while ( true )
{
level waittill ( "connected", player );
if ( player IsHost() )
{
break;
}
}
player waittill( "spawned_player" );
wait( 1 );
player thread maps\mp\killstreaks\_killstreaks::giveKillstreak( CONST_KILLSTREAK_NAME, false, false, player );
}
*/