untyped global function ClassicMP_DefaultDropshipIntro_Setup const array DROPSHIP_IDLE_ANIMS = [ "Classic_MP_flyin_exit_playerA_idle", "Classic_MP_flyin_exit_playerB_idle", "Classic_MP_flyin_exit_playerC_idle", "Classic_MP_flyin_exit_playerD_idle" ] const array DROPSHIP_IDLE_ANIMS_POV = [ "Classic_MP_flyin_exit_povA_idle", "Classic_MP_flyin_exit_povB_idle", "Classic_MP_flyin_exit_povC_idle", "Classic_MP_flyin_exit_povD_idle" ] const array DROPSHIP_JUMP_ANIMS = [ "Classic_MP_flyin_exit_playerA_jump", "Classic_MP_flyin_exit_playerB_jump", "Classic_MP_flyin_exit_playerC_jump", "Classic_MP_flyin_exit_playerD_jump" ] const array DROPSHIP_JUMP_ANIMS_POV = [ "Classic_MP_flyin_exit_povA_jump", "Classic_MP_flyin_exit_povB_jump", "Classic_MP_flyin_exit_povC_jump", "Classic_MP_flyin_exit_povD_jump" ] const int MAX_DROPSHIP_PLAYERS = 4 global const float DROPSHIP_INTRO_LENGTH = 15.0 // TODO tweak this struct { table< entity, array > militiaDropships table< entity, array > imcDropships float introStartTime } file void function ClassicMP_DefaultDropshipIntro_Setup() { AddCallback_OnClientConnected( DropshipIntro_OnClientConnected ) AddCallback_GameStateEnter( eGameState.Prematch, OnPrematchStart ) } void function DropshipIntro_OnClientConnected( entity player ) { if ( GetGameState() == eGameState.Prematch ) { if( PlayerCanSpawn( player ) ) DoRespawnPlayer( player, null ) PutPlayerInDropship( player ) } } void function OnPrematchStart() { ClassicMP_OnIntroStarted() print( "starting dropship intro!" ) file.introStartTime = Time() // Clear Dropship arrays of Teams for Match Restarts (i.e Half-Times) file.militiaDropships.clear() file.imcDropships.clear() // Try to gather all possible Dropship spawn points for Team array validDropshipSpawns array dropshipSpawns = GetEntArrayByClass_Expensive( "info_spawnpoint_dropship_start" ) foreach ( entity dropshipSpawn in dropshipSpawns ) { if ( dropshipSpawn.HasKey( "gamemode_" + GetSpawnpointGamemodeOverride() ) ) if ( dropshipSpawn.kv[ "gamemode_" + GetSpawnpointGamemodeOverride() ] == "0" ) continue validDropshipSpawns.append( dropshipSpawn ) } // Use any spawn point if not enough valid for this Gamemode exists if ( validDropshipSpawns.len() < 2 ) validDropshipSpawns = dropshipSpawns // spawn dropships foreach ( entity dropshipSpawn in validDropshipSpawns ) { int createTeam = HasSwitchedSides() ? GetOtherTeam( dropshipSpawn.GetTeam() ) : dropshipSpawn.GetTeam() table< entity, array > teamDropships = createTeam == TEAM_MILITIA ? file.militiaDropships : file.imcDropships if ( teamDropships.len() >= 2 ) break entity dropship = CreateDropship( createTeam, dropshipSpawn.GetOrigin(), dropshipSpawn.GetAngles() ) AddAnimEvent( dropship, "dropship_warpout", WarpoutEffect ) dropship.SetValueForModelKey( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) if ( dropshipSpawn.GetTeam() == TEAM_IMC ) dropship.SetValueForModelKey( $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl" ) DispatchSpawn( dropship ) dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) if ( dropshipSpawn.GetTeam() == TEAM_IMC ) dropship.SetModel( $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl" ) teamDropships[ dropship ] <- [ null, null, null, null ] thread PlayAnim( dropship, "dropship_classic_mp_flyin" ) } // Populate Dropships foreach ( entity player in GetPlayerArray() ) { if ( !IsPrivateMatchSpectator( player ) ) { if( PlayerCanSpawn( player ) ) DoRespawnPlayer( player, null ) PutPlayerInDropship( player ) } else RespawnPrivateMatchSpectator( player ) } thread EndIntroWhenFinished() } void function EndIntroWhenFinished() { wait DROPSHIP_INTRO_LENGTH ClassicMP_OnIntroFinished() } void function PutPlayerInDropship( entity player ) { //Find the player's dropship and seat table< entity, array > teamDropships if ( player.GetTeam() == TEAM_MILITIA ) teamDropships = file.militiaDropships else teamDropships = file.imcDropships entity playerDropship array< int > availableShipSlots array< entity > introDropships int playerDropshipIndex = RandomInt( MAX_DROPSHIP_PLAYERS ) foreach( dropship, playerslot in teamDropships ) { introDropships.append( dropship ) for ( int i = 0; i < MAX_DROPSHIP_PLAYERS; i++ ) { if ( !IsValidPlayer( playerslot[i] ) ) availableShipSlots.append( i ) } if( !availableShipSlots.len() ) continue int slotPick = availableShipSlots.getrandom() playerslot[slotPick] = player playerDropship = dropship playerDropshipIndex = slotPick break } if( !IsAlive( playerDropship ) ) //If we're at this point, we have more players than we do dropships, so just pick a random one playerDropship = introDropships.getrandom() thread SpawnPlayerIntoDropship( player, playerDropshipIndex, playerDropship ) } void function SpawnPlayerIntoDropship( entity player, int playerDropshipIndex, entity playerDropship ) { player.EndSignal( "OnDestroy" ) player.EndSignal( "OnDeath" ) OnThreadEnd( function() : ( player, playerDropshipIndex, playerDropship ) { if ( IsValid( player ) ) { player.ClearParent() ClearPlayerAnimViewEntity( player ) } if( IsAlive( playerDropship ) ) { if ( playerDropship.GetTeam() == TEAM_MILITIA ) file.militiaDropships[ playerDropship ][ playerDropshipIndex ] = null else file.imcDropships[ playerDropship ][ playerDropshipIndex ] = null } }) HolsterAndDisableWeapons(player) player.DisableWeaponViewModel() // hide hud and fade screen out from black AddCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) ScreenFadeFromBlack( player, 0.5, 0.5 ) // faction leaders are done clientside, spawn them here Remote_CallFunction_NonReplay( player, "ServerCallback_SpawnFactionCommanderInDropship", playerDropship.dropship.GetEncodedEHandle(), file.introStartTime ) // do firstperson sequence FirstPersonSequenceStruct idleSequence idleSequence.firstPersonAnim = DROPSHIP_IDLE_ANIMS_POV[ playerDropshipIndex ] idleSequence.thirdPersonAnim = DROPSHIP_IDLE_ANIMS[ playerDropshipIndex ] idleSequence.attachment = "ORIGIN" idleSequence.teleport = true idleSequence.viewConeFunction = ViewConeRampFree idleSequence.hideProxy = true idleSequence.setInitialTime = Time() - file.introStartTime waitthread FirstPersonSequence( idleSequence, player, playerDropship ) // todo: possibly rework this to actually get the time the idle anim takes and start the starttime of the jump sequence for very late joiners using that // jump sequence FirstPersonSequenceStruct jumpSequence jumpSequence.firstPersonAnim = DROPSHIP_JUMP_ANIMS_POV[ playerDropshipIndex ] jumpSequence.thirdPersonAnim = DROPSHIP_JUMP_ANIMS[ playerDropshipIndex ] jumpSequence.attachment = "ORIGIN" jumpSequence.viewConeFunction = ViewConeFree jumpSequence.setInitialTime = max( 0.0, Time() - ( file.introStartTime + 11.0 ) ) // pretty sure you should do this with GetScriptedAnimEventCycleFrac? // idk unsure how to use that, all i know is getsequenceduration > the length it actually should be waitthread FirstPersonSequence( jumpSequence, player, playerDropship ) thread PlayerJumpsFromDropship( player ) } void function PlayerJumpsFromDropship( entity player ) { player.EndSignal( "OnDeath" ) player.EndSignal( "OnDestroy" ) OnThreadEnd( function() : ( player ) { if ( IsValid( player ) ) { // show weapon viewmodel and hud and let them move again player.MovementEnable() player.EnableWeaponViewModel() DeployAndEnableWeapons(player) RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) } }) // wait for player to hit the ground player.ClearParent() WaitFrame() player.SetVelocity( < 0, 0, -100 > ) // Toss players a bit down so it makes a smoother transition when jumping off the Dropship player.MovementDisable() // Disable all movement but let them look around still player.ConsumeDoubleJump() // MovementDisable doesn't prevent double jumps WaitFrame() while ( !player.IsOnGround() && !player.IsWallRunning() && !player.IsWallHanging() ) // todo this needs tweaking WaitFrame() if ( GetRoundsPlayed() == 0 ) //Intro is announced only for the first round in Vanilla as certain gamemodes have different announcements for rounds restarts TryGameModeAnnouncement( player ) }