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 IntroDropship { entity dropship int playersInDropship entity[MAX_DROPSHIP_PLAYERS] players } struct { // these used to be IntroDropship[2]s but i wanted to be able to use array.getrandom so they have to be actual arrays array militiaDropships 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 ) thread SpawnPlayerIntoDropship( player ) } void function OnPrematchStart() { ClassicMP_OnIntroStarted() print( "starting dropship intro!" ) file.introStartTime = Time() // make 2 empty dropship structs per team IntroDropship emptyDropship file.militiaDropships.clear() file.imcDropships.clear() 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 ) } // if no dropship spawns for this mode, just allow any dropship spawns if ( validDropshipSpawns.len() < 2 ) validDropshipSpawns = dropshipSpawns // spawn dropships foreach ( entity dropshipSpawn in validDropshipSpawns ) { // todo: possibly make this only spawn dropships if we've got enough players to need them int createTeam = HasSwitchedSides() ? GetOtherTeam( dropshipSpawn.GetTeam() ) : dropshipSpawn.GetTeam() array teamDropships = createTeam == TEAM_MILITIA ? file.militiaDropships : file.imcDropships if ( teamDropships.len() >= 2 ) continue // create entity entity dropship = CreateDropship( createTeam, dropshipSpawn.GetOrigin(), dropshipSpawn.GetAngles() ) teamDropships.append( clone emptyDropship ) teamDropships[ teamDropships.len() - 1 ].dropship = dropship AddAnimEvent( dropship, "dropship_warpout", WarpoutEffect ) dropship.SetValueForModelKey( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) DispatchSpawn( dropship ) // have to do this after dispatch otherwise it won't work for some reason dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) // could also use $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl", unsure which thread PlayAnim( dropship, "dropship_classic_mp_flyin" ) } foreach ( entity player in GetPlayerArray() ) thread SpawnPlayerIntoDropship( player ) thread EndIntroWhenFinished() } void function EndIntroWhenFinished() { wait 15.0 ClassicMP_OnIntroFinished() } void function SpawnPlayerIntoDropship( entity player ) { player.EndSignal( "OnDestroy" ) if ( IsAlive( player ) ) player.Die() // kill them so we don't have any issues respawning them later player.s.dropshipIntroIsJumping <- false OnThreadEnd( function() : ( player ) { if ( IsValid( player ) ) { player.ClearParent() ClearPlayerAnimViewEntity( player ) if ( !player.s.dropshipIntroIsJumping ) { player.MovementEnable() player.EnableWeaponViewModel() RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) } } }) WaitFrame() player.EndSignal( "OnDeath" ) // find the player's dropship and seat array teamDropships if ( player.GetTeam() == TEAM_MILITIA ) teamDropships = file.militiaDropships else teamDropships = file.imcDropships IntroDropship playerDropship int playerDropshipIndex = -1 foreach ( IntroDropship dropship in teamDropships ) for ( int i = 0; i < dropship.players.len(); i++ ) if ( dropship.players[ i ] == null ) { playerDropship = dropship playerDropshipIndex = i dropship.players[ i ] = player break } if ( playerDropship.dropship == null ) { // if we're at this point, we have more players than we do dropships, so just pick a random one playerDropship = teamDropships.getrandom() playerDropshipIndex = RandomInt( MAX_DROPSHIP_PLAYERS ) } // respawn player and holster their weapons so they aren't out player.RespawnPlayer( 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 thread FirstPersonSequence( idleSequence, player, playerDropship.dropship ) WaittillAnimDone( player ) // 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.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 thread FirstPersonSequence( jumpSequence, player, playerDropship.dropship ) WaittillAnimDone( player ) // somehow this is better than just waiting for the blocking FirstPersonSequence call? player.s.dropshipIntroIsJumping <- true 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 intro timer to be fully done wait ( file.introStartTime + DROPSHIP_INTRO_LENGTH ) - Time() player.MovementDisable() // disable all movement but let them look around still player.ConsumeDoubleJump() // movementdisable doesn't prevent double jumps // wait for player to hit the ground wait 0.1 // assume players will never actually hit ground before this while ( !player.IsOnGround() && !player.IsWallRunning() && !player.IsWallHanging() ) // todo this needs tweaking WaitFrame() TryGameModeAnnouncement( player ) }